Skip to content

Commit 3126920

Browse files
committed
Add context store config options and expose context commands
This will allow plugins to have custom typed endpoints, as well as create/remove/update contexts with the exact same results as the main CLI (thinking of things like `docker ee login https://my-ucp-server --context ucp-prod)` Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
1 parent cf6c238 commit 3126920

File tree

16 files changed

+273
-203
lines changed

16 files changed

+273
-203
lines changed

‎cli/command/cli.go‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,9 @@ type DockerCli struct {
8181
contextStore store.Store
8282
currentContext string
8383
dockerEndpoint docker.Endpoint
84+
contextStoreConfig store.Config
8485
}
8586

86-
var storeConfig = store.NewConfig(
87-
func() interface{} { return &DockerContext{} },
88-
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
89-
store.EndpointTypeGetter(kubcontext.KubernetesEndpoint, func() interface{} { return &kubcontext.EndpointMeta{} }),
90-
)
91-
9287
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
9388
func (cli *DockerCli) DefaultVersion() string {
9489
return cli.clientInfo.DefaultVersion
@@ -184,7 +179,7 @@ func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.Registry
184179
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
185180
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
186181
var err error
187-
cli.contextStore = store.New(cliconfig.ContextStoreDir(), storeConfig)
182+
cli.contextStore = store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
188183
cli.currentContext, err = resolveContextName(opts.Common, cli.configFile, cli.contextStore)
189184
if err != nil {
190185
return err
@@ -226,7 +221,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
226221

227222
// NewAPIClientFromFlags creates a new APIClient from command line flags
228223
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
229-
store := store.New(cliconfig.ContextStoreDir(), storeConfig)
224+
store := store.New(cliconfig.ContextStoreDir(), defaultContextStoreConfig())
230225
contextName, err := resolveContextName(opts, configFile, store)
231226
if err != nil {
232227
return nil, err
@@ -372,7 +367,7 @@ func (cli *DockerCli) StackOrchestrator(flagValue string) (Orchestrator, error)
372367
if currentContext != "" {
373368
contextstore := cli.contextStore
374369
if contextstore == nil {
375-
contextstore = store.New(cliconfig.ContextStoreDir(), storeConfig)
370+
contextstore = store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
376371
}
377372
ctxRaw, err := contextstore.GetContextMetadata(currentContext)
378373
if store.IsErrContextDoesNotExist(err) {
@@ -430,6 +425,7 @@ func NewDockerCli(ops ...DockerCliOption) (*DockerCli, error) {
430425
WithContentTrustFromEnv(),
431426
WithContainerizedClient(containerizedengine.NewClient),
432427
}
428+
cli.contextStoreConfig = defaultContextStoreConfig()
433429
ops = append(defaultOps, ops...)
434430
if err := cli.Apply(ops...); err != nil {
435431
return nil, err
@@ -501,3 +497,11 @@ func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigF
501497
}
502498
return "", nil
503499
}
500+
501+
func defaultContextStoreConfig() store.Config {
502+
return store.NewConfig(
503+
func() interface{} { return &DockerContext{} },
504+
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
505+
store.EndpointTypeGetter(kubcontext.KubernetesEndpoint, func() interface{} { return &kubcontext.EndpointMeta{} }),
506+
)
507+
}

‎cli/command/cli_options.go‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package command
22

33
import (
4+
"fmt"
45
"io"
56
"os"
67
"strconv"
78

9+
"github.com/docker/cli/cli/context/docker"
10+
"github.com/docker/cli/cli/context/kubernetes"
11+
"github.com/docker/cli/cli/context/store"
812
"github.com/docker/cli/cli/streams"
913
clitypes "github.com/docker/cli/types"
1014
"github.com/docker/docker/pkg/term"
@@ -87,3 +91,16 @@ func WithContainerizedClient(containerizedFn func(string) (clitypes.Containerize
8791
return nil
8892
}
8993
}
94+
95+
// WithContextEndpointType add support for an additional typed endpoint in the context store
96+
// Plugins should use this to store additional endpoints configuration in the context store
97+
func WithContextEndpointType(endpointName string, endpointType store.TypeGetter) DockerCliOption {
98+
return func(cli *DockerCli) error {
99+
switch endpointName {
100+
case docker.DockerEndpoint, kubernetes.KubernetesEndpoint:
101+
return fmt.Errorf("cannot change %q endpoint type", endpointName)
102+
}
103+
cli.contextStoreConfig.SetEndpoint(endpointName, endpointType)
104+
return nil
105+
}
106+
}

‎cli/command/context/create.go‎

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ import (
1414
"github.com/spf13/cobra"
1515
)
1616

17-
type createOptions struct {
18-
name string
19-
description string
20-
defaultStackOrchestrator string
21-
docker map[string]string
22-
kubernetes map[string]string
17+
// CreateOptions are the options used for creating a context
18+
type CreateOptions struct {
19+
Name string
20+
Description string
21+
DefaultStackOrchestrator string
22+
Docker map[string]string
23+
Kubernetes map[string]string
2324
}
2425

2526
func longCreateDescription() string {
@@ -43,61 +44,62 @@ func longCreateDescription() string {
4344
}
4445

4546
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
46-
opts := &createOptions{}
47+
opts := &CreateOptions{}
4748
cmd := &cobra.Command{
4849
Use: "create [OPTIONS] CONTEXT",
4950
Short: "Create a context",
5051
Args: cli.ExactArgs(1),
5152
RunE: func(cmd *cobra.Command, args []string) error {
52-
opts.name = args[0]
53-
return runCreate(dockerCli, opts)
53+
opts.Name = args[0]
54+
return RunCreate(dockerCli, opts)
5455
},
5556
Long: longCreateDescription(),
5657
}
5758
flags := cmd.Flags()
58-
flags.StringVar(&opts.description, "description", "", "Description of the context")
59+
flags.StringVar(&opts.Description, "description", "", "Description of the context")
5960
flags.StringVar(
60-
&opts.defaultStackOrchestrator,
61+
&opts.DefaultStackOrchestrator,
6162
"default-stack-orchestrator", "",
6263
"Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)")
63-
flags.StringToStringVar(&opts.docker, "docker", nil, "set the docker endpoint")
64-
flags.StringToStringVar(&opts.kubernetes, "kubernetes", nil, "set the kubernetes endpoint")
64+
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
65+
flags.StringToStringVar(&opts.Kubernetes, "kubernetes", nil, "set the kubernetes endpoint")
6566
return cmd
6667
}
6768

68-
func runCreate(cli command.Cli, o *createOptions) error {
69+
// RunCreate creates a Docker context
70+
func RunCreate(cli command.Cli, o *CreateOptions) error {
6971
s := cli.ContextStore()
70-
if err := checkContextNameForCreation(s, o.name); err != nil {
72+
if err := checkContextNameForCreation(s, o.Name); err != nil {
7173
return err
7274
}
73-
stackOrchestrator, err := command.NormalizeOrchestrator(o.defaultStackOrchestrator)
75+
stackOrchestrator, err := command.NormalizeOrchestrator(o.DefaultStackOrchestrator)
7476
if err != nil {
7577
return errors.Wrap(err, "unable to parse default-stack-orchestrator")
7678
}
7779
contextMetadata := store.ContextMetadata{
7880
Endpoints: make(map[string]interface{}),
7981
Metadata: command.DockerContext{
80-
Description: o.description,
82+
Description: o.Description,
8183
StackOrchestrator: stackOrchestrator,
8284
},
83-
Name: o.name,
85+
Name: o.Name,
8486
}
85-
if o.docker == nil {
87+
if o.Docker == nil {
8688
return errors.New("docker endpoint configuration is required")
8789
}
8890
contextTLSData := store.ContextTLSData{
8991
Endpoints: make(map[string]store.EndpointTLSData),
9092
}
91-
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.docker)
93+
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.Docker)
9294
if err != nil {
9395
return errors.Wrap(err, "unable to create docker endpoint config")
9496
}
9597
contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP
9698
if dockerTLS != nil {
9799
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS
98100
}
99-
if o.kubernetes != nil {
100-
kubernetesEP, kubernetesTLS, err := getKubernetesEndpointMetadataAndTLS(cli, o.kubernetes)
101+
if o.Kubernetes != nil {
102+
kubernetesEP, kubernetesTLS, err := getKubernetesEndpointMetadataAndTLS(cli, o.Kubernetes)
101103
if err != nil {
102104
return errors.Wrap(err, "unable to create kubernetes endpoint config")
103105
}
@@ -117,11 +119,11 @@ func runCreate(cli command.Cli, o *createOptions) error {
117119
if err := s.CreateOrUpdateContext(contextMetadata); err != nil {
118120
return err
119121
}
120-
if err := s.ResetContextTLSMaterial(o.name, &contextTLSData); err != nil {
122+
if err := s.ResetContextTLSMaterial(o.Name, &contextTLSData); err != nil {
121123
return err
122124
}
123-
fmt.Fprintln(cli.Out(), o.name)
124-
fmt.Fprintf(cli.Err(), "Successfully created context %q\n", o.name)
125+
fmt.Fprintln(cli.Out(), o.Name)
126+
fmt.Fprintf(cli.Err(), "Successfully created context %q\n", o.Name)
125127
return nil
126128
}
127129

‎cli/command/context/create_test.go‎

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -46,68 +46,68 @@ func TestCreateInvalids(t *testing.T) {
4646
defer cleanup()
4747
assert.NilError(t, cli.ContextStore().CreateOrUpdateContext(store.ContextMetadata{Name: "existing-context"}))
4848
tests := []struct {
49-
options createOptions
49+
options CreateOptions
5050
expecterErr string
5151
}{
5252
{
5353
expecterErr: `context name cannot be empty`,
5454
},
5555
{
56-
options: createOptions{
57-
name: " ",
56+
options: CreateOptions{
57+
Name: " ",
5858
},
5959
expecterErr: `context name " " is invalid`,
6060
},
6161
{
62-
options: createOptions{
63-
name: "existing-context",
62+
options: CreateOptions{
63+
Name: "existing-context",
6464
},
6565
expecterErr: `context "existing-context" already exists`,
6666
},
6767
{
68-
options: createOptions{
69-
name: "invalid-docker-host",
70-
docker: map[string]string{
68+
options: CreateOptions{
69+
Name: "invalid-docker-host",
70+
Docker: map[string]string{
7171
keyHost: "some///invalid/host",
7272
},
7373
},
7474
expecterErr: `unable to parse docker host`,
7575
},
7676
{
77-
options: createOptions{
78-
name: "invalid-orchestrator",
79-
defaultStackOrchestrator: "invalid",
77+
options: CreateOptions{
78+
Name: "invalid-orchestrator",
79+
DefaultStackOrchestrator: "invalid",
8080
},
8181
expecterErr: `specified orchestrator "invalid" is invalid, please use either kubernetes, swarm or all`,
8282
},
8383
{
84-
options: createOptions{
85-
name: "orchestrator-swarm-no-endpoint",
86-
defaultStackOrchestrator: "swarm",
84+
options: CreateOptions{
85+
Name: "orchestrator-swarm-no-endpoint",
86+
DefaultStackOrchestrator: "swarm",
8787
},
8888
expecterErr: `docker endpoint configuration is required`,
8989
},
9090
{
91-
options: createOptions{
92-
name: "orchestrator-kubernetes-no-endpoint",
93-
defaultStackOrchestrator: "kubernetes",
94-
docker: map[string]string{},
91+
options: CreateOptions{
92+
Name: "orchestrator-kubernetes-no-endpoint",
93+
DefaultStackOrchestrator: "kubernetes",
94+
Docker: map[string]string{},
9595
},
9696
expecterErr: `cannot specify orchestrator "kubernetes" without configuring a Kubernetes endpoint`,
9797
},
9898
{
99-
options: createOptions{
100-
name: "orchestrator-all-no-endpoint",
101-
defaultStackOrchestrator: "all",
102-
docker: map[string]string{},
99+
options: CreateOptions{
100+
Name: "orchestrator-all-no-endpoint",
101+
DefaultStackOrchestrator: "all",
102+
Docker: map[string]string{},
103103
},
104104
expecterErr: `cannot specify orchestrator "all" without configuring a Kubernetes endpoint`,
105105
},
106106
}
107107
for _, tc := range tests {
108108
tc := tc
109-
t.Run(tc.options.name, func(t *testing.T) {
110-
err := runCreate(cli, &tc.options)
109+
t.Run(tc.options.Name, func(t *testing.T) {
110+
err := RunCreate(cli, &tc.options)
111111
assert.ErrorContains(t, err, tc.expecterErr)
112112
})
113113
}
@@ -117,10 +117,10 @@ func TestCreateOrchestratorSwarm(t *testing.T) {
117117
cli, cleanup := makeFakeCli(t)
118118
defer cleanup()
119119

120-
err := runCreate(cli, &createOptions{
121-
name: "test",
122-
defaultStackOrchestrator: "swarm",
123-
docker: map[string]string{},
120+
err := RunCreate(cli, &CreateOptions{
121+
Name: "test",
122+
DefaultStackOrchestrator: "swarm",
123+
Docker: map[string]string{},
124124
})
125125
assert.NilError(t, err)
126126
assert.Equal(t, "test\n", cli.OutBuffer().String())
@@ -131,9 +131,9 @@ func TestCreateOrchestratorEmpty(t *testing.T) {
131131
cli, cleanup := makeFakeCli(t)
132132
defer cleanup()
133133

134-
err := runCreate(cli, &createOptions{
135-
name: "test",
136-
docker: map[string]string{},
134+
err := RunCreate(cli, &CreateOptions{
135+
Name: "test",
136+
Docker: map[string]string{},
137137
})
138138
assert.NilError(t, err)
139139
}
@@ -156,13 +156,13 @@ func createTestContextWithKube(t *testing.T, cli command.Cli) {
156156
revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig")
157157
defer revert()
158158

159-
err := runCreate(cli, &createOptions{
160-
name: "test",
161-
defaultStackOrchestrator: "all",
162-
kubernetes: map[string]string{
159+
err := RunCreate(cli, &CreateOptions{
160+
Name: "test",
161+
DefaultStackOrchestrator: "all",
162+
Kubernetes: map[string]string{
163163
keyFromCurrent: "true",
164164
},
165-
docker: map[string]string{},
165+
Docker: map[string]string{},
166166
})
167167
assert.NilError(t, err)
168168
}

0 commit comments

Comments
 (0)