Merge pull request #1773 from zappy-shu/create-context-from-current

add --from flag to context create
This commit is contained in:
Sebastiaan van Stijn
2019-04-09 16:38:46 +02:00
committed by GitHub
6 changed files with 301 additions and 56 deletions

View File

@ -21,6 +21,7 @@ type CreateOptions struct {
DefaultStackOrchestrator string
Docker map[string]string
Kubernetes map[string]string
From string
}
func longCreateDescription() string {
@ -63,6 +64,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
"Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)")
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
flags.StringToStringVar(&opts.Kubernetes, "kubernetes", nil, "set the kubernetes endpoint")
flags.StringVar(&opts.From, "from", "", "create context from a named context")
return cmd
}
@ -76,17 +78,20 @@ func RunCreate(cli command.Cli, o *CreateOptions) error {
if err != nil {
return errors.Wrap(err, "unable to parse default-stack-orchestrator")
}
contextMetadata := store.ContextMetadata{
Endpoints: make(map[string]interface{}),
Metadata: command.DockerContext{
Description: o.Description,
StackOrchestrator: stackOrchestrator,
},
Name: o.Name,
if o.From == "" && o.Docker == nil && o.Kubernetes == nil {
return createFromExistingContext(s, cli.CurrentContext(), stackOrchestrator, o)
}
if o.From != "" {
return createFromExistingContext(s, o.From, stackOrchestrator, o)
}
return createNewContext(o, stackOrchestrator, cli, s)
}
func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, cli command.Cli, s store.Store) error {
if o.Docker == nil {
return errors.New("docker endpoint configuration is required")
}
contextMetadata := newContextMetadata(stackOrchestrator, o)
contextTLSData := store.ContextTLSData{
Endpoints: make(map[string]store.EndpointTLSData),
}
@ -139,3 +144,52 @@ func checkContextNameForCreation(s store.Store, name string) error {
}
return nil
}
func createFromExistingContext(s store.Store, fromContextName string, stackOrchestrator command.Orchestrator, o *CreateOptions) error {
if len(o.Docker) != 0 || len(o.Kubernetes) != 0 {
return errors.New("cannot use --docker or --kubernetes flags when --from is set")
}
reader := store.Export(fromContextName, &descriptionAndOrchestratorStoreDecorator{
Store: s,
description: o.Description,
orchestrator: stackOrchestrator,
})
defer reader.Close()
return store.Import(o.Name, s, reader)
}
type descriptionAndOrchestratorStoreDecorator struct {
store.Store
description string
orchestrator command.Orchestrator
}
func (d *descriptionAndOrchestratorStoreDecorator) GetContextMetadata(name string) (store.ContextMetadata, error) {
c, err := d.Store.GetContextMetadata(name)
if err != nil {
return c, err
}
typedContext, err := command.GetDockerContext(c)
if err != nil {
return c, err
}
if d.description != "" {
typedContext.Description = d.description
}
if d.orchestrator != command.Orchestrator("") {
typedContext.StackOrchestrator = d.orchestrator
}
c.Metadata = typedContext
return c, nil
}
func newContextMetadata(stackOrchestrator command.Orchestrator, o *CreateOptions) store.ContextMetadata {
return store.ContextMetadata{
Endpoints: make(map[string]interface{}),
Metadata: command.DockerContext{
Description: o.Description,
StackOrchestrator: stackOrchestrator,
},
Name: o.Name,
}
}

View File

