Merge pull request #1773 from zappy-shu/create-context-from-current
add --from flag to context create
This commit is contained in:
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 == "" {
|
||||
|
||||
Reference in New Issue
Block a user