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-10 14:07:35 +02:00
parent 8930563a25
commit e7682682fb
8 changed files with 147 additions and 48 deletions

View File

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

View File

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

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 {
// 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)

View File

@ -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() {

View File

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

View File

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