@ -105,13 +105,6 @@ func TestCreateInvalids(t *testing.T) {
},
expecterErr: `specified orchestrator "invalid" is invalid, please use either kubernetes, swarm or all`,
},
{
options: CreateOptions{
Name: "orchestrator-swarm-no-endpoint",
DefaultStackOrchestrator: "swarm",
},
expecterErr: `docker endpoint configuration is required`,
},
{
options: CreateOptions{
Name: "orchestrator-kubernetes-no-endpoint",
@ -185,7 +178,7 @@ func createTestContextWithKube(t *testing.T, cli command.Cli) {
Name: "test",
DefaultStackOrchestrator: "all",
Kubernetes: map[string]string{
keyFromCurrent: "true",
keyFrom: "default",
},
Docker: map[string]string{},
})
@ -198,3 +191,157 @@ func TestCreateOrchestratorAllKubernetesEndpointFromCurrent(t *testing.T) {
createTestContextWithKube(t, cli)
validateTestKubeEndpoint(t, cli.ContextStore(), "test")
}
func TestCreateFromContext(t *testing.T) {
cases := []struct {
name string
description string
orchestrator string
expectedDescription string
docker map[string]string
kubernetes map[string]string
expectedOrchestrator command.Orchestrator
}{
{
name: "no-override",
expectedDescription: "original description",
expectedOrchestrator: command.OrchestratorSwarm,
},
{
name: "override-description",
description: "new description",
expectedDescription: "new description",
expectedOrchestrator: command.OrchestratorSwarm,
},
{
name: "override-orchestrator",
orchestrator: "kubernetes",
expectedDescription: "original description",
expectedOrchestrator: command.OrchestratorKubernetes,
},
}
cli, cleanup := makeFakeCli(t)
defer cleanup()
revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")
defer revert()
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "original",
Description: "original description",
Docker: map[string]string{
keyHost: "tcp://42.42.42.42:2375",
},
Kubernetes: map[string]string{
keyFrom: "default",
},
DefaultStackOrchestrator: "swarm",
}))
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "dummy",
Description: "dummy description",
Docker: map[string]string{
keyHost: "tcp://24.24.24.24:2375",
},
Kubernetes: map[string]string{
keyFrom: "default",
},
DefaultStackOrchestrator: "swarm",
}))
cli.SetCurrentContext("dummy")
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := RunCreate(cli, &CreateOptions{
From: "original",
Name: c.name,
Description: c.description,
DefaultStackOrchestrator: c.orchestrator,
Docker: c.docker,
Kubernetes: c.kubernetes,
})
assert.NilError(t, err)
newContext, err := cli.ContextStore().GetContextMetadata(c.name)
assert.NilError(t, err)
newContextTyped, err := command.GetDockerContext(newContext)
assert.NilError(t, err)
dockerEndpoint, err := docker.EndpointFromContext(newContext)
assert.NilError(t, err)
kubeEndpoint := kubernetes.EndpointFromContext(newContext)
assert.Check(t, kubeEndpoint != nil)
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
assert.Equal(t, newContextTyped.StackOrchestrator, c.expectedOrchestrator)
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
assert.Equal(t, kubeEndpoint.Host, "https://someserver")
})
}
}
func TestCreateFromCurrent(t *testing.T) {
cases := []struct {
name string
description string
orchestrator string
expectedDescription string
expectedOrchestrator command.Orchestrator
}{
{
name: "no-override",
expectedDescription: "original description",
expectedOrchestrator: command.OrchestratorSwarm,
},
{
name: "override-description",
description: "new description",
expectedDescription: "new description",
expectedOrchestrator: command.OrchestratorSwarm,
},
{
name: "override-orchestrator",
orchestrator: "kubernetes",
expectedDescription: "original description",
expectedOrchestrator: command.OrchestratorKubernetes,
},
}
cli, cleanup := makeFakeCli(t)
defer cleanup()
revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")
defer revert()
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "original",
Description: "original description",
Docker: map[string]string{
keyHost: "tcp://42.42.42.42:2375",
},
Kubernetes: map[string]string{
keyFrom: "default",
},
DefaultStackOrchestrator: "swarm",
}))
cli.SetCurrentContext("original")
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := RunCreate(cli, &CreateOptions{
Name: c.name,
Description: c.description,
DefaultStackOrchestrator: c.orchestrator,
})
assert.NilError(t, err)
newContext, err := cli.ContextStore().GetContextMetadata(c.name)
assert.NilError(t, err)
newContextTyped, err := command.GetDockerContext(newContext)
assert.NilError(t, err)
dockerEndpoint, err := docker.EndpointFromContext(newContext)
assert.NilError(t, err)
kubeEndpoint := kubernetes.EndpointFromContext(newContext)
assert.Check(t, kubeEndpoint != nil)
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
assert.Equal(t, newContextTyped.StackOrchestrator, c.expectedOrchestrator)
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
assert.Equal(t, kubeEndpoint.Host, "https://someserver")
})
}
}

