Browse Source

Context create aci is now a subcommand, as Moby and example. Root `docker context create` also allows backward compatibility to create docker contexts as before

Guillaume Tardif 5 years ago
parent
commit
113350a09d

+ 88 - 45
cli/cmd/context/create.go

@@ -29,75 +29,118 @@ package context
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
 
 
-	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
-	"github.com/docker/api/client"
+	"github.com/docker/api/cli/dockerclassic"
 	"github.com/docker/api/context/store"
 	"github.com/docker/api/context/store"
 )
 )
 
 
-// AciCreateOpts Options for ACI context create
-type AciCreateOpts struct {
-	description       string
-	aciLocation       string
-	aciSubscriptionID string
-	aciResourceGroup  string
+type descriptionCreateOpts struct {
+	description string
 }
 }
 
 
 func createCommand() *cobra.Command {
 func createCommand() *cobra.Command {
-	var opts AciCreateOpts
+	const longHelp = `Create a new context
+
+Create docker engine context: 
+$ docker context create CONTEXT [flags]
+
+Create Azure Container Instances context:
+$ docker context create aci CONTEXT [flags]
+(see docker context create aci --help)
+
+Docker endpoint config:
+
+NAME                DESCRIPTION
+from                Copy named context's Docker endpoint configuration
+host                Docker endpoint on which to connect
+ca                  Trust certs signed only by this CA
+cert                Path to TLS certificate file
+key                 Path to TLS key file
+skip-tls-verify     Skip TLS certificate validation
+
+Kubernetes endpoint config:
+
+NAME                 DESCRIPTION
+from                 Copy named context's Kubernetes endpoint configuration
+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"`
+
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "create CONTEXT BACKEND [OPTIONS]",
-		Short: "Create a context",
-		Args:  cobra.ExactArgs(2),
+		Use:   "create CONTEXT",
+		Short: "Create new context",
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return runCreate(cmd.Context(), opts, args[0], args[1])
+			return dockerclassic.ExecCmd(cmd)
 		},
 		},
+		Long: longHelp,
 	}
 	}
 
 
-	cmd.Flags().StringVar(&opts.description, "description", "", "Description of the context")
-	cmd.Flags().StringVar(&opts.aciLocation, "aci-location", "eastus", "Location")
-	cmd.Flags().StringVar(&opts.aciSubscriptionID, "aci-subscription-id", "", "Location")
-	cmd.Flags().StringVar(&opts.aciResourceGroup, "aci-resource-group", "", "Resource group")
+	cmd.AddCommand(
+		createAciCommand(),
+		createMobyCommand(),
+		createExampleCommand(),
+	)
+
+	flags := cmd.Flags()
+	flags.String("description", "", "Description of the context")
+	flags.String(
+		"default-stack-orchestrator", "",
+		"Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)")
+	flags.StringToString("docker", nil, "set the docker endpoint")
+	flags.StringToString("kubernetes", nil, "set the kubernetes endpoint")
+	flags.String("from", "", "create context from a named context")
+
+	return cmd
+}
 
 
+func createMobyCommand() *cobra.Command {
+	var opts descriptionCreateOpts
+	cmd := &cobra.Command{
+		Use:    "moby CONTEXT",
+		Short:  "Create a context for accessing docker engine with new CLI commands",
+		Args:   cobra.ExactArgs(1),
+		Hidden: true,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return createDockerContext(cmd.Context(), args[0], store.MobyContextType, opts.description, store.MobyContext{})
+		},
+	}
+	addDescriptionFlag(cmd, &opts.description)
 	return cmd
 	return cmd
 }
 }
 
 
-func runCreate(ctx context.Context, opts AciCreateOpts, name string, contextType string) error {
-	contextData, description, err := getContextData(ctx, contextType, opts)
-	if err != nil {
-		return nil
+func createExampleCommand() *cobra.Command {
+	var opts descriptionCreateOpts
+	cmd := &cobra.Command{
+		Use:    "example CONTEXT",
+		Short:  "Create a test context returning fixed output",
+		Args:   cobra.ExactArgs(1),
+		Hidden: true,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return createDockerContext(cmd.Context(), args[0], store.ExampleContextType, opts.description, store.ExampleContext{})
+		},
 	}
 	}
+
+	addDescriptionFlag(cmd, &opts.description)
+	return cmd
+}
+
+func createDockerContext(ctx context.Context, name string, contextType string, description string, data interface{}) error {
 	s := store.ContextStore(ctx)
 	s := store.ContextStore(ctx)
-	return s.Create(
+	result := s.Create(
 		name,
 		name,
 		contextType,
 		contextType,
 		description,
 		description,
-		contextData,
+		data,
 	)
 	)
+	return result
 }
 }
 
 
