ACI context creation

And remove gRPC stuff from the client for now
This commit is contained in:
Djordje Lukic 2020-04-29 16:21:46 +02:00
parent 01cc77a09a
commit 551eb2326f
7 changed files with 193 additions and 127 deletions

View File

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

View File

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

View File

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

View File

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

35
context/context.go Normal file
View File

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

View File

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

View File

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