diff --git a/Gopkg.lock b/Gopkg.lock index 6096930c2..63d22ee71 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -98,12 +98,13 @@ version = "v1.1.0" [[projects]] - digest = "1:cc0f23b72ad758064c8ea707e75270e664ea5ba1c27dc90035a2adffe0e7d911" + digest = "1:0e3a3b4e78ea3b2a33b89f9ad0e3d88dae3c7a1ba6e0cefa58877095a279b912" name = "github.com/docker/cli" packages = [ "cli", "cli-plugins", "cli-plugins/manager", + "cli-plugins/plugin", "cli/command", "cli/command/bundlefile", "cli/command/formatter", @@ -1058,6 +1059,8 @@ input-imports = [ "github.com/cbroglie/mustache", "github.com/docker/cli/cli", + "github.com/docker/cli/cli-plugins", + "github.com/docker/cli/cli-plugins/plugin", "github.com/docker/cli/cli/command", "github.com/docker/cli/cli/command/stack", "github.com/docker/cli/cli/command/stack/kubernetes", @@ -1084,7 +1087,6 @@ "github.com/docker/docker/api/types/mount", "github.com/docker/docker/distribution", "github.com/docker/docker/pkg/archive", - "github.com/docker/docker/pkg/term", "github.com/docker/docker/registry", "github.com/docker/go-connections/nat", "github.com/docker/go-units", diff --git a/cmd/docker-app/helm.go b/cmd/docker-app/helm.go index b7d8d070c..9f2a1bea9 100644 --- a/cmd/docker-app/helm.go +++ b/cmd/docker-app/helm.go @@ -37,7 +37,7 @@ func helmCmd() *cobra.Command { defer app.Cleanup() d := cliopts.ConvertKVStringsToMap(helmEnv) if stackVersion != helm.V1Beta1 && stackVersion != helm.V1Beta2 { - return fmt.Errorf("invalid stack version %q (accepted values: %s, %s)", stackVersion, helm.V1Beta1, helm.V1Beta2) + return fmt.Errorf("Error: invalid stack version %q (accepted values: %s, %s)", stackVersion, helm.V1Beta1, helm.V1Beta2) } return helm.Helm(app, d, helmRender, stackVersion) }, diff --git a/cmd/docker-app/main.go b/cmd/docker-app/main.go index 928520cb2..0824fcc7e 100644 --- a/cmd/docker-app/main.go +++ b/cmd/docker-app/main.go @@ -1,21 +1,18 @@ package main import ( - "os" - + "github.com/docker/app/internal" + cliplugins "github.com/docker/cli/cli-plugins" + "github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli/command" - "github.com/docker/docker/pkg/term" - "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func main() { - // Set terminal emulation based on platform as required. - stdin, stdout, stderr := term.StdStreams() - logrus.SetOutput(stderr) - - dockerCli := command.NewDockerCli(stdin, stdout, stderr, nil) - cmd := newRootCmd(dockerCli) - if err := cmd.Execute(); err != nil { - os.Exit(1) - } + plugin.Run(func(dockerCli command.Cli) *cobra.Command { + return newRootCmd(dockerCli) + }, cliplugins.Metadata{ + Vendor: "Docker Inc.", + Version: internal.Version, + }) } diff --git a/cmd/docker-app/root.go b/cmd/docker-app/root.go index 8361b746b..306e27bd8 100644 --- a/cmd/docker-app/root.go +++ b/cmd/docker-app/root.go @@ -1,41 +1,22 @@ package main import ( - "fmt" - "github.com/docker/app/internal" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" cliconfig "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/debug" cliflags "github.com/docker/cli/cli/flags" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) // rootCmd represents the base command when called without any subcommands // FIXME(vdemeester) use command.Cli interface -func newRootCmd(dockerCli *command.DockerCli) *cobra.Command { - opts := cliflags.NewClientOptions() - var flags *pflag.FlagSet - +func newRootCmd(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ - Use: "docker-app", - Short: "Docker Application Packages", - Long: `Build and deploy Docker Application Packages.`, - SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - opts.Common.SetDefaultOptions(flags) - dockerPreRun(opts) - return dockerCli.Initialize(opts) - }, - Version: fmt.Sprintf("%s, build %s", internal.Version, internal.GitCommit), + Use: "app", + Short: "Docker Application Packages", + Long: `Build and deploy Docker Application Packages.`, } - cli.SetupRootCommand(cmd) - flags = cmd.Flags() - flags.BoolP("version", "v", false, "Print version information") - opts.Common.InstallFlags(flags) - cmd.SetVersionTemplate("docker-app version {{.Version}}\n") addCommands(cmd, dockerCli) return cmd } diff --git a/e2e/commands_test.go b/e2e/commands_test.go index dcf1edc1e..f655250eb 100644 --- a/e2e/commands_test.go +++ b/e2e/commands_test.go @@ -63,7 +63,7 @@ func testRenderApp(appPath string, env ...string) func(*testing.T) { data, err := ioutil.ReadFile(filepath.Join(appPath, "env.yml")) assert.NilError(t, err) assert.NilError(t, yaml.Unmarshal(data, &envSettings)) - args := []string{dockerApp, "render", filepath.Join(appPath, "my.dockerapp"), + args := []string{dockerApp, "app", "render", filepath.Join(appPath, "my.dockerapp"), "-f", filepath.Join(appPath, "settings-0.yml"), } for k, v := range envSettings { @@ -79,10 +79,10 @@ func testRenderApp(appPath string, env ...string) func(*testing.T) { func TestRenderFormatters(t *testing.T) { appPath := filepath.Join("testdata", "fork", "simple.dockerapp") - result := icmd.RunCommand(dockerApp, "render", "--formatter", "json", appPath).Assert(t, icmd.Success) + result := icmd.RunCommand(dockerApp, "app", "render", "--formatter", "json", appPath).Assert(t, icmd.Success) assert.Assert(t, golden.String(result.Stdout(), "expected-json-render.golden")) - result = icmd.RunCommand(dockerApp, "render", "--formatter", "yaml", appPath).Assert(t, icmd.Success) + result = icmd.RunCommand(dockerApp, "app", "render", "--formatter", "yaml", appPath).Assert(t, icmd.Success) assert.Assert(t, golden.String(result.Stdout(), "expected-yaml-render.golden")) } @@ -119,7 +119,7 @@ maintainers: dirName := internal.DirNameFromAppName(testAppName) defer os.RemoveAll(dirName) - icmd.RunCommand(dockerApp, "init", testAppName, + icmd.RunCommand(dockerApp, "app", "init", testAppName, "-c", dir.Join(internal.ComposeFileName), "-d", "my cool app", "-m", "bob", @@ -135,10 +135,10 @@ maintainers: assert.Assert(t, fs.Equal(dirName, manifest)) // validate metadata with JSON Schema - icmd.RunCommand(dockerApp, "validate", testAppName).Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "validate", testAppName).Assert(t, icmd.Success) // test single-file init - icmd.RunCommand(dockerApp, "init", "tac", + icmd.RunCommand(dockerApp, "app", "init", "tac", "-c", dir.Join(internal.ComposeFileName), "-d", "my cool app", "-m", "bob", @@ -150,8 +150,8 @@ maintainers: assert.NilError(t, err) assert.Assert(t, golden.Bytes(appData, "init-singlefile.dockerapp")) // Check various commands work on single-file app package - icmd.RunCommand(dockerApp, "inspect", "tac").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "render", "tac").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", "tac").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "render", "tac").Assert(t, icmd.Success) } func TestDetectApp(t *testing.T) { @@ -165,19 +165,19 @@ func TestDetectApp(t *testing.T) { ) defer dir.Remove() icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "inspect"}, + Command: []string{dockerApp, "app", "inspect"}, Dir: dir.Path(), }).Assert(t, icmd.Success) icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "inspect"}, + Command: []string{dockerApp, "app", "inspect"}, Dir: dir.Join("helm.dockerapp"), }).Assert(t, icmd.Success) icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "inspect", "."}, + Command: []string{dockerApp, "app", "inspect", "."}, Dir: dir.Join("helm.dockerapp"), }).Assert(t, icmd.Success) result := icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "inspect"}, + Command: []string{dockerApp, "app", "inspect"}, Dir: dir.Join("render"), }) result.Assert(t, icmd.Expected{ @@ -191,23 +191,23 @@ func TestPack(t *testing.T) { tempDir, err := ioutil.TempDir("", "dockerapp") assert.NilError(t, err) defer os.RemoveAll(tempDir) - icmd.RunCommand(dockerApp, "pack", "testdata/helm", "-o", filepath.Join(tempDir, "test.dockerapp")).Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "pack", "testdata/helm", "-o", filepath.Join(tempDir, "test.dockerapp")).Assert(t, icmd.Success) // check that our commands run on the packed version - icmd.RunCommand(dockerApp, "inspect", filepath.Join(tempDir, "test")).Assert(t, icmd.Expected{ + icmd.RunCommand(dockerApp, "app", "inspect", filepath.Join(tempDir, "test")).Assert(t, icmd.Expected{ Out: "myapp", }) - icmd.RunCommand(dockerApp, "render", filepath.Join(tempDir, "test")).Assert(t, icmd.Expected{ + icmd.RunCommand(dockerApp, "app", "render", filepath.Join(tempDir, "test")).Assert(t, icmd.Expected{ Out: "nginx", }) icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "helm", "test"}, + Command: []string{dockerApp, "app", "helm", "test"}, Dir: tempDir, }).Assert(t, icmd.Success) _, err = os.Stat(filepath.Join(tempDir, "test.chart", "Chart.yaml")) assert.NilError(t, err) assert.NilError(t, os.Mkdir(filepath.Join(tempDir, "output"), 0755)) icmd.RunCmd(icmd.Cmd{ - Command: []string{dockerApp, "unpack", "test", "-o", "output"}, + Command: []string{dockerApp, "app", "unpack", "test", "-o", "output"}, Dir: tempDir, }).Assert(t, icmd.Success) _, err = os.Stat(filepath.Join(tempDir, "output", "test.dockerapp", "docker-compose.yml")) @@ -224,7 +224,7 @@ func testHelm(version string) func(*testing.T) { return func(t *testing.T) { dir := fs.NewDir(t, "testHelmBinary", fs.FromDir("testdata")) defer dir.Remove() - cmd := []string{dockerApp, "helm", "helm", "-s", "myapp.nginx_version=2"} + cmd := []string{dockerApp, "app", "helm", "helm", "-s", "myapp.nginx_version=2"} if version != "" { cmd = append(cmd, "--stack-version", version) } @@ -243,31 +243,31 @@ func testHelm(version string) func(*testing.T) { } func TestHelmInvalidStackVersion(t *testing.T) { - icmd.RunCommand(dockerApp, "helm", "testdata/helm", "--stack-version", "foobar").Assert(t, icmd.Expected{ + icmd.RunCommand(dockerApp, "app", "helm", "testdata/helm", "--stack-version", "foobar").Assert(t, icmd.Expected{ ExitCode: 1, Err: `Error: invalid stack version "foobar" (accepted values: v1beta1, v1beta2)`, }) } func TestSplitMerge(t *testing.T) { - icmd.RunCommand(dockerApp, "merge", "testdata/render/envvariables/my.dockerapp", "-o", "remerged.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "merge", "testdata/render/envvariables/my.dockerapp", "-o", "remerged.dockerapp").Assert(t, icmd.Success) defer os.Remove("remerged.dockerapp") // test that inspect works on single-file - result := icmd.RunCommand(dockerApp, "inspect", "remerged").Assert(t, icmd.Success) + result := icmd.RunCommand(dockerApp, "app", "inspect", "remerged").Assert(t, icmd.Success) assert.Assert(t, golden.String(result.Combined(), "envvariables-inspect.golden")) // split it - icmd.RunCommand(dockerApp, "split", "remerged", "-o", "split.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "split", "remerged", "-o", "split.dockerapp").Assert(t, icmd.Success) defer os.RemoveAll("split.dockerapp") - result = icmd.RunCommand(dockerApp, "inspect", "remerged").Assert(t, icmd.Success) + result = icmd.RunCommand(dockerApp, "app", "inspect", "remerged").Assert(t, icmd.Success) assert.Assert(t, golden.String(result.Combined(), "envvariables-inspect.golden")) // test inplace - icmd.RunCommand(dockerApp, "merge", "split").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "split", "split").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "merge", "split").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "split", "split").Assert(t, icmd.Success) } func TestURL(t *testing.T) { url := "https://raw.githubusercontent.com/docker/app/v0.4.1/examples/hello-world/hello-world.dockerapp" - result := icmd.RunCommand(dockerApp, "inspect", url).Assert(t, icmd.Success) + result := icmd.RunCommand(dockerApp, "app", "inspect", url).Assert(t, icmd.Success) assert.Assert(t, golden.String(result.Combined(), "helloworld-inspect.golden")) } @@ -282,36 +282,36 @@ func TestWithRegistry(t *testing.T) { func testImage(registry string) func(*testing.T) { return func(t *testing.T) { // push to a registry - icmd.RunCommand(dockerApp, "push", "--namespace", registry+"/myuser", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "push", "--namespace", registry+"/myuser", "-t", "latest", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "inspect", registry+"/myuser/my.dockerapp:0.1.0").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "inspect", registry+"/myuser/my.dockerapp").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "inspect", registry+"/myuser/my").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "inspect", registry+"/myuser/my:0.1.0").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "--namespace", registry+"/myuser", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "--namespace", registry+"/myuser", "-t", "latest", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", registry+"/myuser/my.dockerapp:0.1.0").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", registry+"/myuser/my.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", registry+"/myuser/my").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", registry+"/myuser/my:0.1.0").Assert(t, icmd.Success) // push a single-file app to a registry dir := fs.NewDir(t, "save-prepare-build", fs.WithFile("my.dockerapp", singleFileApp)) defer dir.Remove() - icmd.RunCommand(dockerApp, "push", "--namespace", registry+"/myuser", dir.Join("my.dockerapp")).Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "--namespace", registry+"/myuser", dir.Join("my.dockerapp")).Assert(t, icmd.Success) // push with custom repo name - icmd.RunCommand(dockerApp, "push", "-t", "marshmallows", "--namespace", registry+"/rainbows", "--repo", "unicorns", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) - icmd.RunCommand(dockerApp, "inspect", registry+"/rainbows/unicorns:marshmallows").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "-t", "marshmallows", "--namespace", registry+"/rainbows", "--repo", "unicorns", "testdata/render/envvariables/my.dockerapp").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "inspect", registry+"/rainbows/unicorns:marshmallows").Assert(t, icmd.Success) } } func testFork(registry string) func(*testing.T) { return func(t *testing.T) { - icmd.RunCommand(dockerApp, "push", "--namespace", registry+"/acmecorp", "testdata/fork/simple").Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "--namespace", registry+"/acmecorp", "testdata/fork/simple").Assert(t, icmd.Success) tempDir := fs.NewDir(t, "dockerapptest") defer tempDir.Remove() - icmd.RunCommand(dockerApp, "fork", registry+"/acmecorp/simple.dockerapp:1.1.0-beta1", "acmecorp/scarlet.devil", + icmd.RunCommand(dockerApp, "app", "fork", registry+"/acmecorp/simple.dockerapp:1.1.0-beta1", "acmecorp/scarlet.devil", "-p", tempDir.Path(), "-m", "Remilia Scarlet:remilia@acmecorp.cool").Assert(t, icmd.Success) metadata := golden.Get(t, tempDir.Join("scarlet.devil.dockerapp", "metadata.yml")) assert.Assert(t, golden.Bytes(metadata, "expected-fork-metadata.golden")) - icmd.RunCommand(dockerApp, "fork", registry+"/acmecorp/simple.dockerapp:1.1.0-beta1", + icmd.RunCommand(dockerApp, "app", "fork", registry+"/acmecorp/simple.dockerapp:1.1.0-beta1", "-p", tempDir.Path(), "-m", "Remilia Scarlet:remilia@acmecorp.cool").Assert(t, icmd.Success) metadata2 := golden.Get(t, tempDir.Join("simple.dockerapp", "metadata.yml")) assert.Assert(t, golden.Bytes(metadata2, "expected-fork-metadata-no-rename.golden")) @@ -328,10 +328,10 @@ func TestAttachmentsWithRegistry(t *testing.T) { ) defer dir.Remove() - icmd.RunCommand(dockerApp, "push", "--namespace", registry+"/acmecorp", dir.Join("attachments.dockerapp")).Assert(t, icmd.Success) + icmd.RunCommand(dockerApp, "app", "push", "--namespace", registry+"/acmecorp", dir.Join("attachments.dockerapp")).Assert(t, icmd.Success) // inspect will run the core pull code too - result := icmd.RunCommand(dockerApp, "inspect", registry+"/acmecorp/attachments.dockerapp:0.1.0") + result := icmd.RunCommand(dockerApp, "app", "inspect", registry+"/acmecorp/attachments.dockerapp:0.1.0") result.Assert(t, icmd.Success) resultOutput := result.Combined() @@ -344,7 +344,7 @@ func TestAttachmentsWithRegistry(t *testing.T) { tempDir := fs.NewDir(t, "dockerapptest") defer tempDir.Remove() - icmd.RunCommand(dockerApp, "fork", registry+"/acmecorp/attachments.dockerapp:0.1.0", + icmd.RunCommand(dockerApp, "app", "fork", registry+"/acmecorp/attachments.dockerapp:0.1.0", "-p", tempDir.Path()).Assert(t, icmd.Success) externalFile := golden.Get(t, tempDir.Join("attachments.dockerapp", "config.cfg")) assert.Assert(t, golden.Bytes(externalFile, filepath.Join("attachments.dockerapp", "config.cfg"))) diff --git a/e2e/example_test.go b/e2e/example_test.go index a99d3d86b..2f573dded 100644 --- a/e2e/example_test.go +++ b/e2e/example_test.go @@ -20,7 +20,7 @@ func TestExamplesAreValid(t *testing.T) { case !info.IsDir(): return nil default: - result := icmd.RunCommand(dockerApp, "validate", filepath.Join(p, filepath.Base(p)+".dockerapp")) + result := icmd.RunCommand(dockerApp, "app", "validate", filepath.Join(p, filepath.Base(p)+".dockerapp")) result.Assert(t, icmd.Success) return filepath.SkipDir } diff --git a/e2e/main_test.go b/e2e/main_test.go index 1fcbe2b49..a816fc5c0 100644 --- a/e2e/main_test.go +++ b/e2e/main_test.go @@ -33,7 +33,7 @@ func TestMain(m *testing.M) { if err != nil { panic(err) } - cmd := exec.Command(dockerApp, "version") + cmd := exec.Command(dockerApp, "app", "version") output, err := cmd.CombinedOutput() if err != nil { panic(err) diff --git a/internal/helm/helm.go b/internal/helm/helm.go index 33b106376..3162c2956 100644 --- a/internal/helm/helm.go +++ b/internal/helm/helm.go @@ -178,7 +178,7 @@ func makeStack(appname string, targetDir string, data []byte, stackVersion strin return errors.Wrap(err, "failed to marshal final stack") } default: - return fmt.Errorf("invalid stack version %q", stackVersion) + return fmt.Errorf("Error: invalid stack version %q", stackVersion) } stackData = unquote(stackData) return ioutil.WriteFile(filepath.Join(targetDir, "templates", "stack.yaml"), stackData, 0644) @@ -215,7 +215,7 @@ func helmRender(app *types.App, targetDir string, env map[string]string, stackVe }, } default: - return fmt.Errorf("invalid stack version %q", stackVersion) + return fmt.Errorf("Error: invalid stack version %q", stackVersion) } stackData, err := yaml.Marshal(stack) if err != nil { diff --git a/internal/packager/extract.go b/internal/packager/extract.go index ab4d686c7..a56649ae4 100644 --- a/internal/packager/extract.go +++ b/internal/packager/extract.go @@ -32,7 +32,7 @@ func findApp() (string, error) { for _, c := range content { if strings.HasSuffix(c.Name(), internal.AppExtension) { if hit != "" { - return "", fmt.Errorf("multiple applications found in current directory, specify the application name on the command line") + return "", fmt.Errorf("Error: multiple applications found in current directory, specify the application name on the command line") } hit = c.Name() } diff --git a/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go new file mode 100644 index 000000000..9fcaeaecc --- /dev/null +++ b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go @@ -0,0 +1,96 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/docker/cli/cli" + cliplugins "github.com/docker/cli/cli-plugins" + "github.com/docker/cli/cli/command" + cliconfig "github.com/docker/cli/cli/config" + cliflags "github.com/docker/cli/cli/flags" + "github.com/docker/docker/pkg/term" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function. +func Run(makeCmd func(command.Cli) *cobra.Command, meta cliplugins.Metadata) { + // Set terminal emulation based on platform as required. + stdin, stdout, stderr := term.StdStreams() + logrus.SetOutput(stderr) + + dockerCli := command.NewDockerCli(stdin, stdout, stderr, nil) + + plugin := makeCmd(dockerCli) + + cmd := newPluginCommand(dockerCli, plugin, meta) + + if err := cmd.Execute(); err != nil { + if sterr, ok := err.(cli.StatusError); ok { + if sterr.Status != "" { + fmt.Fprintln(stderr, sterr.Status) + } + // StatusError should only be used for errors, and all errors should + // have a non-zero exit status, so never exit with 0 + if sterr.StatusCode == 0 { + os.Exit(1) + } + os.Exit(sterr.StatusCode) + } + fmt.Fprintln(stderr, err) + os.Exit(1) + } +} + +func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta cliplugins.Metadata) *cobra.Command { + opts := cliflags.NewClientOptions() + var flags *pflag.FlagSet + + name := plugin.Use + fullname := cliplugins.NamePrefix + name + + cmd := &cobra.Command{ + Use: "docker" + " [OPTIONS] " + name + " [ARG...]", + Short: fullname + " is a Docker CLI plugin", + SilenceUsage: true, + SilenceErrors: true, + TraverseChildren: false, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // flags must be the top-level command flags, not cmd.Flags() + opts.Common.SetDefaultOptions(flags) + return dockerCli.Initialize(opts) + }, + } + flags = cmd.Flags() + flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") + opts.Common.InstallFlags(flags) + + cmd.SetOutput(dockerCli.Out()) + + cmd.AddCommand( + plugin, + newMetadataSubcommand(plugin, meta), + ) + + return cmd +} + +func newMetadataSubcommand(plugin *cobra.Command, meta cliplugins.Metadata) *cobra.Command { + if meta.ShortDescription == "" { + meta.ShortDescription = plugin.Short + } + cmd := &cobra.Command{ + Use: cliplugins.MetadataSubcommandName, + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + enc := json.NewEncoder(os.Stdout) + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + return enc.Encode(meta) + }, + } + return cmd +}