-func getContextData(ctx context.Context, contextType string, opts AciCreateOpts) (interface{}, string, error) {
-	switch contextType {
-	case "aci":
-		cs, err := client.GetCloudService(ctx, "aci")
-		if err != nil {
-			return nil, "", errors.Wrap(err, "cannot connect to ACI backend")
-		}
-		params := map[string]string{
-			"aciSubscriptionId": opts.aciSubscriptionID,
-			"aciResourceGroup":  opts.aciResourceGroup,
-			"aciLocation":       opts.aciLocation,
-			"description":       opts.description,
-		}
-		return cs.CreateContextData(ctx, params)
-	case "moby":
-		return store.MobyContext{}, opts.description, nil
-	case "example":
-		return store.ExampleContext{}, opts.description, nil
-	default:
-		return nil, "", errors.New(fmt.Sprintf("incorrect context type %s, must be one of (aci | moby | docker)", contextType))
-	}
+func addDescriptionFlag(cmd *cobra.Command, descriptionOpt *string) {
+	cmd.Flags().StringVar(descriptionOpt, "description", "", "Description of the context")
 }
 }

+ 0 - 35
cli/cmd/context/create_test.go

@@ -1,35 +0,0 @@
-package context
-
-import (
-	"context"
-	"testing"
-
-	"github.com/docker/api/context/store"
-
-	. "github.com/onsi/gomega"
-	"github.com/stretchr/testify/suite"
-
-	_ "github.com/docker/api/example"
-	"github.com/docker/api/tests/framework"
-)
-
-type PsSuite struct {
-	framework.CliSuite
-}
-
-func (sut *PsSuite) TestCreateContextDataMoby() {
-	data, description, err := getContextData(context.TODO(), "moby", AciCreateOpts{})
-	Expect(err).To(BeNil())
-	Expect(data).To(Equal(store.MobyContext{}))
-	Expect(description).To(Equal(""))
-}
-
-func (sut *PsSuite) TestErrorOnUnknownContextType() {
-	_, _, err := getContextData(context.TODO(), "foo", AciCreateOpts{})
-	Expect(err).To(MatchError("incorrect context type foo, must be one of (aci | moby | docker)"))
-}
-
-func TestPs(t *testing.T) {
-	RegisterTestingT(t)
-	suite.Run(t, new(PsSuite))
-}

+ 85 - 0
cli/cmd/context/createaci.go

