浏览代码

Merge pull request #200 from docker/context_store_type

Store context type in metadata to make retrocompatibility with previous contexts easier (potentially switching back and forth)
Guillaume Tardif 5 年之前
父节点
当前提交
93623dc5aa

+ 1 - 1
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),

+ 2 - 2
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
 }

+ 84 - 0
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
+}

+ 40 - 0
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))
+}

+ 13 - 40
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)

+ 1 - 1
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() {

+ 5 - 3
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,
 		},

+ 1 - 1
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,
 		})
 	}