Browse Source

Merge pull request #27 from rumpl/change-context-store

ACI context creation
Djordje Lukic 5 years ago
parent
commit
f5bf355d1f
7 changed files with 193 additions and 127 deletions
  1. 27 12
      cli/cmd/context.go
  2. 3 3
      cli/cmd/example.go
  3. 4 35
      cli/main.go
  4. 21 20
      client/client.go
  5. 35 0
      context/context.go
  6. 80 31
      context/store/store.go
  7. 23 26
      context/store/store_test.go

+ 27 - 12
cli/cmd/context.go

@@ -55,7 +55,10 @@ func ContextCommand() *cobra.Command {
 }
 
 type createOpts struct {
-	description string
+	description       string
+	aciLocation       string
+	aciSubscriptionID string
+	aciResourceGroup  string
 }
 
 func createCommand() *cobra.Command {
@@ -70,6 +73,9 @@ func createCommand() *cobra.Command {
 	}
 
 	cmd.Flags().StringVar(&opts.description, "description", "", "Description of the context")
+	cmd.Flags().StringVar(&opts.aciLocation, "aci-location", "eastus", "Location")
+	cmd.Flags().StringVar(&opts.aciSubscriptionID, "aci-subscription-id", "", "Location")
+	cmd.Flags().StringVar(&opts.aciResourceGroup, "aci-resource-group", "", "Resource group")
 
 	return cmd
 }