@@ -0,0 +1,85 @@
+/*
+	Copyright (c) 2020 Docker Inc.
+
+	Permission is hereby granted, free of charge, to any person
+	obtaining a copy of this software and associated documentation
+	files (the "Software"), to deal in the Software without
+	restriction, including without limitation the rights to use, copy,
+	modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be
+	included in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+	EXPRESS OR IMPLIED,
+	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+	HOLDERS BE LIABLE FOR ANY CLAIM,
+	DAMAGES OR OTHER LIABILITY,
+	WHETHER IN AN ACTION OF CONTRACT,
+	TORT OR OTHERWISE,
+	ARISING FROM, OUT OF OR IN CONNECTION WITH
+	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+package context
+
+import (
+	"context"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/docker/api/client"
+	"github.com/docker/api/context/store"
+)
+
+type aciCreateOpts struct {
+	description    string
+	location       string
+	subscriptionID string
+	resourceGroup  string
+}
+
+func createAciCommand() *cobra.Command {
+	var opts aciCreateOpts
+	cmd := &cobra.Command{
+		Use:   "aci CONTEXT [flags]",
+		Short: "Create a context for Azure Container Instances",
+		Args:  cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			contextData, description, err := getAciContextData(cmd.Context(), opts)
+			if err != nil {
+				return nil
+			}
+			return createDockerContext(cmd.Context(), args[0], store.AciContextType, description, contextData)
+		},
+	}
+
+	addDescriptionFlag(cmd, &opts.description)
+	cmd.Flags().StringVar(&opts.location, "location", "eastus", "Location")
+	cmd.Flags().StringVar(&opts.subscriptionID, "subscription-id", "", "Location")
+	cmd.Flags().StringVar(&opts.resourceGroup, "resource-group", "", "Resource group")
+
+	return cmd
+}
+
+func getAciContextData(ctx context.Context, opts aciCreateOpts) (interface{}, string, error) {
+	cs, err := client.GetCloudService(ctx, store.AciContextType)
+	if err != nil {
+		return nil, "", errors.Wrap(err, "cannot connect to ACI backend")
+	}
+	return cs.CreateContextData(ctx, convertAciOpts(opts))
+}
+
+func convertAciOpts(opts aciCreateOpts) map[string]string {
+	return map[string]string{
+		"aciSubscriptionId": opts.subscriptionID,
+		"aciResourceGroup":  opts.resourceGroup,
+		"aciLocation":       opts.location,
+		"description":       opts.description,
+	}
+}

+ 2 - 2
context/store/store.go

@@ -211,8 +211,8 @@ func toTypedEndpoints(endpoints map[string]interface{}) (map[string]interface{},
 			return nil, err
 			return nil, err
 		}
 		}
 		typeGetters := getters()
 		typeGetters := getters()
-		typeGetter, ok := typeGetters[k];
-		if  !ok {
+		typeGetter, ok := typeGetters[k]
+		if !ok {
 			typeGetter = func() interface{} {
 			typeGetter = func() interface{} {
 				return &Endpoint{}
 				return &Endpoint{}
 			}
 			}

+ 1 - 1
moby/e2e/backend_test.go

@@ -16,7 +16,7 @@ type MobyBackendTestSuite struct {
 }
 }
 
 
 func (m *MobyBackendTestSuite) BeforeTest(suiteName string, testName string) {
 func (m *MobyBackendTestSuite) BeforeTest(suiteName string, testName string) {
-	m.NewDockerCommand("context", "create", "test-context", "moby").ExecOrDie()
+	m.NewDockerCommand("context", "create", "moby", "test-context").ExecOrDie()
 	m.NewDockerCommand("context", "use", "test-context").ExecOrDie()
 	m.NewDockerCommand("context", "use", "test-context").ExecOrDie()
 }
 }
 
 

+ 1 - 11
tests/aci-e2e/e2e-aci_test.go

@@ -38,16 +38,6 @@ type E2eACISuite struct {
 	Suite
 	Suite
 }
 }
 
 
-func (s *E2eACISuite) TestContextHelp() {
-	It("ensures context command includes azure-login and aci-create", func() {
-		output := s.NewDockerCommand("context", "create", "--help").ExecOrDie()
-		Expect(output).To(ContainSubstring("docker context create CONTEXT BACKEND [OPTIONS] [flags]"))
-		Expect(output).To(ContainSubstring("--aci-location"))
-		Expect(output).To(ContainSubstring("--aci-subscription-id"))
-		Expect(output).To(ContainSubstring("--aci-resource-group"))
-	})
-}
-
 func (s *E2eACISuite) TestContextDefault() {
 func (s *E2eACISuite) TestContextDefault() {
 	It("should be initialized with default context", func() {
 	It("should be initialized with default context", func() {
 		_, err := s.NewCommand("docker", "context", "rm", "-f", contextName).Exec()
 		_, err := s.NewCommand("docker", "context", "rm", "-f", contextName).Exec()
@@ -70,7 +60,7 @@ func (s *E2eACISuite) TestACIBackend() {
 		Expect(err).To(BeNil())
 		Expect(err).To(BeNil())
 		subscriptionID = *models[0].SubscriptionID
 		subscriptionID = *models[0].SubscriptionID
 
 
-		s.NewDockerCommand("context", "create", contextName, "aci", "--aci-subscription-id", subscriptionID, "--aci-resource-group", resourceGroupName, "--aci-location", location).ExecOrDie()
+		s.NewDockerCommand("context", "create", "aci", contextName, "--subscription-id", subscriptionID, "--resource-group", resourceGroupName, "--location", location).ExecOrDie()
 		// Expect(output).To(ContainSubstring("ACI context acitest created"))
 		// Expect(output).To(ContainSubstring("ACI context acitest created"))
 	})
 	})
 
 

+ 23 - 23
tests/e2e/e2e_test.go

@@ -46,36 +46,36 @@ type E2eSuite struct {
 }
 }
 
 
 func (s *E2eSuite) TestContextHelp() {
 func (s *E2eSuite) TestContextHelp() {
-	It("ensures context command includes azure-login and aci-create", func() {
-		output := s.NewDockerCommand("context", "create", "--help").ExecOrDie()
-		Expect(output).To(ContainSubstring("docker context create CONTEXT BACKEND [OPTIONS] [flags]"))
-		Expect(output).To(ContainSubstring("--aci-location"))
-		Expect(output).To(ContainSubstring("--aci-subscription-id"))
-		Expect(output).To(ContainSubstring("--aci-resource-group"))
-	})
+	output := s.NewDockerCommand("context", "create", "aci", "--help").ExecOrDie()
+	Expect(output).To(ContainSubstring("docker context create aci CONTEXT [flags]"))
+	Expect(output).To(ContainSubstring("--location"))
+	Expect(output).To(ContainSubstring("--subscription-id"))
+	Expect(output).To(ContainSubstring("--resource-group"))
 }
 }
 
 
-func (s *E2eSuite) TestContextDefault() {
-	It("should be initialized with default context", func() {
-		output := s.NewDockerCommand("context", "show").ExecOrDie()
-		Expect(output).To(ContainSubstring("default"))
-		output = s.NewCommand("docker", "context", "ls").ExecOrDie()
-		golden.Assert(s.T(), output, GoldenFile("ls-out-default"))
-	})
+func (s *E2eSuite) TestListAndShowDefaultContext() {
+	output := s.NewDockerCommand("context", "show").ExecOrDie()
+	Expect(output).To(ContainSubstring("default"))
+	output = s.NewCommand("docker", "context", "ls").ExecOrDie()
+	golden.Assert(s.T(), output, GoldenFile("ls-out-default"))
 }
 }
 
 
-func (s *E2eSuite) TestContextLegacy() {
-	It("should inspect default", func() {
-		output := s.NewDockerCommand("context", "inspect", "default").ExecOrDie()
-		Expect(output).To(ContainSubstring(`"Name": "default"`))
-	})
+func (s *E2eSuite) TestCreateDockerContextAndListIt() {
+	s.NewDockerCommand("context", "create", "test-docker", "--from", "default").ExecOrDie()
+	output := s.NewCommand("docker", "context", "ls").ExecOrDie()
+	golden.Assert(s.T(), output, GoldenFile("ls-out-test-docker"))
+}
+
+func (s *E2eSuite) TestInspectDefaultContext() {
+	output := s.NewDockerCommand("context", "inspect", "default").ExecOrDie()
+	Expect(output).To(ContainSubstring(`"Name": "default"`))
 }
 }
 
 
 func (s *E2eSuite) TestContextCreateParseErrorDoesNotDelegateToLegacy() {
 func (s *E2eSuite) TestContextCreateParseErrorDoesNotDelegateToLegacy() {
 	It("should dispay new cli error when parsing context create flags", func() {
 	It("should dispay new cli error when parsing context create flags", func() {
-		_, err := s.NewDockerCommand("context", "create", "--aci-subscription-id", "titi").Exec()
+		_, err := s.NewDockerCommand("context", "create", "aci", "--subscription-id", "titi").Exec()
 		Expect(err.Error()).NotTo(ContainSubstring("unknown flag"))
 		Expect(err.Error()).NotTo(ContainSubstring("unknown flag"))
-		Expect(err.Error()).To(ContainSubstring("accepts 2 arg(s), received 0"))
+		Expect(err.Error()).To(ContainSubstring("accepts 1 arg(s), received 0"))
 	})
 	})
 }
 }
 
 
@@ -135,7 +135,7 @@ func (s *E2eSuite) TestLeaveLegacyErrorMessagesUnchanged() {
 }
 }
 
 
 func (s *E2eSuite) TestDisplayFriendlyErrorMessageForLegacyCommands() {
 func (s *E2eSuite) TestDisplayFriendlyErrorMessageForLegacyCommands() {
-	s.NewDockerCommand("context", "create", "test-example", "example").ExecOrDie()
+	s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
 	output, err := s.NewDockerCommand("--context", "test-example", "images").Exec()
 	output, err := s.NewDockerCommand("--context", "test-example", "images").Exec()
 	Expect(output).To(Equal("Command \"images\" not available in current context (test-example), you can use the \"default\" context to run this command\n"))
 	Expect(output).To(Equal("Command \"images\" not available in current context (test-example), you can use the \"default\" context to run this command\n"))
 	Expect(err).NotTo(BeNil())
 	Expect(err).NotTo(BeNil())
@@ -149,7 +149,7 @@ func (s *E2eSuite) TestDisplaysAdditionalLineInDockerVersion() {
 
 
 func (s *E2eSuite) TestMockBackend() {
 func (s *E2eSuite) TestMockBackend() {
 	It("creates a new test context to hardcoded example backend", func() {
 	It("creates a new test context to hardcoded example backend", func() {
-		s.NewDockerCommand("context", "create", "test-example", "example").ExecOrDie()
+		s.NewDockerCommand("context", "create", "example", "test-example").ExecOrDie()
 		// Expect(output).To(ContainSubstring("test-example context acitest created"))
 		// Expect(output).To(ContainSubstring("test-example context acitest created"))
 	})
 	})
 
 

+ 3 - 0
tests/e2e/testdata/ls-out-test-docker-windows.golden

@@ -0,0 +1,3 @@
+NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
+default *           docker              Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
+test-docker         docker                                                        npipe:////./pipe/docker_engine                         swarm

+ 3 - 0
tests/e2e/testdata/ls-out-test-docker.golden

@@ -0,0 +1,3 @@
+NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
+default *           docker              Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
+test-docker         docker                                                        unix:///var/run/docker.sock                         swarm