From e7682682fbbe00efe995341afa89c233a63efc8a Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Wed, 10 Jun 2020 14:07:35 +0200 Subject: [PATCH] Store context type in metadata to make retrocompatibility with previous contexts easier (potentially switching back and forth) --- cli/cmd/context/ls.go | 2 +- client/client.go | 4 +- context/store/contextmetadata.go | 84 +++++++++++++++++++++++++++ context/store/contextmetadata_test.go | 40 +++++++++++++ context/store/store.go | 53 +++++------------ context/store/store_test.go | 2 +- context/store/storedefault.go | 8 ++- server/proxy/contexts.go | 2 +- 8 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 context/store/contextmetadata.go create mode 100644 context/store/contextmetadata_test.go diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index d72b5a0b6..967df9d09 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -79,7 +79,7 @@ func runList(ctx context.Context) error { fmt.Fprintf(w, format, contextName, - c.Type, + c.Type(), c.Metadata.Description, getEndpoint("docker", c.Endpoints), getEndpoint("kubernetes", c.Endpoints), diff --git a/client/client.go b/client/client.go index 8e37dc5b5..a4a923ef4 100644 --- a/client/client.go +++ b/client/client.go @@ -49,13 +49,13 @@ func New(ctx context.Context) (*Client, error) { return nil, err } - service, err := backend.Get(ctx, cc.Type) + service, err := backend.Get(ctx, cc.Type()) if err != nil { return nil, err } return &Client{ - backendType: cc.Type, + backendType: cc.Type(), bs: service, }, nil } diff --git a/context/store/contextmetadata.go b/context/store/contextmetadata.go new file mode 100644 index 000000000..a6ab560d6 --- /dev/null +++ b/context/store/contextmetadata.go @@ -0,0 +1,84 @@ +package store + +import "encoding/json" + +// DockerContext represents the docker context metadata +type DockerContext struct { + Name string `json:",omitempty"` + Metadata ContextMetadata `json:",omitempty"` + Endpoints map[string]interface{} `json:",omitempty"` +} + +// Type the context type +func (m *DockerContext) Type() string { + if m.Metadata.Type == "" { + return defaultContextType + } + return m.Metadata.Type +} + +// ContextMetadata is represtentation of the data we put in a context +// metadata +type ContextMetadata struct { + Type string + Description string + StackOrchestrator string + AdditionalFields map[string]interface{} +} + +// AciContext is the context for the ACI backend +type AciContext struct { + SubscriptionID string `json:",omitempty"` + Location string `json:",omitempty"` + ResourceGroup string `json:",omitempty"` +} + +// MobyContext is the context for the moby backend +type MobyContext struct{} + +// ExampleContext is the context for the example backend +type ExampleContext struct{} + +// MarshalJSON implements custom JSON marshalling +func (dc ContextMetadata) MarshalJSON() ([]byte, error) { + s := map[string]interface{}{} + if dc.Description != "" { + s["Description"] = dc.Description + } + if dc.StackOrchestrator != "" { + s["StackOrchestrator"] = dc.StackOrchestrator + } + if dc.Type != "" { + s["Type"] = dc.Type + } + if dc.AdditionalFields != nil { + for k, v := range dc.AdditionalFields { + s[k] = v + } + } + return json.Marshal(s) +} + +// UnmarshalJSON implements custom JSON marshalling +func (dc *ContextMetadata) UnmarshalJSON(payload []byte) error { + var data map[string]interface{} + if err := json.Unmarshal(payload, &data); err != nil { + return err + } + for k, v := range data { + switch k { + case "Description": + dc.Description = v.(string) + case "StackOrchestrator": + dc.StackOrchestrator = v.(string) + case "Type": + dc.Type = v.(string) + default: + if dc.AdditionalFields == nil { + dc.AdditionalFields = make(map[string]interface{}) + } + dc.AdditionalFields[k] = v + } + } + return nil +} diff --git a/context/store/contextmetadata_test.go b/context/store/contextmetadata_test.go new file mode 100644 index 000000000..87b252dd5 --- /dev/null +++ b/context/store/contextmetadata_test.go @@ -0,0 +1,40 @@ +package store + +import ( + "encoding/json" + "testing" + + . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" +) + +type ContextTestSuite struct { + suite.Suite +} + +func (suite *ContextTestSuite) TestDockerContextMetadataKeepAdditionalFields() { + c := ContextMetadata{ + Description: "test", + Type: "aci", + StackOrchestrator: "swarm", + AdditionalFields: map[string]interface{}{ + "foo": "bar", + }, + } + jsonBytes, err := json.Marshal(c) + Expect(err).To(BeNil()) + Expect(string(jsonBytes)).To(Equal(`{"Description":"test","StackOrchestrator":"swarm","Type":"aci","foo":"bar"}`)) + + var c2 ContextMetadata + err = json.Unmarshal(jsonBytes, &c2) + Expect(err).To(BeNil()) + Expect(c2.AdditionalFields["foo"]).To(Equal("bar")) + Expect(c2.Type).To(Equal("aci")) + Expect(c2.StackOrchestrator).To(Equal("swarm")) + Expect(c2.Description).To(Equal("test")) +} + +func TestPs(t *testing.T) { + RegisterTestingT(t) + suite.Run(t, new(ContextTestSuite)) +} diff --git a/context/store/store.go b/context/store/store.go index 3adb8e477..5844fcbd4 100644 --- a/context/store/store.go +++ b/context/store/store.go @@ -72,7 +72,7 @@ func ContextStore(ctx context.Context) Store { type Store interface { // Get returns the context with name, it returns an error if the context // doesn't exist - Get(name string) (*Metadata, error) + Get(name string) (*DockerContext, error) // GetEndpoint sets the `v` parameter to the value of the endpoint for a // particular context type GetEndpoint(name string, v interface{}) error @@ -80,7 +80,7 @@ type Store interface { // same name exists already. Create(name string, contextType string, description string, data interface{}) error // List returns the list of created contexts - List() ([]*Metadata, error) + List() ([]*DockerContext, error) // Remove removes a context by name from the context store Remove(name string) error } @@ -104,34 +104,6 @@ const ( ExampleContextType = "example" ) -// Metadata represents the docker context metadata -type Metadata struct { - Name string `json:",omitempty"` - Type string `json:",omitempty"` - Metadata ContextMetadata `json:",omitempty"` - Endpoints map[string]interface{} `json:",omitempty"` -} - -// ContextMetadata is represtentation of the data we put in a context -// metadata -type ContextMetadata struct { - Description string `json:",omitempty"` - StackOrchestrator string `json:",omitempty"` -} - -// AciContext is the context for the ACI backend -type AciContext struct { - SubscriptionID string `json:",omitempty"` - Location string `json:",omitempty"` - ResourceGroup string `json:",omitempty"` -} - -// MobyContext is the context for the moby backend -type MobyContext struct{} - -// ExampleContext is the context for the example backend -type ExampleContext struct{} - type store struct { root string } @@ -175,7 +147,7 @@ func New(opts ...Opt) (Store, error) { } // Get returns the context with the given name -func (s *store) Get(name string) (*Metadata, error) { +func (s *store) Get(name string) (*DockerContext, error) { meta := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name), metaFile) m, err := read(meta) if os.IsNotExist(err) { @@ -192,14 +164,15 @@ func (s *store) GetEndpoint(name string, data interface{}) error { if err != nil { return err } - if _, ok := meta.Endpoints[meta.Type]; !ok { - return errors.Wrapf(errdefs.ErrNotFound, "endpoint of type %q", meta.Type) + contextType := meta.Type() + if _, ok := meta.Endpoints[contextType]; !ok { + return errors.Wrapf(errdefs.ErrNotFound, "endpoint of type %q", contextType) } dstPtrValue := reflect.ValueOf(data) dstValue := reflect.Indirect(dstPtrValue) - val := reflect.ValueOf(meta.Endpoints[meta.Type]) + val := reflect.ValueOf(meta.Endpoints[contextType]) valIndirect := reflect.Indirect(val) if dstValue.Type() != valIndirect.Type() { @@ -211,13 +184,13 @@ func (s *store) GetEndpoint(name string, data interface{}) error { return nil } -func read(meta string) (*Metadata, error) { +func read(meta string) (*DockerContext, error) { bytes, err := ioutil.ReadFile(meta) if err != nil { return nil, err } - var metadata Metadata + var metadata DockerContext if err := json.Unmarshal(bytes, &metadata); err != nil { return nil, err } @@ -270,10 +243,10 @@ func (s *store) Create(name string, contextType string, description string, data return err } - meta := Metadata{ + meta := DockerContext{ Name: name, - Type: contextType, Metadata: ContextMetadata{ + Type: contextType, Description: description, }, Endpoints: map[string]interface{}{ @@ -290,14 +263,14 @@ func (s *store) Create(name string, contextType string, description string, data return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644) } -func (s *store) List() ([]*Metadata, error) { +func (s *store) List() ([]*DockerContext, error) { root := filepath.Join(s.root, contextsDir, metadataDir) c, err := ioutil.ReadDir(root) if err != nil { return nil, err } - var result []*Metadata + var result []*DockerContext for _, fi := range c { if fi.IsDir() { meta := filepath.Join(root, fi.Name(), metaFile) diff --git a/context/store/store_test.go b/context/store/store_test.go index aa9164da2..ed3add676 100644 --- a/context/store/store_test.go +++ b/context/store/store_test.go @@ -104,7 +104,7 @@ func (suite *StoreTestSuite) TestGet() { require.Equal(suite.T(), "test", meta.Name) require.Equal(suite.T(), "description", meta.Metadata.Description) - require.Equal(suite.T(), "type", meta.Type) + require.Equal(suite.T(), "type", meta.Type()) } func (suite *StoreTestSuite) TestRemoveNotFound() { diff --git a/context/store/storedefault.go b/context/store/storedefault.go index 82d151878..3ce345af4 100644 --- a/context/store/storedefault.go +++ b/context/store/storedefault.go @@ -8,6 +8,8 @@ import ( "github.com/pkg/errors" ) +const defaultContextType = "docker" + // Represents a context as created by the docker cli type defaultContext struct { Metadata ContextMetadata @@ -31,7 +33,7 @@ type endpoint struct { DefaultNamespace string } -func dockerDefaultContext() (*Metadata, error) { +func dockerDefaultContext() (*DockerContext, error) { cmd := exec.Command("docker-classic", "context", "inspect", "default") var stdout bytes.Buffer cmd.Stdout = &stdout @@ -52,9 +54,8 @@ func dockerDefaultContext() (*Metadata, error) { defaultCtx := ctx[0] - meta := Metadata{ + meta := DockerContext{ Name: "default", - Type: "docker", Endpoints: map[string]interface{}{ "docker": Endpoint{ Host: defaultCtx.Endpoints.Docker.Host, @@ -65,6 +66,7 @@ func dockerDefaultContext() (*Metadata, error) { }, }, Metadata: ContextMetadata{ + Type: defaultContextType, Description: "Current DOCKER_HOST based configuration", StackOrchestrator: defaultCtx.Metadata.StackOrchestrator, }, diff --git a/server/proxy/contexts.go b/server/proxy/contexts.go index a0c4f6945..fb02d362f 100644 --- a/server/proxy/contexts.go +++ b/server/proxy/contexts.go @@ -36,7 +36,7 @@ func (cp *contextsProxy) List(ctx context.Context, request *contextsv1.ListReque for _, c := range contexts { result.Contexts = append(result.Contexts, &contextsv1.Context{ Name: c.Name, - ContextType: c.Type, + ContextType: c.Type(), Current: c.Name == configFile.CurrentContext, }) }