From e2c7370a82fc2895504a7291c3cddaabfa4bc677 Mon Sep 17 00:00:00 2001 From: Djordje Lukic Date: Sun, 26 Apr 2020 22:07:50 +0200 Subject: [PATCH] Implement context list --- cli/cmd/context.go | 37 ++++++++++++++++++++++ context/context.go | 32 ------------------- context/store/store.go | 61 ++++++++++++++++++++++++++++++++----- context/store/store_test.go | 31 ++++++++++++++++++- go.mod | 2 +- go.sum | 4 +++ 6 files changed, 125 insertions(+), 42 deletions(-) delete mode 100644 context/context.go diff --git a/cli/cmd/context.go b/cli/cmd/context.go index 1d854710..8e8041fe 100644 --- a/cli/cmd/context.go +++ b/cli/cmd/context.go @@ -29,6 +29,9 @@ package cmd import ( "context" + "fmt" + "os" + "text/tabwriter" "github.com/docker/api/context/store" "github.com/spf13/cobra" @@ -45,6 +48,7 @@ func ContextCommand() *cobra.Command { cmd.AddCommand( createCommand(), + listCommand(), ) return cmd @@ -70,6 +74,17 @@ func createCommand() *cobra.Command { return cmd } +func listCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + RunE: func(cmd *cobra.Command, args []string) error { + return runList(cmd.Context()) + }, + } + return cmd +} + func runCreate(ctx context.Context, opts createOpts, name string, contextType string) error { s := store.ContextStore(ctx) return s.Create(name, store.TypeContext{ @@ -81,3 +96,25 @@ func runCreate(ctx context.Context, opts createOpts, name string, contextType st "docker": CliContext{}, }) } + +func runList(ctx context.Context) error { + s := store.ContextStore(ctx) + contexts, err := s.List() + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintln(w, "NAME\tDESCRIPTION\tTYPE") + format := "%s\t%s\t%s\n" + + for _, c := range contexts { + meta, ok := c.Metadata.(store.TypeContext) + if !ok { + return fmt.Errorf("Unable to list contexts, context %q is not valid", c.Name) + } + fmt.Fprintf(w, format, c.Name, meta.Description, meta.Type) + } + + return w.Flush() +} diff --git a/context/context.go b/context/context.go deleted file mode 100644 index 100ad311..00000000 --- a/context/context.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (c) 2020 Docker Inc. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, copy, - modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH - THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -package context - -type TypeContext struct { - Type string -} diff --git a/context/store/store.go b/context/store/store.go index 8750f386..d693b90a 100644 --- a/context/store/store.go +++ b/context/store/store.go @@ -56,9 +56,16 @@ func ContextStore(ctx context.Context) Store { return s } +// Store type Store interface { + // Get returns the context with with name, it returns an error if the + // context doesn't exist Get(name string) (*Metadata, error) + // Create creates a new context, it returns an error if a context with the + // same name exists already. Create(name string, data interface{}, endpoints map[string]interface{}) error + // List returns the list of created contexts + List() ([]*Metadata, error) } type store struct { @@ -92,23 +99,33 @@ func (s *store) Get(name string) (*Metadata, error) { } meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile) + return read(meta) +} + +func read(meta string) (*Metadata, error) { bytes, err := ioutil.ReadFile(meta) if err != nil { return nil, err } - r := &Metadata{ - Endpoints: make(map[string]interface{}), + var r untypedContextMetadata + if err := json.Unmarshal(bytes, &r); err != nil { + return nil, err + } + + result := &Metadata{ + Name: r.Name, + Endpoints: r.Endpoints, } typed := getter() - if err := json.Unmarshal(bytes, typed); err != nil { - return r, err + if err := json.Unmarshal(r.Metadata, typed); err != nil { + return nil, err } - r.Metadata = reflect.ValueOf(typed).Elem().Interface() + result.Metadata = reflect.ValueOf(typed).Elem().Interface() - return r, nil + return result, nil } func (s *store) Create(name string, data interface{}, endpoints map[string]interface{}) error { @@ -137,6 +154,28 @@ func (s *store) Create(name string, data interface{}, endpoints map[string]inter return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644) } +func (s *store) List() ([]*Metadata, error) { + root := filepath.Join(s.root, contextsDir, metadataDir) + c, err := ioutil.ReadDir(root) + if err != nil { + return nil, err + } + + var result []*Metadata + for _, fi := range c { + if fi.IsDir() { + meta := filepath.Join(root, fi.Name(), metaFile) + r, err := read(meta) + if err != nil { + return nil, err + } + result = append(result, r) + } + } + + return result, nil +} + func contextdirOf(name string) string { return digest.FromString(name).Encoded() } @@ -147,9 +186,15 @@ type Metadata struct { Endpoints map[string]interface{} `json:",omitempty"` } +type untypedContextMetadata struct { + Metadata json.RawMessage `json:"metadata,omitempty"` + Endpoints map[string]interface{} `json:"endpoints,omitempty"` + Name string `json:"name,omitempty"` +} + type TypeContext struct { - Type string - Description string + Type string `json:",omitempty"` + Description string `json:",omitempty"` } func getter() interface{} { diff --git a/context/store/store_test.go b/context/store/store_test.go index a5db64d9..43c96b47 100644 --- a/context/store/store_test.go +++ b/context/store/store_test.go @@ -29,6 +29,7 @@ package store import ( _ "crypto/sha256" + "fmt" "io/ioutil" "os" "testing" @@ -64,10 +65,38 @@ func TestCreate(t *testing.T) { func TestGet(t *testing.T) { setup(t, func(t *testing.T, store Store) { - err := store.Create("test", nil, nil) + err := store.Create("test", TypeContext{ + Type: "type", + Description: "description", + }, nil) assert.Nil(t, err) + meta, err := store.Get("test") assert.Nil(t, err) assert.NotNil(t, meta) + assert.Equal(t, "test", meta.Name) + + m, ok := meta.Metadata.(TypeContext) + assert.Equal(t, ok, true) + fmt.Printf("%#v\n", meta) + assert.Equal(t, "description", m.Description) + assert.Equal(t, "type", m.Type) + }) +} + +func TestList(t *testing.T) { + setup(t, func(t *testing.T, store Store) { + err := store.Create("test1", TypeContext{}, nil) + assert.Nil(t, err) + + err = store.Create("test2", TypeContext{}, nil) + assert.Nil(t, err) + + contexts, err := store.List() + assert.Nil(t, err) + + assert.Equal(t, len(contexts), 2) + assert.Equal(t, contexts[0].Name, "test1") + assert.Equal(t, contexts[1].Name, "test2") }) } diff --git a/go.mod b/go.mod index 226add97..47237d18 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 github.com/urfave/cli/v2 v2.2.0 - golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect + golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect golang.org/x/text v0.3.2 // indirect google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.21.0 diff --git a/go.sum b/go.sum index d82778e6..8568eec5 100644 --- a/go.sum +++ b/go.sum @@ -197,6 +197,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowK golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -213,6 +215,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/p golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=