View File

@ -17,7 +17,7 @@ func createTestContextWithKubeAndSwarm(t *testing.T, cli command.Cli, name strin
Name: name,
DefaultStackOrchestrator: orchestrator,
Description: "description of " + name,
Kubernetes: map[string]string{keyFromCurrent: "true"},
Kubernetes: map[string]string{keyFrom: "default"},
Docker: map[string]string{keyHost: "https://someswarmserver"},
})
assert.NilError(t, err)

View File

@ -18,7 +18,7 @@ import (
)
const (
keyFromCurrent = "from-current"
keyFrom = "from"
keyHost = "host"
keyCA = "ca"
keyCert = "cert"
@ -36,7 +36,7 @@ type configKeyDescription struct {
var (
allowedDockerConfigKeys = map[string]struct{}{
keyFromCurrent: {},
keyFrom: {},
keyHost: {},
keyCA: {},
keyCert: {},
@ -44,15 +44,15 @@ var (
keySkipTLSVerify: {},
}
allowedKubernetesConfigKeys = map[string]struct{}{
keyFromCurrent: {},
keyFrom: {},
keyKubeconfig: {},
keyKubecontext: {},
keyKubenamespace: {},
}
dockerConfigKeysDescriptions = []configKeyDescription{
{
name: keyFromCurrent,
description: "Copy current Docker endpoint configuration",
name: keyFrom,
description: "Copy named context's Docker endpoint configuration",
},
{
name: keyHost,
@ -77,8 +77,8 @@ var (
}
kubernetesConfigKeysDescriptions = []configKeyDescription{
{
name: keyFromCurrent,
description: "Copy current Kubernetes endpoint configuration",
name: keyFrom,
description: "Copy named context's Kubernetes endpoint configuration",
},
{
name: keyKubeconfig,
@ -121,12 +121,15 @@ func getDockerEndpoint(dockerCli command.Cli, config map[string]string) (docker.
if err := validateConfig(config, allowedDockerConfigKeys); err != nil {
return docker.Endpoint{}, err
}
fromCurrent, err := parseBool(config, keyFromCurrent)
if err != nil {
return docker.Endpoint{}, err
}
if fromCurrent {
return dockerCli.DockerEndpoint(), nil
if contextName, ok := config[keyFrom]; ok {
metadata, err := dockerCli.ContextStore().GetContextMetadata(contextName)
if err != nil {
return docker.Endpoint{}, err
}
if ep, ok := metadata.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta); ok {
return docker.Endpoint{EndpointMeta: ep}, nil
}
return docker.Endpoint{}, errors.Errorf("unable to get endpoint from context %q", contextName)
}
tlsData, err := context.TLSDataFromFiles(config[keyCA], config[keyCert], config[keyKey])
if err != nil {
@ -169,25 +172,20 @@ func getKubernetesEndpoint(dockerCli command.Cli, config map[string]string) (*ku
if len(config) == 0 {
return nil, nil
}
fromCurrent, err := parseBool(config, keyFromCurrent)
if err != nil {
return nil, err
}
if fromCurrent {
if dockerCli.CurrentContext() != "" {
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(dockerCli.CurrentContext())
if contextName, ok := config[keyFrom]; ok {
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(contextName)
if err != nil {
return nil, err
}
endpointMeta := kubernetes.EndpointFromContext(ctxMeta)
if endpointMeta != nil {
res, err := endpointMeta.WithTLSData(dockerCli.ContextStore(), dockerCli.CurrentContext())
if err != nil {
return nil, err
}
endpointMeta := kubernetes.EndpointFromContext(ctxMeta)
if endpointMeta != nil {
res, err := endpointMeta.WithTLSData(dockerCli.ContextStore(), dockerCli.CurrentContext())
if err != nil {
return nil, err
}
return &res, nil
}
return &res, nil
}
// fallback to env-based kubeconfig
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {