mirror of https://github.com/docker/cli.git
Merge pull request #1773 from zappy-shu/create-context-from-current
add --from flag to context create
This commit is contained in:
commit
d6af3e143e
|
@ -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 == "" {
|
||||
|
|
|
@ -23,7 +23,7 @@ Create a context
|
|||
Docker endpoint config:
|
||||
|
||||
NAME DESCRIPTION
|
||||
from-current Copy current Docker endpoint configuration
|
||||
from Copy Docker endpoint configuration from an existing context
|
||||
host Docker endpoint on which to connect
|
||||
ca Trust certs signed only by this CA
|
||||
cert Path to TLS certificate file
|
||||
|
@ -33,14 +33,16 @@ skip-tls-verify Skip TLS certificate validation
|
|||
Kubernetes endpoint config:
|
||||
|
||||
NAME DESCRIPTION
|
||||
from-current Copy current Kubernetes endpoint configuration
|
||||
from Copy Kubernetes endpoint configuration from an existing context
|
||||
config-file Path to a Kubernetes config file
|
||||
context-override Overrides the context set in the kubernetes config file
|
||||
namespace-override Overrides the namespace set in the kubernetes config file
|
||||
|
||||
Example:
|
||||
|
||||
$ docker context create my-context --description "some description" --docker "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file"
|
||||
$ docker context create my-context \
|
||||
--description "some description" \
|
||||
--docker "host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file"
|
||||
|
||||
Options:
|
||||
--default-stack-orchestrator string Default orchestrator for
|
||||
|
@ -52,24 +54,68 @@ Options:
|
|||
(default [])
|
||||
--kubernetes stringToString set the kubernetes endpoint
|
||||
(default [])
|
||||
--from string Create the context from an existing context
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
Creates a new `context`. This will allow you to quickly switch the cli configuration to connect to different clusters or single nodes.
|
||||
Creates a new `context`. This allows you to quickly switch the cli
|
||||
configuration to connect to different clusters or single nodes.
|
||||
|
||||
To create a `context` out of an existing `DOCKER_HOST` based script, you can use the `from-current` config key:
|
||||
To create a context from scratch provide the docker and, if required,
|
||||
kubernetes options. The example below creates the context `my-context`
|
||||
with a docker endpoint of `/var/run/docker.sock` and a kubernetes configuration
|
||||
sourced from the file `/home/me/my-kube-config`:
|
||||
|
||||
```bash
|
||||
$ docker context create my-context \
|
||||
--docker host=/var/run/docker.sock \
|
||||
--kubernetes config-file=/home/me/my-kube-config
|
||||
```
|
||||
|
||||
Use the `--from=<context-name>` option to create a new context from
|
||||
an existing context. The example below creates a new context named `my-context`
|
||||
from the existing context `existing-context`:
|
||||
|
||||
```bash
|
||||
$ docker context create my-context --from existing-context
|
||||
```
|
||||
|
||||
If the `--from` option is not set, the `context` is created from the current context:
|
||||
|
||||
```bash
|
||||
$ docker context create my-context
|
||||
```
|
||||
|
||||
This can be used to create a context out of an existing `DOCKER_HOST` based script:
|
||||
|
||||
```bash
|
||||
$ source my-setup-script.sh
|
||||
$ docker context create my-context --docker "from-current=true"
|
||||
$ docker context create my-context
|
||||
```
|
||||
|
||||
Similarly, to reference the currently active Kubernetes configuration, you can use `--kubernetes "from-current=true"`:
|
||||
To source only the `docker` endpoint configuration from an existing context
|
||||
use the `--docker from=<context-name>` option. The example below creates a
|
||||
new context named `my-context` using the docker endpoint configuration from
|
||||
the existing context `existing-context` and a kubernetes configuration sourced
|
||||
from the file `/home/me/my-kube-config`:
|
||||
|
||||
```bash
|
||||
$ export KUBECONFIG=/path/to/my/kubeconfig
|
||||
$ docker context create my-context --kubernetes "from-current=true" --docker "host=/var/run/docker.sock"
|
||||
$ docker context create my-context \
|
||||
--docker from=existing-context \
|
||||
--kubernetes config-file=/home/me/my-kube-config
|
||||
```
|
||||
|
||||
Docker and Kubernetes endpoints configurations, as well as default stack orchestrator and description can be modified with `docker context update`
|
||||
To source only the `kubernetes` configuration from an existing context use the
|
||||
`--kubernetes from=<context-name>` option. The example below creates a new
|
||||
context named `my-context` using the kuberentes configuration from the existing
|
||||
context `existing-context` and a docker endpoint of `/var/run/docker.sock`:
|
||||
|
||||
```bash
|
||||
$ docker context create my-context \
|
||||
--docker host=/var/run/docker.sock \
|
||||
--kubernetes from=existing-context
|
||||
```
|
||||
|
||||
Docker and Kubernetes endpoints configurations, as well as default stack
|
||||
orchestrator and description can be modified with `docker context update`
|
|
@ -23,7 +23,7 @@ Update a context
|
|||
Docker endpoint config:
|
||||
|
||||
NAME DESCRIPTION
|
||||
from-current Copy current Docker endpoint configuration
|
||||
from Copy Docker endpoint configuration from an existing context
|
||||
host Docker endpoint on which to connect
|
||||
ca Trust certs signed only by this CA
|
||||
cert Path to TLS certificate file
|
||||
|
@ -33,7 +33,7 @@ skip-tls-verify Skip TLS certificate validation
|
|||
Kubernetes endpoint config:
|
||||
|
||||
NAME DESCRIPTION
|
||||
from-current Copy current Kubernetes endpoint configuration
|
||||
from Copy Kubernetes endpoint configuration from an existing context
|
||||
config-file Path to a Kubernetes config file
|
||||
context-override Overrides the context set in the kubernetes config file
|
||||
namespace-override Overrides the namespace set in the kubernetes config file
|
||||
|
|
Loading…
Reference in New Issue