Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Store context type in metadata to make retrocompatibility with previous contexts easier (potentially switching back and forth) #200

Merged
merged 1 commit into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/cmd/context/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
84 changes: 84 additions & 0 deletions context/store/contextmetadata.go
Original file line number Diff line number Diff line change
@@ -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 changes: 40 additions & 0 deletions context/store/contextmetadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package store

import (
"encoding/json"
"testing"

. "github.com/onsi/gomega"
Copy link
Contributor

@chris-crone chris-crone Jun 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We should decide as a team if we want to use gomega or testify/{require,assert}. Not at all urgent but a bit jarring to have two different libraries depending on where we are in the code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have gotest.tools, so there's three now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.. gotest.tools doesn't have all the functionality we need. e.g.: Test suites with setup and teardown functions. It also doesn't have something like testify's require which continues testing even if a failure is encountered.

"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))
}
53 changes: 13 additions & 40 deletions context/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ 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
// Create creates a new context, it returns an error if a context with the
// 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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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() {
Expand All @@ -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
}
Expand Down Expand Up @@ -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{}{
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion context/store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
8 changes: 5 additions & 3 deletions context/store/storedefault.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -65,6 +66,7 @@ func dockerDefaultContext() (*Metadata, error) {
},
},
Metadata: ContextMetadata{
Type: defaultContextType,
Description: "Current DOCKER_HOST based configuration",
StackOrchestrator: defaultCtx.Metadata.StackOrchestrator,
},
Expand Down
2 changes: 1 addition & 1 deletion server/proxy/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
Expand Down