mirror of https://github.com/docker/compose.git
ACI context creation
And remove gRPC stuff from the client for now
This commit is contained in:
parent
01cc77a09a
commit
551eb2326f
|
@ -56,6 +56,9 @@ func ContextCommand() *cobra.Command {
|
|||
|
||||
type createOpts struct {
|
||||
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.TypeContext{
|
||||
Type: contextType,
|
||||
return s.Create(name, store.TypedContext{
|
||||
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{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createACIContext(ctx context.Context, name string, opts createOpts) error {
|
||||
s := store.ContextStore(ctx)
|
||||
return s.Create(name, store.TypedContext{
|
||||
Type: "aci",
|
||||
Description: opts.description,
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
39
cli/main.go
39
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
|
||||
|
|
|
@ -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 {
|
||||
if c.conn != nil {
|
||||
return c.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
typed := getter()
|
||||
if err := json.Unmarshal(r.Metadata, typed); err != nil {
|
||||
var uc untypedContext
|
||||
if err := json.Unmarshal(um.Metadata, &uc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Metadata = reflect.ValueOf(typed).Elem().Interface()
|
||||
data, err := parse(uc.Data, getter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return &Metadata{
|
||||
Name: um.Name,
|
||||
Endpoints: um.Endpoints,
|
||||
Metadata: TypedContext{
|
||||
Description: uc.Description,
|
||||
Type: uc.Type,
|
||||
Data: data,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *store) Create(name string, data interface{}, endpoints map[string]interface{}) error {
|
||||
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) 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,
|
||||
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 untypedContextMetadata struct {
|
||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||
Endpoints map[string]interface{} `json:"endpoints,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
type untypedMetadata struct {
|
||||
Name string `json:",omitempty"`
|
||||
Metadata json.RawMessage `json:",omitempty"`
|
||||
Endpoints map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
type TypeContext struct {
|
||||
type untypedContext struct {
|
||||
Data json.RawMessage `json:",omitempty"`
|
||||
Description string `json:",omitempty"`
|
||||
Type 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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
require.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)
|
||||
meta, err := suite.store.Get("test", nil)
|
||||
require.Nil(suite.T(), err)
|
||||
require.NotNil(suite.T(), meta)
|
||||
require.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.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) {
|
||||
|
|
Loading…
Reference in New Issue