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

This commit is contained in:
Guillaume Tardif 2020-06-11 10:12:41 +02:00
parent d0b2bfbf52
commit 113350a09d
9 changed files with 206 additions and 117 deletions

View File

@ -29,75 +29,118 @@ package context
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/api/client"
"github.com/docker/api/cli/dockerclassic"
"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 {
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{
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 {
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 runCreate(ctx context.Context, opts AciCreateOpts, name string, contextType string) error {
contextData, description, err := getContextData(ctx, contextType, opts)
if err != nil {
return nil
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
}
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)
return s.Create(
result := s.Create(
name,
contextType,
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")
}

View File

@ -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))
}

View File

@ -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,
}
}

View File

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

View File

@ -16,7 +16,7 @@ type MobyBackendTestSuite struct {
}
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()
}

View File

@ -38,16 +38,6 @@ type E2eACISuite struct {
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() {
It("should be initialized with default context", func() {
_, err := s.NewCommand("docker", "context", "rm", "-f", contextName).Exec()
@ -70,7 +60,7 @@ func (s *E2eACISuite) TestACIBackend() {
Expect(err).To(BeNil())
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"))
})

View File

@ -46,36 +46,36 @@ type E2eSuite struct {
}
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() {
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()).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() {
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()
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())
@ -149,7 +149,7 @@ func (s *E2eSuite) TestDisplaysAdditionalLineInDockerVersion() {
func (s *E2eSuite) TestMockBackend() {
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"))
})

View File

@ -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

View File

@ -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