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)
This commit is contained in:
Guillaume Tardif 2020-06-11 13:34:05 +02:00 committed by GitHub
commit 93623dc5aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 48 deletions

View File

@ -79,7 +79,7 @@ func runList(ctx context.Context) error {
fmt.Fprintf(w, fmt.Fprintf(w,
format, format,
contextName, contextName,
c.Type, c.Type(),
c.Metadata.Description, c.Metadata.Description,
getEndpoint("docker", c.Endpoints), getEndpoint("docker", c.Endpoints),
getEndpoint("kubernetes", c.Endpoints), getEndpoint("kubernetes", c.Endpoints),

View File

@ -49,13 +49,13 @@ func New(ctx context.Context) (*Client, error) {
return nil, err return nil, err
} }
service, err := backend.Get(ctx, cc.Type) service, err := backend.Get(ctx, cc.Type())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Client{ return &Client{
backendType: cc.Type, backendType: cc.Type(),
bs: service, bs: service,
}, nil }, nil
} }

View File

@ -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
}

View File

@ -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))
}

View File

@ -72,7 +72,7 @@ func ContextStore(ctx context.Context) Store {
type Store interface { type Store interface {
// Get returns the context with name, it returns an error if the context // Get returns the context with name, it returns an error if the context
// doesn't exist // 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 // GetEndpoint sets the `v` parameter to the value of the endpoint for a
// particular context type // particular context type
GetEndpoint(name string, v interface{}) error GetEndpoint(name string, v interface{}) error
@ -80,7 +80,7 @@ type Store interface {
// same name exists already. // same name exists already.
Create(name string, contextType string, description string, data interface{}) error Create(name string, contextType string, description string, data interface{}) error
// List returns the list of created contexts // List returns the list of created contexts
List() ([]*Metadata, error) List() ([]*DockerContext, error)
// Remove removes a context by name from the context store // Remove removes a context by name from the context store
Remove(name string) error Remove(name string) error
} }
@ -104,34 +104,6 @@ const (
ExampleContextType = "example" 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 { type store struct {
root string root string
} }
@ -175,7 +147,7 @@ func New(opts ...Opt) (Store, error) {
} }
// Get returns the context with the given name // 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) meta := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name), metaFile)
m, err := read(meta) m, err := read(meta)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -192,14 +164,15 @@ func (s *store) GetEndpoint(name string, data interface{}) error {
if err != nil { if err != nil {
return err return err
} }
if _, ok := meta.Endpoints[meta.Type]; !ok { contextType := meta.Type()
return errors.Wrapf(errdefs.ErrNotFound, "endpoint of type %q", meta.Type) if _, ok := meta.Endpoints[contextType]; !ok {
return errors.Wrapf(errdefs.ErrNotFound, "endpoint of type %q", contextType)
} }
dstPtrValue := reflect.ValueOf(data) dstPtrValue := reflect.ValueOf(data)
dstValue := reflect.Indirect(dstPtrValue) dstValue := reflect.Indirect(dstPtrValue)
val := reflect.ValueOf(meta.Endpoints[meta.Type]) val := reflect.ValueOf(meta.Endpoints[contextType])
valIndirect := reflect.Indirect(val) valIndirect := reflect.Indirect(val)
if dstValue.Type() != valIndirect.Type() { if dstValue.Type() != valIndirect.Type() {
@ -211,13 +184,13 @@ func (s *store) GetEndpoint(name string, data interface{}) error {
return nil return nil
} }
func read(meta string) (*Metadata, error) { func read(meta string) (*DockerContext, error) {
bytes, err := ioutil.ReadFile(meta) bytes, err := ioutil.ReadFile(meta)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var metadata Metadata var metadata DockerContext
if err := json.Unmarshal(bytes, &metadata); err != nil { if err := json.Unmarshal(bytes, &metadata); err != nil {
return nil, err return nil, err
} }
@ -270,10 +243,10 @@ func (s *store) Create(name string, contextType string, description string, data
return err return err
} }
meta := Metadata{ meta := DockerContext{
Name: name, Name: name,
Type: contextType,
Metadata: ContextMetadata{ Metadata: ContextMetadata{
Type: contextType,
Description: description, Description: description,
}, },
Endpoints: map[string]interface{}{ 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) 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) root := filepath.Join(s.root, contextsDir, metadataDir)
c, err := ioutil.ReadDir(root) c, err := ioutil.ReadDir(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var result []*Metadata var result []*DockerContext
for _, fi := range c { for _, fi := range c {
if fi.IsDir() { if fi.IsDir() {
meta := filepath.Join(root, fi.Name(), metaFile) meta := filepath.Join(root, fi.Name(), metaFile)

View File

@ -104,7 +104,7 @@ func (suite *StoreTestSuite) TestGet() {
require.Equal(suite.T(), "test", meta.Name) require.Equal(suite.T(), "test", meta.Name)
require.Equal(suite.T(), "description", meta.Metadata.Description) 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() { func (suite *StoreTestSuite) TestRemoveNotFound() {

View File

@ -8,6 +8,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
const defaultContextType = "docker"
// Represents a context as created by the docker cli // Represents a context as created by the docker cli
type defaultContext struct { type defaultContext struct {
Metadata ContextMetadata Metadata ContextMetadata
@ -31,7 +33,7 @@ type endpoint struct {
DefaultNamespace string DefaultNamespace string
} }
func dockerDefaultContext() (*Metadata, error) { func dockerDefaultContext() (*DockerContext, error) {
cmd := exec.Command("docker-classic", "context", "inspect", "default") cmd := exec.Command("docker-classic", "context", "inspect", "default")
var stdout bytes.Buffer var stdout bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
@ -52,9 +54,8 @@ func dockerDefaultContext() (*Metadata, error) {
defaultCtx := ctx[0] defaultCtx := ctx[0]
meta := Metadata{ meta := DockerContext{
Name: "default", Name: "default",
Type: "docker",
Endpoints: map[string]interface{}{ Endpoints: map[string]interface{}{
"docker": Endpoint{ "docker": Endpoint{
Host: defaultCtx.Endpoints.Docker.Host, Host: defaultCtx.Endpoints.Docker.Host,
@ -65,6 +66,7 @@ func dockerDefaultContext() (*Metadata, error) {
}, },
}, },
Metadata: ContextMetadata{ Metadata: ContextMetadata{
Type: defaultContextType,
Description: "Current DOCKER_HOST based configuration", Description: "Current DOCKER_HOST based configuration",
StackOrchestrator: defaultCtx.Metadata.StackOrchestrator, StackOrchestrator: defaultCtx.Metadata.StackOrchestrator,
}, },

View File

@ -36,7 +36,7 @@ func (cp *contextsProxy) List(ctx context.Context, request *contextsv1.ListReque
for _, c := range contexts { for _, c := range contexts {
result.Contexts = append(result.Contexts, &contextsv1.Context{ result.Contexts = append(result.Contexts, &contextsv1.Context{
Name: c.Name, Name: c.Name,
ContextType: c.Type, ContextType: c.Type(),
Current: c.Name == configFile.CurrentContext, Current: c.Name == configFile.CurrentContext,
}) })
} }