@@ -87,14 +93,27 @@ func listCommand() *cobra.Command {
 }
 
 func runCreate(ctx context.Context, opts createOpts, name string, contextType string) error {
+	switch contextType {
+	case "aci":
+		return createACIContext(ctx, name, opts)
+	default:
+		s := store.ContextStore(ctx)
+		return s.Create(name, store.TypedContext{
+			Description: opts.description,
+		})
+	}
+}
+
+func createACIContext(ctx context.Context, name string, opts createOpts) error {
 	s := store.ContextStore(ctx)
-	return s.Create(name, store.TypeContext{
-		Type:        contextType,
+	return s.Create(name, store.TypedContext{
+		Type:        "aci",
 		Description: opts.description,
-	}, map[string]interface{}{
-		// If we don't set anything here the main docker cli
-		// doesn't know how to read the context any more
-		"docker": CliContext{},
+		Data: store.AciContext{
+			SubscriptionID: opts.aciSubscriptionID,
+			Location:       opts.aciLocation,
+			ResourceGroup:  opts.aciResourceGroup,
+		},
 	})
 }
 
@@ -110,11 +129,7 @@ func runList(ctx context.Context) error {
 	format := "%s\t%s\t%s\n"
 
 	for _, c := range contexts {
-		meta, ok := c.Metadata.(store.TypeContext)
-		if !ok {
-			return fmt.Errorf("Unable to list contexts, context %q is not valid", c.Name)
-		}
-		fmt.Fprintf(w, format, c.Name, meta.Description, meta.Type)
+		fmt.Fprintf(w, format, c.Name, c.Metadata.Description)
 	}
 
 	return w.Flush()

+ 3 - 3
cli/cmd/example.go

@@ -32,7 +32,6 @@ import (
 	"encoding/json"
 	"os"
 	"os/exec"
-	"time"
 
 	"github.com/docker/api/client"
 	"github.com/golang/protobuf/ptypes/empty"
@@ -77,7 +76,8 @@ func connect(ctx context.Context) (*client.Client, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "no backend address")
 	}
-	c, err := client.New("unix://"+address, 500*time.Millisecond)
+
+	c, err := client.New(ctx)
 	if err != nil {
 		if err != context.DeadlineExceeded {
 			return nil, errors.Wrap(err, "connect to backend")
@@ -89,7 +89,7 @@ func connect(ctx context.Context) (*client.Client, error) {
 		if err := cmd.Start(); err != nil {
 			return nil, errors.Wrap(err, "start backend")
 		}
-		cl, e := client.New("unix://"+address, 10*time.Second)
+		cl, e := client.New(ctx)
 		return cl, e
 	}
 	return c, nil

+ 4 - 35
cli/main.go

@@ -113,7 +113,7 @@ func main() {
 	ctx, cancel := util.NewSigContext()
 	defer cancel()
 
-	ctx, err := withCurrentContext(ctx, opts)
+	ctx, err := apicontext.WithCurrentContext(ctx, opts.Config, opts.Context)
 	if err != nil {
 		logrus.Fatal(err)
 	}
@@ -133,45 +133,14 @@ func main() {
 	}
 }
 
-type currentContextKey struct{}
-
-func withCurrentContext(ctx context.Context, opts mainOpts) (context.Context, error) {
-	config, err := apicontext.LoadConfigFile(opts.Config, "config.json")
-	if err != nil {
-		return ctx, err
-	}
-
-	currentContext := opts.Context
-	if currentContext == "" {
-		currentContext = config.CurrentContext
-	}
-	if currentContext == "" {
-		currentContext = "default"
-	}
-
-	logrus.Debugf("Current context %q", currentContext)
-
-	return context.WithValue(ctx, currentContextKey{}, currentContext), nil
-}
-
-// CurrentContext returns the current context name
-func CurrentContext(ctx context.Context) string {
-	cc, _ := ctx.Value(currentContextKey{}).(string)
-	return cc
-}
-
 func execMoby(ctx context.Context) {
-	currentContext := CurrentContext(ctx)
+	currentContext := apicontext.CurrentContext(ctx)
 	s := store.ContextStore(ctx)
 
-	cc, err := s.Get(currentContext)
-	if err != nil {
-		logrus.Fatal(err)
-	}
+	_, err := s.Get(currentContext, nil)
 	// Only run original docker command if the current context is not
 	// ours.
-	_, ok := cc.Metadata.(store.TypeContext)
-	if !ok {
+	if err != nil {
 		cmd := exec.Command("docker", os.Args[1:]...)
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout

+ 21 - 20
client/client.go

@@ -29,45 +29,46 @@ package client
 
 import (
 	"context"
-	"time"
 
 	"google.golang.org/grpc"
-	"google.golang.org/grpc/backoff"
 
 	v1 "github.com/docker/api/backend/v1"
+	"github.com/docker/api/containers"
+	apicontext "github.com/docker/api/context"
+	"github.com/docker/api/context/store"
+	"github.com/docker/api/example"
 )
 
 // New returns a GRPC client
-func New(address string, timeout time.Duration) (*Client, error) {
-	backoffConfig := backoff.DefaultConfig
-	backoffConfig.MaxDelay = 3 * time.Second
-	backoffConfig.BaseDelay = 10 * time.Millisecond
-	connParams := grpc.ConnectParams{
-		Backoff: backoffConfig,
-	}
-	opts := []grpc.DialOption{
-		grpc.WithInsecure(),
-		grpc.WithConnectParams(connParams),
-		grpc.WithBlock(),
-	}
-	ctx, cancel := context.WithTimeout(context.Background(), timeout)
-	defer cancel()
-	conn, err := grpc.DialContext(ctx, address, opts...)
+func New(ctx context.Context) (*Client, error) {
+	currentContext := apicontext.CurrentContext(ctx)
+	s := store.ContextStore(ctx)
+
+	cc, err := s.Get(currentContext, nil)
 	if err != nil {
 		return nil, err
 	}
+	contextType := s.GetType(cc)
 
 	return &Client{
-		conn:          conn,
-		BackendClient: v1.NewBackendClient(conn),
+		backendType: contextType,
 	}, nil
 }
 
 type Client struct {
 	conn *grpc.ClientConn
 	v1.BackendClient
+	backendType string
+}
+
+func (c *Client) ContainerService(ctx context.Context) containers.ContainerService {
+	return example.New()
+
 }
 
 func (c *Client) Close() error {
-	return c.conn.Close()
+	if c.conn != nil {
+		return c.conn.Close()
+	}
+	return nil
 }

+ 35 - 0
context/context.go

@@ -0,0 +1,35 @@
+package context
+
+import (
+	gocontext "context"
+
+	"github.com/sirupsen/logrus"
+	"golang.org/x/net/context"
+)
+
+type currentContextKey struct{}
+
+func WithCurrentContext(ctx gocontext.Context, configName string, contextName string) (context.Context, error) {
+	config, err := LoadConfigFile(configName, "config.json")
+	if err != nil {
+		return ctx, err
+	}
+
+	currentContext := contextName
+	if currentContext == "" {
+		currentContext = config.CurrentContext
+	}
+	if currentContext == "" {
+		currentContext = "default"
+	}
+
+	logrus.Debugf("Current context %q", currentContext)
+
+	return context.WithValue(ctx, currentContextKey{}, currentContext), nil
+}
+
+// CurrentContext returns the current context name
+func CurrentContext(ctx context.Context) string {
+	cc, _ := ctx.Value(currentContextKey{}).(string)
+	return cc
+}

+ 80 - 31
context/store/store.go

@@ -60,10 +60,12 @@ 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, getter func() interface{}) (*Metadata, error)
+	// GetType reurns the type of the context (docker, aci etc)
+	GetType(meta *Metadata) string
 	// Create creates a new context, it returns an error if a context with the
 	// same name exists already.
-	Create(name string, data interface{}, endpoints map[string]interface{}) error
+	Create(name string, data TypedContext) error
 	// List returns the list of created contexts
 	List() ([]*Metadata, error)
 }
@@ -86,8 +88,8 @@ func New(opts ...StoreOpt) (Store, error) {
 	if err != nil {
 		return nil, err
 	}
-	s := &store {
-		root: home,
+	s := &store{
+		root: filepath.Join(home, ".docker"),
 	}
 	for _, opt := range opts {
 		opt(s)
@@ -108,42 +110,72 @@ func New(opts ...StoreOpt) (Store, error) {
 }
 
 // Get returns the context with the given name
-func (s *store) Get(name string) (*Metadata, error) {
+func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
 	if name == "default" {
 		return &Metadata{}, nil
 	}
 
 	meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
-	return read(meta)
+	return read(meta, getter)
 }
 
-func read(meta string) (*Metadata, error) {
+func read(meta string, getter func() interface{}) (*Metadata, error) {
 	bytes, err := ioutil.ReadFile(meta)
 	if err != nil {
 		return nil, err
 	}
 
-	var r untypedContextMetadata
-	if err := json.Unmarshal(bytes, &r); err != nil {
+	var um untypedMetadata
+	if err := json.Unmarshal(bytes, &um); err != nil {
 		return nil, err
 	}
 
-	result := &Metadata{
-		Name:      r.Name,
-		Endpoints: r.Endpoints,
+	var uc untypedContext
+	if err := json.Unmarshal(um.Metadata, &uc); err != nil {
+		return nil, err
 	}
 
-	typed := getter()
-	if err := json.Unmarshal(r.Metadata, typed); err != nil {
+	data, err := parse(uc.Data, getter)
+	if err != nil {
 		return nil, err
 	}
 
-	result.Metadata = reflect.ValueOf(typed).Elem().Interface()
+	return &Metadata{
+		Name:      um.Name,
+		Endpoints: um.Endpoints,
+		Metadata: TypedContext{
+			Description: uc.Description,
+			Type:        uc.Type,
+			Data:        data,
+		},
+	}, nil
+}
 
-	return result, nil
+func parse(payload []byte, getter func() interface{}) (interface{}, error) {
+	if getter == nil {
+		var res map[string]interface{}
+		if err := json.Unmarshal(payload, &res); err != nil {
+			return nil, err
+		}
+		return res, nil
+	}
+	typed := getter()
+	if err := json.Unmarshal(payload, &typed); err != nil {
+		return nil, err
+	}
+	return reflect.ValueOf(typed).Elem().Interface(), nil
 }
 
-func (s *store) Create(name string, data interface{}, endpoints map[string]interface{}) error {
+func (s *store) GetType(meta *Metadata) string {
+	for k := range meta.Endpoints {
+		if k != "docker" {
+			return k
+		}
+	}
+	return "docker"
+}
+
+func (s *store) Create(name string, data TypedContext) error {
 	dir := contextdirOf(name)
 	metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
 	if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
@@ -155,10 +187,16 @@ func (s *store) Create(name string, data interface{}, endpoints map[string]inter
 		return err
 	}
 
+	if data.Data == nil {
+		data.Data = DummyContext{}
+	}
+
 	meta := Metadata{
-		Name:      name,
-		Metadata:  data,
-		Endpoints: endpoints,
+		Name:     name,
+		Metadata: data,
+		Endpoints: map[string]interface{}{
+			"docker": DummyContext{},
+		},
 	}
 
 	bytes, err := json.Marshal(&meta)
@@ -180,7 +218,7 @@ func (s *store) List() ([]*Metadata, error) {
 	for _, fi := range c {
 		if fi.IsDir() {
 			meta := filepath.Join(root, fi.Name(), metaFile)
-			r, err := read(meta)
+			r, err := read(meta, nil)
 			if err != nil {
 				return nil, err
 			}
@@ -195,23 +233,34 @@ func contextdirOf(name string) string {
 	return digest.FromString(name).Encoded()
 }
 
+type DummyContext struct{}
+
 type Metadata struct {
 	Name      string                 `json:",omitempty"`
-	Metadata  interface{}            `json:",omitempty"`
+	Metadata  TypedContext           `json:",omitempty"`
+	Endpoints map[string]interface{} `json:",omitempty"`
+}
+
+type untypedMetadata struct {
+	Name      string                 `json:",omitempty"`
+	Metadata  json.RawMessage        `json:",omitempty"`
 	Endpoints map[string]interface{} `json:",omitempty"`
 }
 
-type untypedContextMetadata struct {
-	Metadata  json.RawMessage        `json:"metadata,omitempty"`
-	Endpoints map[string]interface{} `json:"endpoints,omitempty"`
-	Name      string                 `json:"name,omitempty"`
+type untypedContext struct {
+	Data        json.RawMessage `json:",omitempty"`
+	Description string          `json:",omitempty"`
+	Type        string          `json:",omitempty"`
 }
 
-type TypeContext struct {
-	Type        string `json:",omitempty"`
-	Description string `json:",omitempty"`
+type TypedContext struct {
+	Type        string      `json:",omitempty"`
+	Description string      `json:",omitempty"`
+	Data        interface{} `json:",omitempty"`
 }
 
-func getter() interface{} {
-	return &TypeContext{}
+type AciContext struct {
+	SubscriptionID string `json:",omitempty"`
+	Location       string `json:",omitempty"`
+	ResourceGroup  string `json:",omitempty"`
 }

+ 23 - 26
context/store/store_test.go

@@ -33,7 +33,6 @@ import (
 	"os"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 )
@@ -60,47 +59,45 @@ func (suite *StoreTestSuite) AfterTest(suiteName, testName string) {
 }
 
 func (suite *StoreTestSuite) TestCreate() {
-	err := suite.store.Create("test", nil, nil)
-	assert.Nil(suite.T(), err)
+	err := suite.store.Create("test", TypedContext{})
+	require.Nil(suite.T(), err)
 }
 
 func (suite *StoreTestSuite) TestGetUnknown() {
-	meta, err := suite.store.Get("unknown")
-	assert.Nil(suite.T(), meta)
-	assert.Error(suite.T(), err)
+	meta, err := suite.store.Get("unknown", nil)
+	require.Nil(suite.T(), meta)
+	require.Error(suite.T(), err)
 }
 
 func (suite *StoreTestSuite) TestGet() {
-	err := suite.store.Create("test", TypeContext{
+	err := suite.store.Create("test", TypedContext{
 		Type:        "type",
 		Description: "description",
-	}, nil)
-	assert.Nil(suite.T(), err)
-
-	meta, err := suite.store.Get("test")
-	assert.Nil(suite.T(), err)
-	assert.NotNil(suite.T(), meta)
-	assert.Equal(suite.T(), "test", meta.Name)
-
-	m, ok := meta.Metadata.(TypeContext)
-	assert.Equal(suite.T(), ok, true)
-	assert.Equal(suite.T(), "description", m.Description)
-	assert.Equal(suite.T(), "type", m.Type)
+	})
+	require.Nil(suite.T(), err)
+
+	meta, err := suite.store.Get("test", nil)
+	require.Nil(suite.T(), err)
+	require.NotNil(suite.T(), meta)
+	require.Equal(suite.T(), "test", meta.Name)
+
+	require.Equal(suite.T(), "description", meta.Metadata.Description)
+	require.Equal(suite.T(), "type", meta.Metadata.Type)
 }
 
 func (suite *StoreTestSuite) TestList() {
-	err := suite.store.Create("test1", TypeContext{}, nil)
-	assert.Nil(suite.T(), err)
+	err := suite.store.Create("test1", TypedContext{})
+	require.Nil(suite.T(), err)
 
-	err = suite.store.Create("test2", TypeContext{}, nil)
-	assert.Nil(suite.T(), err)
+	err = suite.store.Create("test2", TypedContext{})
+	require.Nil(suite.T(), err)
 
 	contexts, err := suite.store.List()
-	assert.Nil(suite.T(), err)
+	require.Nil(suite.T(), err)
 
 	require.Equal(suite.T(), len(contexts), 2)
-	assert.Equal(suite.T(), contexts[0].Name, "test1")
-	assert.Equal(suite.T(), contexts[1].Name, "test2")
+	require.Equal(suite.T(), contexts[0].Name, "test1")
+	require.Equal(suite.T(), contexts[1].Name, "test2")
 }
 
 func TestExampleTestSuite(t *testing.T) {