package context import ( "fmt" "testing" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/kubernetes" "github.com/docker/cli/cli/context/store" "github.com/docker/cli/internal/test" "gotest.tools/v3/assert" "gotest.tools/v3/env" ) func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) *test.FakeCli { t.Helper() dir := t.TempDir() storeConfig := store.NewConfig( func() interface{} { return &command.DockerContext{} }, store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }), store.EndpointTypeGetter(kubernetes.KubernetesEndpoint, func() interface{} { return &kubernetes.EndpointMeta{} }), ) store := &command.ContextStoreWithDefault{ Store: store.New(dir, storeConfig), Resolver: func() (*command.DefaultContext, error) { return &command.DefaultContext{ Meta: store.Metadata{ Endpoints: map[string]interface{}{ docker.DockerEndpoint: docker.EndpointMeta{ Host: "unix:///var/run/docker.sock", }, }, Metadata: command.DockerContext{ Description: "", StackOrchestrator: command.OrchestratorSwarm, }, Name: command.DefaultContextName, }, TLS: store.ContextTLSData{}, }, nil }, } result := test.NewFakeCli(nil, opts...) for _, o := range opts { o(result) } result.SetContextStore(store) return result } func withCliConfig(configFile *configfile.ConfigFile) func(*test.FakeCli) { return func(m *test.FakeCli) { m.SetConfigFile(configFile) } } func TestCreateInvalids(t *testing.T) { cli := makeFakeCli(t) assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"})) tests := []struct { options CreateOptions expecterErr string }{ { expecterErr: `context name cannot be empty`, }, { options: CreateOptions{ Name: "default", }, expecterErr: `"default" is a reserved context name`, }, { options: CreateOptions{ Name: " ", }, expecterErr: `context name " " is invalid`, }, { options: CreateOptions{ Name: "existing-context", }, expecterErr: `context "existing-context" already exists`, }, { options: CreateOptions{ Name: "invalid-docker-host", Docker: map[string]string{ keyHost: "some///invalid/host", }, }, expecterErr: `unable to parse docker host`, }, { options: CreateOptions{ Name: "invalid-orchestrator", DefaultStackOrchestrator: "invalid", }, expecterErr: `specified orchestrator "invalid" is invalid, please use either kubernetes, swarm or all`, }, { options: CreateOptions{ Name: "orchestrator-kubernetes-no-endpoint", DefaultStackOrchestrator: "kubernetes", Docker: map[string]string{}, }, expecterErr: `cannot specify orchestrator "kubernetes" without configuring a Kubernetes endpoint`, }, { options: CreateOptions{ Name: "orchestrator-all-no-endpoint", DefaultStackOrchestrator: "all", Docker: map[string]string{}, }, expecterErr: `cannot specify orchestrator "all" without configuring a Kubernetes endpoint`, }, } for _, tc := range tests { tc := tc t.Run(tc.options.Name, func(t *testing.T) { err := RunCreate(cli, &tc.options) assert.ErrorContains(t, err, tc.expecterErr) }) } } func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) { assert.Equal(t, n+"\n", cli.OutBuffer().String()) assert.Equal(t, fmt.Sprintf("Successfully created context %q\n", n), cli.ErrBuffer().String()) } func TestCreateOrchestratorSwarm(t *testing.T) { cli := makeFakeCli(t) err := RunCreate(cli, &CreateOptions{ Name: "test", DefaultStackOrchestrator: "swarm", Docker: map[string]string{}, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, "test") } func TestCreateOrchestratorEmpty(t *testing.T) { cli := makeFakeCli(t) err := RunCreate(cli, &CreateOptions{ Name: "test", Docker: map[string]string{}, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, "test") } func validateTestKubeEndpoint(t *testing.T, s store.Reader, name string) { t.Helper() ctxMetadata, err := s.GetMetadata(name) assert.NilError(t, err) kubeMeta := ctxMetadata.Endpoints[kubernetes.KubernetesEndpoint].(kubernetes.EndpointMeta) kubeEP, err := kubeMeta.WithTLSData(s, name) assert.NilError(t, err) assert.Equal(t, "https://someserver.example.com", kubeEP.Host) assert.Equal(t, "the-ca", string(kubeEP.TLSData.CA)) assert.Equal(t, "the-cert", string(kubeEP.TLSData.Cert)) assert.Equal(t, "the-key", string(kubeEP.TLSData.Key)) } func createTestContextWithKube(t *testing.T, cli command.Cli) { t.Helper() revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig") defer revert() err := RunCreate(cli, &CreateOptions{ Name: "test", DefaultStackOrchestrator: "all", Kubernetes: map[string]string{ keyFrom: "default", }, Docker: map[string]string{}, }) assert.NilError(t, err) } func TestCreateOrchestratorAllKubernetesEndpointFromCurrent(t *testing.T) { cli := makeFakeCli(t) createTestContextWithKube(t, cli) assertContextCreateLogging(t, cli, "test") 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 := makeFakeCli(t) revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig") defer revert() cli.ResetOutputBuffers() 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", })) assertContextCreateLogging(t, cli, "original") cli.ResetOutputBuffers() 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", })) assertContextCreateLogging(t, cli, "dummy") cli.SetCurrentContext("dummy") for _, c := range cases { c := c t.Run(c.name, func(t *testing.T) { cli.ResetOutputBuffers() 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) assertContextCreateLogging(t, cli, c.name) newContext, err := cli.ContextStore().GetMetadata(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.example.com") }) } } 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 := makeFakeCli(t) revert := env.Patch(t, "KUBECONFIG", "./testdata/test-kubeconfig") defer revert() cli.ResetOutputBuffers() 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", })) assertContextCreateLogging(t, cli, "original") cli.SetCurrentContext("original") for _, c := range cases { c := c t.Run(c.name, func(t *testing.T) { cli.ResetOutputBuffers() err := RunCreate(cli, &CreateOptions{ Name: c.name, Description: c.description, DefaultStackOrchestrator: c.orchestrator, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, c.name) newContext, err := cli.ContextStore().GetMetadata(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.example.com") }) } }