diff --git a/ecs/Makefile b/ecs/Makefile index 5d3c5883d..126d5bc56 100644 --- a/ecs/Makefile +++ b/ecs/Makefile @@ -4,7 +4,7 @@ clean: build: go build -v -o dist/docker-ecs cmd/main/main.go -test: ## Run tests +test: build ## Run tests go test ./... -v dev: build diff --git a/ecs/cmd/main/main.go b/ecs/cmd/main/main.go index be8d21829..812ccfeea 100644 --- a/ecs/cmd/main/main.go +++ b/ecs/cmd/main/main.go @@ -43,6 +43,12 @@ func NewRootCmd(name string, dockerCli command.Cli) *cobra.Command { opts, err = docker.CheckAwsContextExists(contextName) return err }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return fmt.Errorf("%q is not a docker ecs command\nSee 'docker ecs --help'", args[0]) + } + return nil + }, } cmd.AddCommand( VersionCommand(), diff --git a/ecs/go.mod b/ecs/go.mod index 564890334..74a6a89f1 100644 --- a/ecs/go.mod +++ b/ecs/go.mod @@ -50,6 +50,7 @@ require ( gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect gopkg.in/fatih/pool.v2 v2.0.0 // indirect gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect + gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.0.2 vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect ) diff --git a/ecs/pkg/docker/contextStore.go b/ecs/pkg/docker/contextStore.go index 2d40b9dd0..31e4bc4e8 100644 --- a/ecs/pkg/docker/contextStore.go +++ b/ecs/pkg/docker/contextStore.go @@ -3,6 +3,7 @@ package docker import ( "fmt" + "github.com/docker/cli/cli/command" cliconfig "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/context/store" "github.com/mitchellh/mapstructure" @@ -25,7 +26,12 @@ type AwsContext struct { } func NewContext(name string, awsContext *AwsContext) error { - contextStore := initContextStore() + _, err := NewContextWithStore(name, awsContext, cliconfig.ContextStoreDir()) + return err +} + +func NewContextWithStore(name string, awsContext *AwsContext, contextDirectory string) (store.Store, error) { + contextStore := initContextStore(contextDirectory) endpoints := map[string]interface{}{ "aws": awsContext, "docker": awsContext, @@ -36,16 +42,19 @@ func NewContext(name string, awsContext *AwsContext) error { Endpoints: endpoints, Metadata: TypeContext{Type: contextType}, } - return contextStore.CreateOrUpdate(metadata) + return contextStore, contextStore.CreateOrUpdate(metadata) } -func initContextStore() store.Store { +func initContextStore(contextDir string) store.Store { config := store.NewConfig(getter) - return store.New(cliconfig.ContextStoreDir(), config) + return store.New(contextDir, config) } func CheckAwsContextExists(contextName string) (*AwsContext, error) { - contextStore := initContextStore() + if contextName == command.DefaultContextName { + return nil, fmt.Errorf("can't use \"%s\" with ECS command because it is not an AWS context", contextName) + } + contextStore := initContextStore(cliconfig.ContextStoreDir()) metadata, err := contextStore.GetMetadata(contextName) if err != nil { return nil, err diff --git a/ecs/tests/command_test.go b/ecs/tests/command_test.go new file mode 100644 index 000000000..a027fbaef --- /dev/null +++ b/ecs/tests/command_test.go @@ -0,0 +1,18 @@ +package tests + +import ( + "testing" + + "gotest.tools/v3/icmd" +) + +func TestExitErrorCode(t *testing.T) { + cmd, cleanup := dockerCli.createTestCmd() + defer cleanup() + + cmd.Command = dockerCli.Command("ecs", "unknown_command") + icmd.RunCmd(cmd).Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "\"unknown_command\" is not a docker ecs command\nSee 'docker ecs --help'", + }) +} diff --git a/ecs/tests/main_test.go b/ecs/tests/main_test.go new file mode 100644 index 000000000..a093c6c9b --- /dev/null +++ b/ecs/tests/main_test.go @@ -0,0 +1,127 @@ +package tests + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "testing" + + dockerConfigFile "github.com/docker/cli/cli/config/configfile" + "github.com/docker/ecs-plugin/pkg/docker" + "gotest.tools/v3/icmd" +) + +var ( + e2ePath = flag.String("e2e-path", ".", "Set path to the e2e directory") + dockerCliPath = os.Getenv("DOCKERCLI_BINARY") + dockerCli dockerCliCommand + testContextName = "testAwsContextToBeRemoved" +) + +type dockerCliCommand struct { + path string + cliPluginDir string +} + +type ConfigFileOperator func(configFile *dockerConfigFile.ConfigFile) + +func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, func()) { + configDir, err := ioutil.TempDir("", "config") + if err != nil { + panic(err) + } + configFilePath := filepath.Join(configDir, "config.json") + config := dockerConfigFile.ConfigFile{ + CLIPluginsExtraDirs: []string{ + d.cliPluginDir, + }, + Filename: configFilePath, + } + for _, op := range ops { + op(&config) + } + configFile, err := os.Create(configFilePath) + if err != nil { + panic(err) + } + defer configFile.Close() + err = json.NewEncoder(configFile).Encode(config) + if err != nil { + panic(err) + } + + awsContext := docker.AwsContext{ + Profile: "TestProfile", + Cluster: "TestCluster", + Region: "TestRegion", + } + testStore, err := docker.NewContextWithStore(testContextName, &awsContext, filepath.Join(configDir, "contexts")) + if err != nil { + panic(err) + } + cleanup := func() { + fmt.Println("cleanup") + testStore.Remove(testContextName) + os.RemoveAll(configDir) + } + env := append(os.Environ(), + "DOCKER_CONFIG="+configDir, + "DOCKER_CLI_EXPERIMENTAL=enabled") // TODO: Remove this once docker ecs plugin is no more experimental + return icmd.Cmd{Env: env}, cleanup +} + +func (d dockerCliCommand) Command(args ...string) []string { + return append([]string{d.path, "--context", testContextName}, args...) +} + +func TestMain(m *testing.M) { + flag.Parse() + if err := os.Chdir(*e2ePath); err != nil { + panic(err) + } + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + dockerEcs := os.Getenv("DOCKERECS_BINARY") + if dockerEcs == "" { + dockerEcs = filepath.Join(cwd, "../dist/docker-ecs") + } + dockerEcs, err = filepath.Abs(dockerEcs) + if err != nil { + panic(err) + } + if dockerCliPath == "" { + dockerCliPath = "docker" + } else { + dockerCliPath, err = filepath.Abs(dockerCliPath) + if err != nil { + panic(err) + } + } + // Prepare docker cli to call the docker-ecs plugin binary: + // - Create a symbolic link with the dockerEcs binary to the plugin directory + cliPluginDir, err := ioutil.TempDir("", "configContent") + if err != nil { + panic(err) + } + defer os.RemoveAll(cliPluginDir) + createDockerECSSymLink(dockerEcs, cliPluginDir) + + dockerCli = dockerCliCommand{path: dockerCliPath, cliPluginDir: cliPluginDir} + os.Exit(m.Run()) +} + +func createDockerECSSymLink(dockerEcs, configDir string) { + dockerEcsExecName := "docker-ecs" + if runtime.GOOS == "windows" { + dockerEcsExecName += ".exe" + } + if err := os.Symlink(dockerEcs, filepath.Join(configDir, dockerEcsExecName)); err != nil { + panic(err) + } +} diff --git a/ecs/tests/plugin_test.go b/ecs/tests/plugin_test.go new file mode 100644 index 000000000..f519c252a --- /dev/null +++ b/ecs/tests/plugin_test.go @@ -0,0 +1,33 @@ +package tests + +import ( + "regexp" + "testing" + + "gotest.tools/assert" + "gotest.tools/v3/golden" + "gotest.tools/v3/icmd" +) + +func TestInvokePluginFromCLI(t *testing.T) { + cmd, cleanup := dockerCli.createTestCmd() + defer cleanup() + // docker --help should list app as a top command + cmd.Command = dockerCli.Command("--help") + icmd.RunCmd(cmd).Assert(t, icmd.Expected{ + Out: "ecs* Docker ECS (Docker Inc.,", + }) + + // docker app --help prints docker-app help + cmd.Command = dockerCli.Command("ecs", "--help") + usage := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() + + goldenFile := "plugin-usage.golden" + golden.Assert(t, usage, goldenFile) + + // docker info should print app version and short description + cmd.Command = dockerCli.Command("info") + re := regexp.MustCompile(`ecs: Docker ECS \(Docker Inc\., .*\)`) + output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() + assert.Assert(t, re.MatchString(output)) +} diff --git a/ecs/tests/setup_command_test.go b/ecs/tests/setup_command_test.go new file mode 100644 index 000000000..308d3e7ea --- /dev/null +++ b/ecs/tests/setup_command_test.go @@ -0,0 +1,34 @@ +package tests + +import ( + "strings" + "testing" + + "gotest.tools/assert" + "gotest.tools/v3/golden" + "gotest.tools/v3/icmd" +) + +func TestSetupMandatoryArguments(t *testing.T) { + cmd, cleanup := dockerCli.createTestCmd() + defer cleanup() + + cmd.Command = dockerCli.Command("ecs", "setup") + icmd.RunCmd(cmd).Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "required flag(s) \"cluster\", \"profile\", \"region\" not set", + }) +} +func TestDefaultAwsContextName(t *testing.T) { + cmd, cleanup := dockerCli.createTestCmd() + defer cleanup() + + cmd.Command = dockerCli.Command("ecs", "setup", "--cluster", "clusterName", "--profile", "profileName", + "--region", "regionName") + icmd.RunCmd(cmd).Assert(t, icmd.Success) + + cmd.Command = dockerCli.Command("context", "inspect", "aws") + output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() + expected := golden.Get(t, "context-inspect.golden") + assert.Assert(t, strings.HasPrefix(output, string(expected))) +} diff --git a/ecs/tests/testdata/context-inspect.golden b/ecs/tests/testdata/context-inspect.golden new file mode 100644 index 000000000..ff61b55fc --- /dev/null +++ b/ecs/tests/testdata/context-inspect.golden @@ -0,0 +1,16 @@ +[ + { + "Name": "aws", + "Metadata": {}, + "Endpoints": { + "aws": { + "Cluster": "clusterName", + "Profile": "profileName", + "Region": "regionName" + }, + "docker": { + "SkipTLSVerify": false + } + }, + "TLSMaterial": {}, + "Storage": \ No newline at end of file diff --git a/ecs/tests/testdata/plugin-usage.golden b/ecs/tests/testdata/plugin-usage.golden new file mode 100644 index 000000000..8114b6f43 --- /dev/null +++ b/ecs/tests/testdata/plugin-usage.golden @@ -0,0 +1,14 @@ + +Usage: docker ecs COMMAND + +run multi-container applications on Amazon ECS. + +Management Commands: + compose + secret Manages secrets + +Commands: + setup + version Show version. + +Run 'docker ecs COMMAND --help' for more information on a command.