From 8961805412ba307255f378680a1af903b19b384c Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Mon, 28 Sep 2020 17:08:27 +0200 Subject: [PATCH 01/15] Add json output format to several commands - docker context ls - docker ps - docker compose ls - docker compose ps - docker secret ls - docker volume ls - docker version Signed-off-by: Ulysses Souza --- cli/cmd/compose/compose.go | 1 + cli/cmd/compose/list.go | 37 +++- cli/cmd/compose/list_test.go | 45 +++++ cli/cmd/compose/ps.go | 39 ++-- .../testdata/compose-list-out-json.golden | 7 + .../compose/testdata/compose-list-out.golden | 2 + cli/cmd/context/ls.go | 66 +++--- cli/cmd/ps.go | 66 +++--- cli/cmd/secrets.go | 43 ++-- cli/cmd/secrets_test.go | 10 +- cli/cmd/testdata/secrets-out-json.golden | 8 + cli/cmd/version.go | 69 ++++++- cli/cmd/version_test.go | 190 ++++++++++++++++++ cli/cmd/volume/list.go | 43 ++-- cli/cmd/volume/list_test.go | 9 +- .../volume/testdata/volumes-out-json.golden | 6 + cli/mobycli/exec.go | 7 +- formatter/consts.go | 24 +++ formatter/pretty.go | 32 +++ go.mod | 1 + tests/e2e/e2e_test.go | 32 +++ tests/e2e/testdata/ls-out-json.golden | 16 ++ tests/e2e/testdata/ps-out-example-json.golden | 30 +++ 23 files changed, 665 insertions(+), 118 deletions(-) create mode 100644 cli/cmd/compose/list_test.go create mode 100644 cli/cmd/compose/testdata/compose-list-out-json.golden create mode 100644 cli/cmd/compose/testdata/compose-list-out.golden create mode 100644 cli/cmd/testdata/secrets-out-json.golden create mode 100644 cli/cmd/version_test.go create mode 100644 cli/cmd/volume/testdata/volumes-out-json.golden create mode 100644 formatter/consts.go create mode 100644 formatter/pretty.go create mode 100644 tests/e2e/testdata/ls-out-json.golden create mode 100644 tests/e2e/testdata/ps-out-example-json.golden diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index 8e821120..30248d25 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -33,6 +33,7 @@ type composeOptions struct { WorkingDir string ConfigPaths []string Environment []string + Format string } func (o *composeOptions) toProjectName() (string, error) { diff --git a/cli/cmd/compose/list.go b/cli/cmd/compose/list.go index d44e05bb..b1daa690 100644 --- a/cli/cmd/compose/list.go +++ b/cli/cmd/compose/list.go @@ -21,10 +21,16 @@ import ( "fmt" "io" "os" + "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/errdefs" + "github.com/docker/compose-cli/formatter" ) func listCommand() *cobra.Command { @@ -35,10 +41,15 @@ func listCommand() *cobra.Command { return runList(cmd.Context(), opts) }, } - lsCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name") + addComposeCommonFlags(lsCmd.Flags(), &opts) return lsCmd } +func addComposeCommonFlags(f *pflag.FlagSet, opts *composeOptions) { + f.StringVarP(&opts.Name, "project-name", "p", "", "Project name") + f.StringVar(&opts.Format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") +} + func runList(ctx context.Context, opts composeOptions) error { c, err := client.New(ctx) if err != nil { @@ -49,10 +60,26 @@ func runList(ctx context.Context, opts composeOptions) error { return err } - err = printSection(os.Stdout, func(w io.Writer) { - for _, stack := range stackList { - fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) + return printListFormatted(opts.Format, os.Stdout, stackList) +} + +func printListFormatted(format string, out io.Writer, stackList []compose.Stack) error { + var err error + switch strings.ToLower(format) { + case formatter.PRETTY, "": + err = formatter.PrintPrettySection(out, func(w io.Writer) { + for _, stack := range stackList { + fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) + } + }, "NAME", "STATUS") + case formatter.JSON: + outJSON, err := formatter.ToStandardJSON(stackList) + if err != nil { + return err } - }, "NAME", "STATUS") + _, _ = fmt.Fprint(out, outJSON) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) + } return err } diff --git a/cli/cmd/compose/list_test.go b/cli/cmd/compose/list_test.go new file mode 100644 index 00000000..c1ddf8be --- /dev/null +++ b/cli/cmd/compose/list_test.go @@ -0,0 +1,45 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "bytes" + "testing" + + "gotest.tools/assert" + "gotest.tools/golden" + + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/formatter" +) + +func TestPrintComposeList(t *testing.T) { + secretList := []compose.Stack{ + { + ID: "123", + Name: "myName123", + Status: "Running", + }, + } + out := &bytes.Buffer{} + assert.NilError(t, printListFormatted(formatter.PRETTY, out, secretList)) + golden.Assert(t, out.String(), "compose-list-out.golden") + + out.Reset() + assert.NilError(t, printListFormatted(formatter.JSON, out, secretList)) + golden.Assert(t, out.String(), "compose-list-out-json.golden") +} diff --git a/cli/cmd/compose/ps.go b/cli/cmd/compose/ps.go index 8ad40055..bdef232c 100644 --- a/cli/cmd/compose/ps.go +++ b/cli/cmd/compose/ps.go @@ -22,11 +22,14 @@ import ( "io" "os" "strings" - "text/tabwriter" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/errdefs" + "github.com/docker/compose-cli/formatter" ) func psCommand() *cobra.Command { @@ -37,10 +40,9 @@ func psCommand() *cobra.Command { return runPs(cmd.Context(), opts) }, } - psCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name") psCmd.Flags().StringVar(&opts.WorkingDir, "workdir", "", "Work dir") psCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files") - + addComposeCommonFlags(psCmd.Flags(), &opts) return psCmd } @@ -59,17 +61,26 @@ func runPs(ctx context.Context, opts composeOptions) error { return err } - err = printSection(os.Stdout, func(w io.Writer) { - for _, service := range serviceList { - fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", ")) - } - }, "ID", "NAME", "REPLICAS", "PORTS") - return err + return printPsFormatted(opts.Format, os.Stdout, serviceList) } -func printSection(out io.Writer, printer func(io.Writer), headers ...string) error { - w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(headers, "\t")) - printer(w) - return w.Flush() +func printPsFormatted(format string, out io.Writer, serviceList []compose.ServiceStatus) error { + var err error + switch strings.ToLower(format) { + case formatter.PRETTY, "": + err = formatter.PrintPrettySection(out, func(w io.Writer) { + for _, service := range serviceList { + fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", ")) + } + }, "ID", "NAME", "REPLICAS", "PORTS") + case formatter.JSON: + outJSON, err := formatter.ToStandardJSON(serviceList) + if err != nil { + return err + } + _, _ = fmt.Fprint(out, outJSON) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) + } + return err } diff --git a/cli/cmd/compose/testdata/compose-list-out-json.golden b/cli/cmd/compose/testdata/compose-list-out-json.golden new file mode 100644 index 00000000..219a65d9 --- /dev/null +++ b/cli/cmd/compose/testdata/compose-list-out-json.golden @@ -0,0 +1,7 @@ +[ + { + "ID": "123", + "Name": "myName123", + "Status": "Running" + } +] \ No newline at end of file diff --git a/cli/cmd/compose/testdata/compose-list-out.golden b/cli/cmd/compose/testdata/compose-list-out.golden new file mode 100644 index 00000000..fab2b912 --- /dev/null +++ b/cli/cmd/compose/testdata/compose-list-out.golden @@ -0,0 +1,2 @@ +NAME STATUS +myName123 Running diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 2cad5c7b..01db114f 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -17,18 +17,19 @@ package context import ( - "errors" "fmt" + "io" "os" "sort" "strings" - "text/tabwriter" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/cli/mobycli" apicontext "github.com/docker/compose-cli/context" "github.com/docker/compose-cli/context/store" + "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -58,7 +59,8 @@ func listCommand() *cobra.Command { } cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only show context names") cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") - cmd.Flags().StringVar(&opts.format, "format", "", "Format output as JSON") + cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") + _ = cmd.Flags().MarkHidden("json") return cmd } @@ -68,7 +70,7 @@ func runList(cmd *cobra.Command, opts lsOpts) error { if err != nil { return err } - if opts.format != "" { + if opts.format != "" && opts.format != formatter.JSON && opts.format != formatter.PRETTY { mobycli.Exec(cmd.Root()) return nil } @@ -93,35 +95,41 @@ func runList(cmd *cobra.Command, opts lsOpts) error { } if opts.json { - j, err := formatter.ToStandardJSON(contexts) + opts.format = formatter.JSON + } + + return printContextLsFormatted(opts.format, currentContext, os.Stdout, contexts) +} + +func printContextLsFormatted(format string, currContext string, out io.Writer, contexts []*store.DockerContext) error { + var err error + switch strings.ToLower(format) { + case formatter.PRETTY, "": + err = formatter.PrintPrettySection(out, func(w io.Writer) { + for _, c := range contexts { + contextName := c.Name + if c.Name == currContext { + contextName += " *" + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", + contextName, + c.Type(), + c.Metadata.Description, + getEndpoint("docker", c.Endpoints), + getEndpoint("kubernetes", c.Endpoints), + c.Metadata.StackOrchestrator) + } + }, "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") + case formatter.JSON: + out, err := formatter.ToStandardJSON(contexts) if err != nil { return err } - fmt.Println(j) - return nil + fmt.Println(out) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) } - - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, "NAME\tTYPE\tDESCRIPTION\tDOCKER ENDPOINT\tKUBERNETES ENDPOINT\tORCHESTRATOR") - format := "%s\t%s\t%s\t%s\t%s\t%s\n" - - for _, c := range contexts { - contextName := c.Name - if c.Name == currentContext { - contextName += " *" - } - - fmt.Fprintf(w, - format, - contextName, - c.Type(), - c.Metadata.Description, - getEndpoint("docker", c.Endpoints), - getEndpoint("kubernetes", c.Endpoints), - c.Metadata.StackOrchestrator) - } - - return w.Flush() + return err } func getEndpoint(name string, meta map[string]interface{}) string { diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index 815284de..9f9861fc 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -19,30 +19,25 @@ package cmd import ( "context" "fmt" + "io" "os" "strings" - "text/tabwriter" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/containers" + "github.com/docker/compose-cli/errdefs" formatter2 "github.com/docker/compose-cli/formatter" "github.com/docker/compose-cli/utils/formatter" ) type psOpts struct { - all bool - quiet bool - json bool -} - -func (o psOpts) validate() error { - if o.quiet && o.json { - return errors.New(`cannot combine "quiet" and "json" options`) - } - return nil + all bool + quiet bool + json bool + format string } // PsCommand lists containers @@ -59,50 +54,69 @@ func PsCommand() *cobra.Command { cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") cmd.Flags().BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") + cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") + _ = cmd.Flags().MarkHidden("json") return cmd } +func (o psOpts) validate() error { + if o.quiet && o.json { + return errors.New(`cannot combine "quiet" and "json" options`) + } + return nil +} + func runPs(ctx context.Context, opts psOpts) error { err := opts.validate() if err != nil { return err } - c, err := client.New(ctx) if err != nil { return errors.Wrap(err, "cannot connect to backend") } - containers, err := c.ContainerService().List(ctx, opts.all) + containerList, err := c.ContainerService().List(ctx, opts.all) if err != nil { - return errors.Wrap(err, "fetch containers") + return errors.Wrap(err, "fetch containerList") } if opts.quiet { - for _, c := range containers { + for _, c := range containerList { fmt.Println(c.ID) } return nil } if opts.json { - j, err := formatter2.ToStandardJSON(containers) + opts.format = formatter2.JSON + } + + return printPsFormatted(opts.format, os.Stdout, containerList) +} + +func printPsFormatted(format string, out io.Writer, containers []containers.Container) error { + var err error + switch strings.ToLower(format) { + case formatter2.PRETTY, "": + err = formatter2.PrintPrettySection(out, func(w io.Writer) { + for _, c := range containers { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status, + strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", ")) + } + }, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS") + case formatter2.JSON: + out, err := formatter2.ToStandardJSON(containers) if err != nil { return err } - fmt.Println(j) - return nil - } + fmt.Println(out) - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - fmt.Fprintf(w, "CONTAINER ID\tIMAGE\tCOMMAND\tSTATUS\tPORTS\n") - format := "%s\t%s\t%s\t%s\t%s\n" - for _, container := range containers { - fmt.Fprintf(w, format, container.ID, container.Image, container.Command, container.Status, strings.Join(formatter.PortsToStrings(container.Ports, fqdn(container)), ", ")) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) } - - return w.Flush() + return err } func fqdn(container containers.Container) string { diff --git a/cli/cmd/secrets.go b/cli/cmd/secrets.go index cce9141f..b7dc2aad 100644 --- a/cli/cmd/secrets.go +++ b/cli/cmd/secrets.go @@ -21,12 +21,14 @@ import ( "io" "os" "strings" - "text/tabwriter" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/secrets" + "github.com/docker/compose-cli/errdefs" + "github.com/docker/compose-cli/formatter" ) type createSecretOptions struct { @@ -105,7 +107,12 @@ func inspectSecret() *cobra.Command { return cmd } +type listSecretsOpts struct { + format string +} + func listSecrets() *cobra.Command { + var opts listSecretsOpts cmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, @@ -119,10 +126,10 @@ func listSecrets() *cobra.Command { if err != nil { return err } - printList(os.Stdout, list) - return nil + return printSecretList(opts.format, os.Stdout, list) }, } + cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") return cmd } @@ -149,17 +156,23 @@ func deleteSecret() *cobra.Command { return cmd } -func printList(out io.Writer, secrets []secrets.Secret) { - printSection(out, func(w io.Writer) { - for _, secret := range secrets { - fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) // nolint:errcheck +func printSecretList(format string, out io.Writer, secrets []secrets.Secret) error { + var err error + switch strings.ToLower(format) { + case formatter.PRETTY, "": + err = formatter.PrintPrettySection(out, func(w io.Writer) { + for _, secret := range secrets { + fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) // nolint:errcheck + } + }, "ID", "NAME", "DESCRIPTION") + case formatter.JSON: + outJSON, err := formatter.ToStandardJSON(secrets) + if err != nil { + return err } - }, "ID", "NAME", "DESCRIPTION") -} - -func printSection(out io.Writer, printer func(io.Writer), headers ...string) { - w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(headers, "\t")) // nolint:errcheck - printer(w) - w.Flush() // nolint:errcheck + _, _ = fmt.Fprint(out, outJSON) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) + } + return err } diff --git a/cli/cmd/secrets_test.go b/cli/cmd/secrets_test.go index 9624c0c5..2e6a2fd1 100644 --- a/cli/cmd/secrets_test.go +++ b/cli/cmd/secrets_test.go @@ -20,13 +20,15 @@ import ( "bytes" "testing" + "gotest.tools/assert" "gotest.tools/v3/golden" "github.com/docker/compose-cli/api/secrets" + "github.com/docker/compose-cli/formatter" ) func TestPrintList(t *testing.T) { - secrets := []secrets.Secret{ + secretList := []secrets.Secret{ { ID: "123", Name: "secret123", @@ -34,6 +36,10 @@ func TestPrintList(t *testing.T) { }, } out := &bytes.Buffer{} - printList(out, secrets) + assert.NilError(t, printSecretList(formatter.PRETTY, out, secretList)) golden.Assert(t, out.String(), "secrets-out.golden") + + out.Reset() + assert.NilError(t, printSecretList(formatter.JSON, out, secretList)) + golden.Assert(t, out.String(), "secrets-out-json.golden") } diff --git a/cli/cmd/testdata/secrets-out-json.golden b/cli/cmd/testdata/secrets-out-json.golden new file mode 100644 index 00000000..55a32b3e --- /dev/null +++ b/cli/cmd/testdata/secrets-out-json.golden @@ -0,0 +1,8 @@ +[ + { + "ID": "123", + "Name": "secret123", + "Labels": null, + "Description": "secret 1,2,3" + } +] \ No newline at end of file diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 70a7cd0a..c37171d7 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -18,43 +18,94 @@ package cmd import ( "fmt" + "os" "strings" "github.com/spf13/cobra" "github.com/docker/compose-cli/cli/cmd/mobyflags" "github.com/docker/compose-cli/cli/mobycli" + "github.com/docker/compose-cli/formatter" ) +const formatOpt = "format" + // VersionCommand command to display version func VersionCommand(version string) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Show the Docker version information", Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, _ []string) error { - return runVersion(cmd, version) + Run: func(cmd *cobra.Command, _ []string) { + runVersion(cmd, version) }, } // define flags for backward compatibility with com.docker.cli flags := cmd.Flags() - flags.StringP("format", "f", "", "Format the output using the given Go template") + flags.StringP(formatOpt, "f", "", "Format the output using the given Go template") + // flags.String(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") flags.String("kubeconfig", "", "Kubernetes config file") mobyflags.AddMobyFlagsForRetrocompatibility(flags) return cmd } -func runVersion(cmd *cobra.Command, version string) error { +func runVersion(cmd *cobra.Command, version string) { + var versionString string + format := strings.TrimSpace(cmd.Flag(formatOpt).Value.String()) displayedVersion := strings.TrimPrefix(version, "v") - versionResult, _ := mobycli.ExecSilent(cmd.Context()) + // Replace is preferred in this case to keep the order. + switch format { + case formatter.PRETTY, "": + versionString = strings.Replace(getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...), + "\n Version:", "\n Cloud integration: "+displayedVersion+"\n Version:", 1) + case formatter.JSON, "{{json .}}", "{{json . }}", "{{ json .}}", "{{ json . }}": // Try to catch full JSON formats + versionString = strings.Replace(getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...), + `"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, displayedVersion), 1) + } + fmt.Print(versionString) +} + +func getOutFromMoby(cmd *cobra.Command, args ...string) string { + versionResult, _ := mobycli.ExecSilent(cmd.Context(), args...) // we don't want to fail on error, there is an error if the engine is not available but it displays client version info // Still, technically the [] byte versionResult could be nil, just let the original command display what it has to display if versionResult == nil { mobycli.Exec(cmd.Root()) - return nil + return "" } - var s string = string(versionResult) - fmt.Print(strings.Replace(s, "\n Version:", "\n Cloud integration "+displayedVersion+"\n Version:", 1)) - return nil + return string(versionResult) +} + +func fixedPrettyArgs(oArgs []string) []string { + var args []string + for i := 0; i < len(oArgs); i++ { + if isFormatOpt(oArgs[i]) && + len(oArgs) > i && + (strings.ToLower(oArgs[i+1]) == formatter.PRETTY || oArgs[i+1] == "") { + i++ + continue + } + args = append(args, oArgs[i]) + } + return args +} + +func fixedJSONArgs(oArgs []string) []string { + var args []string + for i := 0; i < len(oArgs); i++ { + if isFormatOpt(oArgs[i]) && + len(oArgs) > i && + strings.ToLower(oArgs[i+1]) == formatter.JSON { + args = append(args, oArgs[i], "{{json .}}") + i++ + continue + } + args = append(args, oArgs[i]) + } + return args +} + +func isFormatOpt(o string) bool { + return o == "--format" || o == "-f" } diff --git a/cli/cmd/version_test.go b/cli/cmd/version_test.go new file mode 100644 index 00000000..ea61e024 --- /dev/null +++ b/cli/cmd/version_test.go @@ -0,0 +1,190 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cmd + +import ( + "testing" + + "gotest.tools/assert" +) + +type caze struct { + Actual []string + Expected []string +} + +func TestVersionFormat(t *testing.T) { + jsonCases := []caze{ + { + Actual: fixedJSONArgs([]string{}), + Expected: nil, + }, + { + Actual: fixedJSONArgs([]string{ + "docker", + "version", + }), + Expected: []string{ + "docker", + "version", + }, + }, + { + Actual: fixedJSONArgs([]string{ + "docker", + "version", + "--format", + "json", + }), + Expected: []string{ + "docker", + "version", + "--format", + "{{json .}}", + }, + }, + { + Actual: fixedJSONArgs([]string{ + "docker", + "version", + "--format", + "jSoN", + }), + Expected: []string{ + "docker", + "version", + "--format", + "{{json .}}", + }, + }, + { + Actual: fixedJSONArgs([]string{ + "docker", + "version", + "--format", + "json", + "--kubeconfig", + "myKubeConfig", + }), + Expected: []string{ + "docker", + "version", + "--format", + "{{json .}}", + "--kubeconfig", + "myKubeConfig", + }, + }, + { + Actual: fixedJSONArgs([]string{ + "--format", + "json", + }), + Expected: []string{ + "--format", + "{{json .}}", + }, + }, + } + prettyCases := []caze{ + { + Actual: fixedPrettyArgs([]string{}), + Expected: nil, + }, + { + Actual: fixedPrettyArgs([]string{ + "docker", + "version", + }), + Expected: []string{ + "docker", + "version", + }, + }, + { + Actual: fixedPrettyArgs([]string{ + "docker", + "version", + "--format", + "pretty", + }), + Expected: []string{ + "docker", + "version", + }, + }, + { + Actual: fixedPrettyArgs([]string{ + "docker", + "version", + "--format", + "pRettY", + }), + Expected: []string{ + "docker", + "version", + }, + }, + { + Actual: fixedPrettyArgs([]string{ + "docker", + "version", + "--format", + "", + }), + Expected: []string{ + "docker", + "version", + }, + }, + { + Actual: fixedPrettyArgs([]string{ + "docker", + "version", + "--format", + "pretty", + "--kubeconfig", + "myKubeConfig", + }), + Expected: []string{ + "docker", + "version", + "--kubeconfig", + "myKubeConfig", + }, + }, + { + Actual: fixedPrettyArgs([]string{ + "--format", + "pretty", + }), + Expected: nil, + }, + } + + t.Run("json", func(t *testing.T) { + for _, c := range jsonCases { + assert.DeepEqual(t, c.Actual, c.Expected) + } + }) + + t.Run("pretty", func(t *testing.T) { + for _, c := range prettyCases { + assert.DeepEqual(t, c.Actual, c.Expected) + } + }) +} diff --git a/cli/cmd/volume/list.go b/cli/cmd/volume/list.go index 9ab3313a..d20b618b 100644 --- a/cli/cmd/volume/list.go +++ b/cli/cmd/volume/list.go @@ -21,15 +21,22 @@ import ( "io" "os" "strings" - "text/tabwriter" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/volumes" + "github.com/docker/compose-cli/errdefs" + "github.com/docker/compose-cli/formatter" ) +type listVolumeOpts struct { + format string +} + func listVolume() *cobra.Command { + var opts listVolumeOpts cmd := &cobra.Command{ Use: "ls", Short: "list available volumes in context.", @@ -43,24 +50,30 @@ func listVolume() *cobra.Command { if err != nil { return err } - printList(os.Stdout, vols) - return nil + return printList(opts.format, os.Stdout, vols) }, } + cmd.Flags().StringVar(&opts.format, "format", formatter.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)") return cmd } -func printList(out io.Writer, volumes []volumes.Volume) { - printSection(out, func(w io.Writer) { - for _, vol := range volumes { - _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) +func printList(format string, out io.Writer, volumes []volumes.Volume) error { + var err error + switch strings.ToLower(format) { + case formatter.PRETTY, "": + _ = formatter.PrintPrettySection(out, func(w io.Writer) { + for _, vol := range volumes { + _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) + } + }, "ID", "DESCRIPTION") + case formatter.JSON: + outJSON, err := formatter.ToStandardJSON(volumes) + if err != nil { + return err } - }, "ID", "DESCRIPTION") -} - -func printSection(out io.Writer, printer func(io.Writer), headers ...string) { - w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) - _, _ = fmt.Fprintln(w, strings.Join(headers, "\t")) - printer(w) - _ = w.Flush() + _, _ = fmt.Fprint(out, outJSON) + default: + err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) + } + return err } diff --git a/cli/cmd/volume/list_test.go b/cli/cmd/volume/list_test.go index f88716e9..093a4339 100644 --- a/cli/cmd/volume/list_test.go +++ b/cli/cmd/volume/list_test.go @@ -20,9 +20,11 @@ import ( "bytes" "testing" + "gotest.tools/assert" "gotest.tools/v3/golden" "github.com/docker/compose-cli/api/volumes" + "github.com/docker/compose-cli/formatter" ) func TestPrintList(t *testing.T) { @@ -33,6 +35,11 @@ func TestPrintList(t *testing.T) { }, } out := &bytes.Buffer{} - printList(out, secrets) + assert.NilError(t, printList(formatter.PRETTY, out, secrets)) golden.Assert(t, out.String(), "volumes-out.golden") + + out.Reset() + assert.NilError(t, printList(formatter.JSON, out, secrets)) + golden.Assert(t, out.String(), "volumes-out-json.golden") + } diff --git a/cli/cmd/volume/testdata/volumes-out-json.golden b/cli/cmd/volume/testdata/volumes-out-json.golden new file mode 100644 index 00000000..480be035 --- /dev/null +++ b/cli/cmd/volume/testdata/volumes-out-json.golden @@ -0,0 +1,6 @@ +[ + { + "ID": "volume/123", + "Description": "volume 123" + } +] \ No newline at end of file diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index 665de45b..5b89cd1c 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -112,7 +112,10 @@ func IsDefaultContextCommand(dockerCommand string) bool { } // ExecSilent executes a command and do redirect output to stdOut, return output -func ExecSilent(ctx context.Context) ([]byte, error) { - cmd := exec.CommandContext(ctx, ComDockerCli, os.Args[1:]...) +func ExecSilent(ctx context.Context, args ...string) ([]byte, error) { + if len(args) == 0 { + args = os.Args[1:] + } + cmd := exec.CommandContext(ctx, ComDockerCli, args...) return cmd.CombinedOutput() } diff --git a/formatter/consts.go b/formatter/consts.go new file mode 100644 index 00000000..1ca9ebcd --- /dev/null +++ b/formatter/consts.go @@ -0,0 +1,24 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package formatter + +const ( + // JSON is the constant for Json formats on list commands + JSON = "json" + // PRETTY is the constant for default formats on list commands + PRETTY = "pretty" +) diff --git a/formatter/pretty.go b/formatter/pretty.go new file mode 100644 index 00000000..9f014fe9 --- /dev/null +++ b/formatter/pretty.go @@ -0,0 +1,32 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package formatter + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" +) + +// PrintPrettySection prints a tabbed section on the writer parameter +func PrintPrettySection(out io.Writer, printer func(writer io.Writer), headers ...string) error { + w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) + fmt.Fprintln(w, strings.Join(headers, "\t")) + printer(w) + return w.Flush() +} diff --git a/go.mod b/go.mod index f0cf60a4..aef8bdfa 100644 --- a/go.mod +++ b/go.mod @@ -62,5 +62,6 @@ require ( google.golang.org/protobuf v1.25.0 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.61.0 + gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.0.2 ) diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 4b33c8e8..8211a5f8 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -75,6 +75,12 @@ func TestContextDefault(t *testing.T) { t.Run("ls", func(t *testing.T) { res := c.RunDockerCmd("context", "ls") golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default")) + + res = c.RunDockerCmd("context", "ls", "--format", "pretty") + golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default")) + + res = c.RunDockerCmd("context", "ls", "--format", "json") + golden.Assert(t, res.Stdout(), GoldenFile("ls-out-json")) }) t.Run("inspect", func(t *testing.T) { @@ -407,6 +413,26 @@ func TestVersion(t *testing.T) { res.Assert(t, icmd.Expected{Out: `"Client":`}) }) + t.Run("format cloud integration", func(t *testing.T) { + res := c.RunDockerCmd("version", "-f", "pretty") + res.Assert(t, icmd.Expected{Out: `Cloud integration:`}) + res = c.RunDockerCmd("version", "-f", "") + res.Assert(t, icmd.Expected{Out: `Cloud integration:`}) + + res = c.RunDockerCmd("version", "-f", "json") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + res = c.RunDockerCmd("version", "-f", "{{ json . }}") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + res = c.RunDockerCmd("version", "--format", "{{json .}}") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + res = c.RunDockerCmd("version", "--format", "{{json . }}") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + res = c.RunDockerCmd("version", "--format", "{{ json .}}") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + res = c.RunDockerCmd("version", "--format", "{{ json . }}") + res.Assert(t, icmd.Expected{Out: `"CloudIntegration":`}) + }) + t.Run("delegate version flag", func(t *testing.T) { c.RunDockerCmd("context", "create", "example", "test-example") c.RunDockerCmd("context", "use", "test-example") @@ -431,6 +457,12 @@ func TestMockBackend(t *testing.T) { t.Run("ps", func(t *testing.T) { res := c.RunDockerCmd("ps") golden.Assert(t, res.Stdout(), "ps-out-example.golden") + + res = c.RunDockerCmd("ps", "--format", "pretty") + golden.Assert(t, res.Stdout(), "ps-out-example.golden") + + res = c.RunDockerCmd("ps", "--format", "json") + golden.Assert(t, res.Stdout(), "ps-out-example-json.golden") }) t.Run("ps quiet", func(t *testing.T) { diff --git a/tests/e2e/testdata/ls-out-json.golden b/tests/e2e/testdata/ls-out-json.golden new file mode 100644 index 00000000..e497ccd6 --- /dev/null +++ b/tests/e2e/testdata/ls-out-json.golden @@ -0,0 +1,16 @@ +[ + { + "Name": "default", + "Metadata": { + "Description": "Current DOCKER_HOST based configuration", + "StackOrchestrator": "swarm", + "Type": "moby" + }, + "Endpoints": { + "docker": { + "Host": "unix:///var/run/docker.sock" + }, + "kubernetes": {} + } + } +] diff --git a/tests/e2e/testdata/ps-out-example-json.golden b/tests/e2e/testdata/ps-out-example-json.golden new file mode 100644 index 00000000..24b8048e --- /dev/null +++ b/tests/e2e/testdata/ps-out-example-json.golden @@ -0,0 +1,30 @@ +[ + { + "ID": "id", + "Status": "", + "Image": "nginx", + "Command": "", + "CPUTime": 0, + "CPULimit": 0, + "MemoryUsage": 0, + "MemoryLimit": 0, + "PidsCurrent": 0, + "PidsLimit": 0, + "Platform": "", + "RestartPolicyCondition": "" + }, + { + "ID": "1234", + "Status": "", + "Image": "alpine", + "Command": "", + "CPUTime": 0, + "CPULimit": 0, + "MemoryUsage": 0, + "MemoryLimit": 0, + "PidsCurrent": 0, + "PidsLimit": 0, + "Platform": "", + "RestartPolicyCondition": "" + } +] From 0f6f54721440cab876e5efde97219997c3823317 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 10:32:26 +0200 Subject: [PATCH 02/15] Refactor Print method for lists This also fixes in the case of nil list Signed-off-by: Ulysses Souza --- cli/cmd/compose/list.go | 29 ++------- cli/cmd/compose/list_test.go | 45 -------------- cli/cmd/compose/ps.go | 26 ++------ cli/cmd/context/ls.go | 26 ++------ cli/cmd/ps.go | 31 ++-------- cli/cmd/secrets.go | 32 ++-------- cli/cmd/secrets_test.go | 45 -------------- cli/cmd/version.go | 8 +-- cli/cmd/volume/list.go | 31 ++-------- cli/cmd/volume/list_test.go | 45 -------------- .../volume/testdata/volumes-out-json.golden | 6 -- cli/cmd/volume/testdata/volumes-out.golden | 2 - formatter/formatter.go | 44 +++++++++++++ formatter/formatter_test.go | 62 +++++++++++++++++++ formatter/json.go | 8 ++- go.mod | 1 + go.sum | 1 + tests/e2e/testdata/ls-out-json.golden | 2 +- tests/e2e/testdata/ps-out-example-json.golden | 2 +- 19 files changed, 151 insertions(+), 295 deletions(-) delete mode 100644 cli/cmd/compose/list_test.go delete mode 100644 cli/cmd/secrets_test.go delete mode 100644 cli/cmd/volume/list_test.go delete mode 100644 cli/cmd/volume/testdata/volumes-out-json.golden delete mode 100644 cli/cmd/volume/testdata/volumes-out.golden create mode 100644 formatter/formatter.go create mode 100644 formatter/formatter_test.go diff --git a/cli/cmd/compose/list.go b/cli/cmd/compose/list.go index b1daa690..54cd0672 100644 --- a/cli/cmd/compose/list.go +++ b/cli/cmd/compose/list.go @@ -21,15 +21,11 @@ import ( "fmt" "io" "os" - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/compose" - "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -60,26 +56,9 @@ func runList(ctx context.Context, opts composeOptions) error { return err } - return printListFormatted(opts.Format, os.Stdout, stackList) -} - -func printListFormatted(format string, out io.Writer, stackList []compose.Stack) error { - var err error - switch strings.ToLower(format) { - case formatter.PRETTY, "": - err = formatter.PrintPrettySection(out, func(w io.Writer) { - for _, stack := range stackList { - fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) - } - }, "NAME", "STATUS") - case formatter.JSON: - outJSON, err := formatter.ToStandardJSON(stackList) - if err != nil { - return err + return formatter.Print(stackList, opts.Format, os.Stdout, func(w io.Writer) { + for _, stack := range stackList { + _, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) } - _, _ = fmt.Fprint(out, outJSON) - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err + }, "NAME", "STATUS") } diff --git a/cli/cmd/compose/list_test.go b/cli/cmd/compose/list_test.go deleted file mode 100644 index c1ddf8be..00000000 --- a/cli/cmd/compose/list_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package compose - -import ( - "bytes" - "testing" - - "gotest.tools/assert" - "gotest.tools/golden" - - "github.com/docker/compose-cli/api/compose" - "github.com/docker/compose-cli/formatter" -) - -func TestPrintComposeList(t *testing.T) { - secretList := []compose.Stack{ - { - ID: "123", - Name: "myName123", - Status: "Running", - }, - } - out := &bytes.Buffer{} - assert.NilError(t, printListFormatted(formatter.PRETTY, out, secretList)) - golden.Assert(t, out.String(), "compose-list-out.golden") - - out.Reset() - assert.NilError(t, printListFormatted(formatter.JSON, out, secretList)) - golden.Assert(t, out.String(), "compose-list-out-json.golden") -} diff --git a/cli/cmd/compose/ps.go b/cli/cmd/compose/ps.go index bdef232c..ef0af1bb 100644 --- a/cli/cmd/compose/ps.go +++ b/cli/cmd/compose/ps.go @@ -23,12 +23,9 @@ import ( "os" "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/compose" - "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -61,26 +58,11 @@ func runPs(ctx context.Context, opts composeOptions) error { return err } - return printPsFormatted(opts.Format, os.Stdout, serviceList) -} - -func printPsFormatted(format string, out io.Writer, serviceList []compose.ServiceStatus) error { - var err error - switch strings.ToLower(format) { - case formatter.PRETTY, "": - err = formatter.PrintPrettySection(out, func(w io.Writer) { + return formatter.Print(serviceList, opts.Format, os.Stdout, + func(w io.Writer) { for _, service := range serviceList { fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", ")) } - }, "ID", "NAME", "REPLICAS", "PORTS") - case formatter.JSON: - outJSON, err := formatter.ToStandardJSON(serviceList) - if err != nil { - return err - } - _, _ = fmt.Fprint(out, outJSON) - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err + }, + "ID", "NAME", "REPLICAS", "PORTS") } diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 01db114f..5b33a927 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -29,7 +29,6 @@ import ( "github.com/docker/compose-cli/cli/mobycli" apicontext "github.com/docker/compose-cli/context" "github.com/docker/compose-cli/context/store" - "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -98,17 +97,11 @@ func runList(cmd *cobra.Command, opts lsOpts) error { opts.format = formatter.JSON } - return printContextLsFormatted(opts.format, currentContext, os.Stdout, contexts) -} - -func printContextLsFormatted(format string, currContext string, out io.Writer, contexts []*store.DockerContext) error { - var err error - switch strings.ToLower(format) { - case formatter.PRETTY, "": - err = formatter.PrintPrettySection(out, func(w io.Writer) { + return formatter.Print(contexts, opts.format, os.Stdout, + func(w io.Writer) { for _, c := range contexts { contextName := c.Name - if c.Name == currContext { + if c.Name == currentContext { contextName += " *" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", @@ -119,17 +112,8 @@ func printContextLsFormatted(format string, currContext string, out io.Writer, c getEndpoint("kubernetes", c.Endpoints), c.Metadata.StackOrchestrator) } - }, "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") - case formatter.JSON: - out, err := formatter.ToStandardJSON(contexts) - if err != nil { - return err - } - fmt.Println(out) - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err + }, + "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") } func getEndpoint(name string, meta map[string]interface{}) string { diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index 9f9861fc..f3eee0d1 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -28,7 +28,6 @@ import ( "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/containers" - "github.com/docker/compose-cli/errdefs" formatter2 "github.com/docker/compose-cli/formatter" "github.com/docker/compose-cli/utils/formatter" ) @@ -79,7 +78,7 @@ func runPs(ctx context.Context, opts psOpts) error { containerList, err := c.ContainerService().List(ctx, opts.all) if err != nil { - return errors.Wrap(err, "fetch containerList") + return errors.Wrap(err, "fetch containers") } if opts.quiet { @@ -93,30 +92,12 @@ func runPs(ctx context.Context, opts psOpts) error { opts.format = formatter2.JSON } - return printPsFormatted(opts.format, os.Stdout, containerList) -} - -func printPsFormatted(format string, out io.Writer, containers []containers.Container) error { - var err error - switch strings.ToLower(format) { - case formatter2.PRETTY, "": - err = formatter2.PrintPrettySection(out, func(w io.Writer) { - for _, c := range containers { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status, - strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", ")) - } - }, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS") - case formatter2.JSON: - out, err := formatter2.ToStandardJSON(containers) - if err != nil { - return err + return formatter2.Print(containerList, opts.format, os.Stdout, func(w io.Writer) { + for _, c := range containerList { + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status, + strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", ")) } - fmt.Println(out) - - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err + }, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS") } func fqdn(container containers.Container) string { diff --git a/cli/cmd/secrets.go b/cli/cmd/secrets.go index b7dc2aad..9bfd4751 100644 --- a/cli/cmd/secrets.go +++ b/cli/cmd/secrets.go @@ -20,14 +20,11 @@ import ( "fmt" "io" "os" - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -122,11 +119,15 @@ func listSecrets() *cobra.Command { if err != nil { return err } - list, err := c.SecretsService().ListSecrets(cmd.Context()) + secretsList, err := c.SecretsService().ListSecrets(cmd.Context()) if err != nil { return err } - return printSecretList(opts.format, os.Stdout, list) + return formatter.Print(secretsList, opts.format, os.Stdout, func(w io.Writer) { + for _, secret := range secretsList { + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) + } + }, "ID", "NAME", "DESCRIPTION") }, } cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") @@ -155,24 +156,3 @@ func deleteSecret() *cobra.Command { cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.") return cmd } - -func printSecretList(format string, out io.Writer, secrets []secrets.Secret) error { - var err error - switch strings.ToLower(format) { - case formatter.PRETTY, "": - err = formatter.PrintPrettySection(out, func(w io.Writer) { - for _, secret := range secrets { - fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) // nolint:errcheck - } - }, "ID", "NAME", "DESCRIPTION") - case formatter.JSON: - outJSON, err := formatter.ToStandardJSON(secrets) - if err != nil { - return err - } - _, _ = fmt.Fprint(out, outJSON) - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err -} diff --git a/cli/cmd/secrets_test.go b/cli/cmd/secrets_test.go deleted file mode 100644 index 2e6a2fd1..00000000 --- a/cli/cmd/secrets_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package cmd - -import ( - "bytes" - "testing" - - "gotest.tools/assert" - "gotest.tools/v3/golden" - - "github.com/docker/compose-cli/api/secrets" - "github.com/docker/compose-cli/formatter" -) - -func TestPrintList(t *testing.T) { - secretList := []secrets.Secret{ - { - ID: "123", - Name: "secret123", - Description: "secret 1,2,3", - }, - } - out := &bytes.Buffer{} - assert.NilError(t, printSecretList(formatter.PRETTY, out, secretList)) - golden.Assert(t, out.String(), "secrets-out.golden") - - out.Reset() - assert.NilError(t, printSecretList(formatter.JSON, out, secretList)) - golden.Assert(t, out.String(), "secrets-out-json.golden") -} diff --git a/cli/cmd/version.go b/cli/cmd/version.go index c37171d7..bdefec3d 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -52,14 +52,14 @@ func VersionCommand(version string) *cobra.Command { func runVersion(cmd *cobra.Command, version string) { var versionString string - format := strings.TrimSpace(cmd.Flag(formatOpt).Value.String()) + format := strings.ToLower(strings.ReplaceAll(cmd.Flag(formatOpt).Value.String(), " ", "")) displayedVersion := strings.TrimPrefix(version, "v") // Replace is preferred in this case to keep the order. switch format { case formatter.PRETTY, "": versionString = strings.Replace(getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...), "\n Version:", "\n Cloud integration: "+displayedVersion+"\n Version:", 1) - case formatter.JSON, "{{json .}}", "{{json . }}", "{{ json .}}", "{{ json . }}": // Try to catch full JSON formats + case formatter.JSON, "{{json.}}": // Try to catch full JSON formats versionString = strings.Replace(getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...), `"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, displayedVersion), 1) } @@ -78,7 +78,7 @@ func getOutFromMoby(cmd *cobra.Command, args ...string) string { } func fixedPrettyArgs(oArgs []string) []string { - var args []string + args := make([]string, 0) for i := 0; i < len(oArgs); i++ { if isFormatOpt(oArgs[i]) && len(oArgs) > i && @@ -92,7 +92,7 @@ func fixedPrettyArgs(oArgs []string) []string { } func fixedJSONArgs(oArgs []string) []string { - var args []string + args := make([]string, 0) for i := 0; i < len(oArgs); i++ { if isFormatOpt(oArgs[i]) && len(oArgs) > i && diff --git a/cli/cmd/volume/list.go b/cli/cmd/volume/list.go index d20b618b..75570b9f 100644 --- a/cli/cmd/volume/list.go +++ b/cli/cmd/volume/list.go @@ -20,14 +20,10 @@ import ( "fmt" "io" "os" - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/formatter" ) @@ -50,30 +46,13 @@ func listVolume() *cobra.Command { if err != nil { return err } - return printList(opts.format, os.Stdout, vols) + return formatter.Print(vols, opts.format, os.Stdout, func(w io.Writer) { + for _, vol := range vols { + _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) + } + }, "ID", "DESCRIPTION") }, } cmd.Flags().StringVar(&opts.format, "format", formatter.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)") return cmd } - -func printList(format string, out io.Writer, volumes []volumes.Volume) error { - var err error - switch strings.ToLower(format) { - case formatter.PRETTY, "": - _ = formatter.PrintPrettySection(out, func(w io.Writer) { - for _, vol := range volumes { - _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) - } - }, "ID", "DESCRIPTION") - case formatter.JSON: - outJSON, err := formatter.ToStandardJSON(volumes) - if err != nil { - return err - } - _, _ = fmt.Fprint(out, outJSON) - default: - err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) - } - return err -} diff --git a/cli/cmd/volume/list_test.go b/cli/cmd/volume/list_test.go deleted file mode 100644 index 093a4339..00000000 --- a/cli/cmd/volume/list_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2020 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package volume - -import ( - "bytes" - "testing" - - "gotest.tools/assert" - "gotest.tools/v3/golden" - - "github.com/docker/compose-cli/api/volumes" - "github.com/docker/compose-cli/formatter" -) - -func TestPrintList(t *testing.T) { - secrets := []volumes.Volume{ - { - ID: "volume/123", - Description: "volume 123", - }, - } - out := &bytes.Buffer{} - assert.NilError(t, printList(formatter.PRETTY, out, secrets)) - golden.Assert(t, out.String(), "volumes-out.golden") - - out.Reset() - assert.NilError(t, printList(formatter.JSON, out, secrets)) - golden.Assert(t, out.String(), "volumes-out-json.golden") - -} diff --git a/cli/cmd/volume/testdata/volumes-out-json.golden b/cli/cmd/volume/testdata/volumes-out-json.golden deleted file mode 100644 index 480be035..00000000 --- a/cli/cmd/volume/testdata/volumes-out-json.golden +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "ID": "volume/123", - "Description": "volume 123" - } -] \ No newline at end of file diff --git a/cli/cmd/volume/testdata/volumes-out.golden b/cli/cmd/volume/testdata/volumes-out.golden deleted file mode 100644 index 61c99679..00000000 --- a/cli/cmd/volume/testdata/volumes-out.golden +++ /dev/null @@ -1,2 +0,0 @@ -ID DESCRIPTION -volume/123 volume 123 diff --git a/formatter/formatter.go b/formatter/formatter.go new file mode 100644 index 00000000..46af7a45 --- /dev/null +++ b/formatter/formatter.go @@ -0,0 +1,44 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package formatter + +import ( + "fmt" + "io" + "strings" + + "github.com/pkg/errors" + + "github.com/docker/compose-cli/errdefs" +) + +// Print prints formatted lists in different formats +func Print(list interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error { + switch strings.ToLower(format) { + case PRETTY, "": + return PrintPrettySection(outWriter, writerFn, headers...) + case JSON: + outJSON, err := ToStandardJSON(list) + if err != nil { + return err + } + _, _ = fmt.Fprint(outWriter, outJSON) + default: + return errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) + } + return nil +} diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go new file mode 100644 index 00000000..bfc4a713 --- /dev/null +++ b/formatter/formatter_test.go @@ -0,0 +1,62 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package formatter + +import ( + "fmt" + "io" + "testing" + + "go.uber.org/zap/buffer" + "gotest.tools/assert" +) + +type testStruct struct { + Name string + Status string +} + +// Print prints formatted lists in different formats +func TestPrint(t *testing.T) { + testList := []testStruct{ + { + Name: "myName", + Status: "myStatus", + }, + } + + b := &buffer.Buffer{} + assert.NilError(t, Print(testList, PRETTY, b, func(w io.Writer) { + for _, t := range testList { + _, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status) + } + }, "NAME", "STATUS")) + assert.Equal(t, b.String(), "NAME STATUS\nmyName myStatus\n") + + b.Reset() + assert.NilError(t, Print(testList, JSON, b, func(w io.Writer) { + for _, t := range testList { + _, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status) + } + }, "NAME", "STATUS")) + assert.Equal(t, b.String(), `[ + { + "Name": "myName", + "Status": "myStatus" + } +]`) +} diff --git a/formatter/json.go b/formatter/json.go index bb9b6078..df174b92 100644 --- a/formatter/json.go +++ b/formatter/json.go @@ -16,12 +16,18 @@ package formatter -import "encoding/json" +import ( + "encoding/json" + "reflect" +) const standardIndentation = " " // ToStandardJSON return a string with the JSON representation of the interface{} func ToStandardJSON(i interface{}) (string, error) { + if reflect.ValueOf(i).IsNil() { + return "{}", nil + } b, err := json.MarshalIndent(i, "", standardIndentation) if err != nil { return "", err diff --git a/go.mod b/go.mod index aef8bdfa..d56c8cff 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 + go.uber.org/zap v1.10.0 golang.org/x/mod v0.3.0 golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 diff --git a/go.sum b/go.sum index 378b6d0d..1239c69e 100644 --- a/go.sum +++ b/go.sum @@ -480,6 +480,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/tests/e2e/testdata/ls-out-json.golden b/tests/e2e/testdata/ls-out-json.golden index e497ccd6..319e7f8e 100644 --- a/tests/e2e/testdata/ls-out-json.golden +++ b/tests/e2e/testdata/ls-out-json.golden @@ -13,4 +13,4 @@ "kubernetes": {} } } -] +] \ No newline at end of file diff --git a/tests/e2e/testdata/ps-out-example-json.golden b/tests/e2e/testdata/ps-out-example-json.golden index 24b8048e..34699ba3 100644 --- a/tests/e2e/testdata/ps-out-example-json.golden +++ b/tests/e2e/testdata/ps-out-example-json.golden @@ -27,4 +27,4 @@ "Platform": "", "RestartPolicyCondition": "" } -] +] \ No newline at end of file From ef5e013aa76c72c70ca8510565d0d61cc025eb77 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 10:41:36 +0200 Subject: [PATCH 03/15] Return empty list instead of nil on List Signed-off-by: Ulysses Souza --- aci/containers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aci/containers.go b/aci/containers.go index 570f6afd..93fb03eb 100644 --- a/aci/containers.go +++ b/aci/containers.go @@ -53,7 +53,7 @@ func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers if err != nil { return nil, err } - var res []containers.Container + res := []containers.Container{} for _, group := range containerGroups { if group.Containers == nil || len(*group.Containers) == 0 { return nil, fmt.Errorf("no containers found in ACI container group %s", *group.Name) From fb59c9385dd9dcad859d1bf8c2c4def44538386f Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 11:00:48 +0200 Subject: [PATCH 04/15] Fix tests Signed-off-by: Ulysses Souza --- cli/cmd/version_test.go | 6 +++--- formatter/json.go | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/cli/cmd/version_test.go b/cli/cmd/version_test.go index ea61e024..bdcf2435 100644 --- a/cli/cmd/version_test.go +++ b/cli/cmd/version_test.go @@ -31,7 +31,7 @@ func TestVersionFormat(t *testing.T) { jsonCases := []caze{ { Actual: fixedJSONArgs([]string{}), - Expected: nil, + Expected: []string{}, }, { Actual: fixedJSONArgs([]string{ @@ -103,7 +103,7 @@ func TestVersionFormat(t *testing.T) { prettyCases := []caze{ { Actual: fixedPrettyArgs([]string{}), - Expected: nil, + Expected: []string{}, }, { Actual: fixedPrettyArgs([]string{ @@ -172,7 +172,7 @@ func TestVersionFormat(t *testing.T) { "--format", "pretty", }), - Expected: nil, + Expected: []string{}, }, } diff --git a/formatter/json.go b/formatter/json.go index df174b92..dc68e50d 100644 --- a/formatter/json.go +++ b/formatter/json.go @@ -18,16 +18,12 @@ package formatter import ( "encoding/json" - "reflect" ) const standardIndentation = " " // ToStandardJSON return a string with the JSON representation of the interface{} func ToStandardJSON(i interface{}) (string, error) { - if reflect.ValueOf(i).IsNil() { - return "{}", nil - } b, err := json.MarshalIndent(i, "", standardIndentation) if err != nil { return "", err From 044e7f571d4b132e9c069af7c70c84a1ee9c1fa7 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 11:25:08 +0200 Subject: [PATCH 05/15] Remove go template from help on list commands Signed-off-by: Ulysses Souza --- cli/cmd/context/ls.go | 4 ++-- cli/cmd/ps.go | 2 +- cli/cmd/version.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 5b33a927..1e0ee111 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -58,7 +58,7 @@ func listCommand() *cobra.Command { } cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only show context names") cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") - cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") + cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") _ = cmd.Flags().MarkHidden("json") return cmd @@ -104,7 +104,7 @@ func runList(cmd *cobra.Command, opts lsOpts) error { if c.Name == currentContext { contextName += " *" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", contextName, c.Type(), c.Metadata.Description, diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index f3eee0d1..ff1c43f1 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -53,7 +53,7 @@ func PsCommand() *cobra.Command { cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") cmd.Flags().BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") - cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") + cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") _ = cmd.Flags().MarkHidden("json") return cmd diff --git a/cli/cmd/version.go b/cli/cmd/version.go index bdefec3d..0353dcdf 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -42,8 +42,7 @@ func VersionCommand(version string) *cobra.Command { } // define flags for backward compatibility with com.docker.cli flags := cmd.Flags() - flags.StringP(formatOpt, "f", "", "Format the output using the given Go template") - // flags.String(&opts.format, "format", "", "Format the output. Values: [pretty | json | go template]. (Default: pretty)") + flags.StringP(formatOpt, "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)") flags.String("kubeconfig", "", "Kubernetes config file") mobyflags.AddMobyFlagsForRetrocompatibility(flags) From ac8975ea1e3a56bbfbe1b1596c8a74146afc4609 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 11:25:30 +0200 Subject: [PATCH 06/15] Fix import Signed-off-by: Ulysses Souza --- formatter/formatter_test.go | 4 ++-- go.mod | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go index bfc4a713..21668ae1 100644 --- a/formatter/formatter_test.go +++ b/formatter/formatter_test.go @@ -17,11 +17,11 @@ package formatter import ( + "bytes" "fmt" "io" "testing" - "go.uber.org/zap/buffer" "gotest.tools/assert" ) @@ -39,7 +39,7 @@ func TestPrint(t *testing.T) { }, } - b := &buffer.Buffer{} + b := &bytes.Buffer{} assert.NilError(t, Print(testList, PRETTY, b, func(w io.Writer) { for _, t := range testList { _, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status) diff --git a/go.mod b/go.mod index d56c8cff..aef8bdfa 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,6 @@ require ( github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 - go.uber.org/zap v1.10.0 golang.org/x/mod v0.3.0 golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 From 604cf0c911c69fcefac204069e2024c3dbe58d77 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 11:29:55 +0200 Subject: [PATCH 07/15] Add comment on legacy --json option Signed-off-by: Ulysses Souza --- cli/cmd/ps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index ff1c43f1..27f5a41a 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -54,7 +54,7 @@ func PsCommand() *cobra.Command { cmd.Flags().BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") cmd.Flags().BoolVar(&opts.json, "json", false, "Format output as JSON") cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - _ = cmd.Flags().MarkHidden("json") + _ = cmd.Flags().MarkHidden("json") // Legacy. This is used by VSCode Docker extension return cmd } From 6c883dc4a56c8c25da6af807a6aa7b99086e2ccd Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 30 Sep 2020 21:43:06 +0200 Subject: [PATCH 08/15] Print only the information present in pretty on json Signed-off-by: Ulysses Souza --- cli/cmd/compose/list.go | 22 ++++++++++++++++-- cli/cmd/compose/ps.go | 30 ++++++++++++++++++++++--- cli/cmd/context/ls.go | 49 +++++++++++++++++++++++++++++++---------- cli/cmd/ps.go | 29 +++++++++++++++++++++--- cli/cmd/secrets.go | 23 +++++++++++++++++-- cli/cmd/volume/list.go | 22 ++++++++++++++++-- 6 files changed, 151 insertions(+), 24 deletions(-) diff --git a/cli/cmd/compose/list.go b/cli/cmd/compose/list.go index 54cd0672..806ee535 100644 --- a/cli/cmd/compose/list.go +++ b/cli/cmd/compose/list.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/pflag" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/formatter" ) @@ -56,9 +57,26 @@ func runList(ctx context.Context, opts composeOptions) error { return err } - return formatter.Print(stackList, opts.Format, os.Stdout, func(w io.Writer) { - for _, stack := range stackList { + view := viewFromStackList(stackList) + return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) { + for _, stack := range view { _, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status) } }, "NAME", "STATUS") } + +type stackView struct { + Name string + Status string +} + +func viewFromStackList(stackList []compose.Stack) []stackView { + retList := make([]stackView, len(stackList)) + for i, s := range stackList { + retList[i] = stackView{ + Name: s.Name, + Status: s.Status, + } + } + return retList +} diff --git a/cli/cmd/compose/ps.go b/cli/cmd/compose/ps.go index ef0af1bb..13327f83 100644 --- a/cli/cmd/compose/ps.go +++ b/cli/cmd/compose/ps.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/formatter" ) @@ -58,11 +59,34 @@ func runPs(ctx context.Context, opts composeOptions) error { return err } - return formatter.Print(serviceList, opts.Format, os.Stdout, + view := viewFromServiceStatusList(serviceList) + return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) { - for _, service := range serviceList { - fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", ")) + for _, service := range view { + _, _ = fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", ")) } }, "ID", "NAME", "REPLICAS", "PORTS") } + +type serviceStatusView struct { + ID string + Name string + Replicas int + Desired int + Ports []string +} + +func viewFromServiceStatusList(serviceStatusList []compose.ServiceStatus) []serviceStatusView { + retList := make([]serviceStatusView, len(serviceStatusList)) + for i, s := range serviceStatusList { + retList[i] = serviceStatusView{ + ID: s.ID, + Name: s.Name, + Replicas: s.Replicas, + Desired: s.Desired, + Ports: s.Ports, + } + } + return retList +} diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 1e0ee111..5cb86557 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -97,20 +97,17 @@ func runList(cmd *cobra.Command, opts lsOpts) error { opts.format = formatter.JSON } - return formatter.Print(contexts, opts.format, os.Stdout, + view := viewFromContextList(contexts, currentContext) + return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) { - for _, c := range contexts { - contextName := c.Name - if c.Name == currentContext { - contextName += " *" - } + for _, c := range view { _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - contextName, - c.Type(), - c.Metadata.Description, - getEndpoint("docker", c.Endpoints), - getEndpoint("kubernetes", c.Endpoints), - c.Metadata.StackOrchestrator) + c.Name, + c.Type, + c.Description, + c.DockerEndpoint, + c.KubernetesEndpoint, + c.Orchestrator) } }, "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") @@ -133,3 +130,31 @@ func getEndpoint(name string, meta map[string]interface{}) string { return result } + +type contextView struct { + Name string + Type string + Description string + DockerEndpoint string + KubernetesEndpoint string + Orchestrator string +} + +func viewFromContextList(contextList []*store.DockerContext, currentContext string) []contextView { + retList := make([]contextView, len(contextList)) + for i, c := range contextList { + contextName := c.Name + if c.Name == currentContext { + contextName += " *" + } + retList[i] = contextView{ + Name: contextName, + Type: c.Type(), + Description: c.Metadata.Description, + DockerEndpoint: getEndpoint("docker", c.Endpoints), + KubernetesEndpoint: getEndpoint("kubernetes", c.Endpoints), + Orchestrator: c.Metadata.StackOrchestrator, + } + } + return retList +} diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index 27f5a41a..77b6fc87 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -92,10 +92,11 @@ func runPs(ctx context.Context, opts psOpts) error { opts.format = formatter2.JSON } - return formatter2.Print(containerList, opts.format, os.Stdout, func(w io.Writer) { - for _, c := range containerList { + view := viewFromContainerList(containerList) + return formatter2.Print(view, opts.format, os.Stdout, func(w io.Writer) { + for _, c := range view { _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status, - strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", ")) + strings.Join(c.Ports, ", ")) } }, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS") } @@ -107,3 +108,25 @@ func fqdn(container containers.Container) string { } return fqdn } + +type containerView struct { + ID string + Image string + Command string + Status string + Ports []string +} + +func viewFromContainerList(containerList []containers.Container) []containerView { + retList := make([]containerView, len(containerList)) + for i, c := range containerList { + retList[i] = containerView{ + ID: c.ID, + Image: c.Image, + Command: c.Command, + Status: c.Status, + Ports: formatter.PortsToStrings(c.Ports, fqdn(c)), + } + } + return retList +} diff --git a/cli/cmd/secrets.go b/cli/cmd/secrets.go index 9bfd4751..f372376b 100644 --- a/cli/cmd/secrets.go +++ b/cli/cmd/secrets.go @@ -123,8 +123,9 @@ func listSecrets() *cobra.Command { if err != nil { return err } - return formatter.Print(secretsList, opts.format, os.Stdout, func(w io.Writer) { - for _, secret := range secretsList { + view := viewFromSecretList(secretsList) + return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) { + for _, secret := range view { _, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) } }, "ID", "NAME", "DESCRIPTION") @@ -134,6 +135,24 @@ func listSecrets() *cobra.Command { return cmd } +type secretView struct { + ID string + Name string + Description string +} + +func viewFromSecretList(secretList []secrets.Secret) []secretView { + retList := make([]secretView, len(secretList)) + for i, s := range secretList { + retList[i] = secretView{ + ID: s.ID, + Name: s.Name, + Description: s.Description, + } + } + return retList +} + type deleteSecretOptions struct { recover bool } diff --git a/cli/cmd/volume/list.go b/cli/cmd/volume/list.go index 75570b9f..b279e912 100644 --- a/cli/cmd/volume/list.go +++ b/cli/cmd/volume/list.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/volumes" "github.com/docker/compose-cli/formatter" ) @@ -46,8 +47,9 @@ func listVolume() *cobra.Command { if err != nil { return err } - return formatter.Print(vols, opts.format, os.Stdout, func(w io.Writer) { - for _, vol := range vols { + view := viewFromVolumeList(vols) + return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) { + for _, vol := range view { _, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) } }, "ID", "DESCRIPTION") @@ -56,3 +58,19 @@ func listVolume() *cobra.Command { cmd.Flags().StringVar(&opts.format, "format", formatter.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)") return cmd } + +type volumeView struct { + ID string + Description string +} + +func viewFromVolumeList(volumeList []volumes.Volume) []volumeView { + retList := make([]volumeView, len(volumeList)) + for i, v := range volumeList { + retList[i] = volumeView{ + ID: v.ID, + Description: v.Description, + } + } + return retList +} From 178ac40dba34ba2bba8ad0a4ecb5a45d4652ad01 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Oct 2020 02:33:41 +0200 Subject: [PATCH 09/15] Fix return on ports when empty Signed-off-by: Ulysses Souza --- utils/formatter/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/formatter/container.go b/utils/formatter/container.go index dc5c3fdb..5dcdf342 100644 --- a/utils/formatter/container.go +++ b/utils/formatter/container.go @@ -33,8 +33,8 @@ type portGroup struct { // PortsToStrings returns a human readable published ports func PortsToStrings(ports []containers.Port, fqdn string) []string { groupMap := make(map[string]*portGroup) + result := []string{} var ( - result []string hostMappings []string groupMapKeys []string ) From 3e9095a873bd12f963ecb83e2ec44f8a59ddff74 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Oct 2020 03:41:55 +0200 Subject: [PATCH 10/15] Fix docker context ls for retrocompatibility It writes each context as an independent object line Signed-off-by: Ulysses Souza --- cli/cmd/context/ls.go | 42 ++++++++++++------- cli/cmd/ps.go | 4 +- formatter/json.go | 9 ++++ tests/e2e/testdata/ls-out-json.golden | 17 +------- tests/e2e/testdata/ps-out-example-json.golden | 22 ++-------- 5 files changed, 42 insertions(+), 52 deletions(-) diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 5cb86557..5e9cd21d 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -93,21 +93,33 @@ func runList(cmd *cobra.Command, opts lsOpts) error { return nil } - if opts.json { - opts.format = formatter.JSON + view := viewFromContextList(contexts, currentContext) + + if opts.json || opts.format == formatter.JSON { + for _, l := range view { + outJSON, err := formatter.ToCompressedJSON(l) + if err != nil { + return err + } + _, _ = fmt.Fprintln(os.Stdout, outJSON) + } + return nil } - view := viewFromContextList(contexts, currentContext) - return formatter.Print(view, opts.format, os.Stdout, + return formatter.Print(view, formatter.PRETTY, os.Stdout, func(w io.Writer) { for _, c := range view { + contextName := c.Name + if c.Current { + contextName += " *" + } _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - c.Name, + contextName, c.Type, c.Description, c.DockerEndpoint, c.KubernetesEndpoint, - c.Orchestrator) + c.StackOrchestrator) } }, "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR") @@ -132,28 +144,26 @@ func getEndpoint(name string, meta map[string]interface{}) string { } type contextView struct { - Name string - Type string + Current bool Description string DockerEndpoint string KubernetesEndpoint string - Orchestrator string + Type string + Name string + StackOrchestrator string } func viewFromContextList(contextList []*store.DockerContext, currentContext string) []contextView { retList := make([]contextView, len(contextList)) for i, c := range contextList { - contextName := c.Name - if c.Name == currentContext { - contextName += " *" - } retList[i] = contextView{ - Name: contextName, - Type: c.Type(), + Current: c.Name == currentContext, Description: c.Metadata.Description, DockerEndpoint: getEndpoint("docker", c.Endpoints), KubernetesEndpoint: getEndpoint("kubernetes", c.Endpoints), - Orchestrator: c.Metadata.StackOrchestrator, + Name: c.Name, + Type: c.Type(), + StackOrchestrator: c.Metadata.StackOrchestrator, } } return retList diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index 77b6fc87..56d6ab72 100644 --- a/cli/cmd/ps.go +++ b/cli/cmd/ps.go @@ -112,8 +112,8 @@ func fqdn(container containers.Container) string { type containerView struct { ID string Image string - Command string Status string + Command string Ports []string } @@ -123,8 +123,8 @@ func viewFromContainerList(containerList []containers.Container) []containerView retList[i] = containerView{ ID: c.ID, Image: c.Image, - Command: c.Command, Status: c.Status, + Command: c.Command, Ports: formatter.PortsToStrings(c.Ports, fqdn(c)), } } diff --git a/formatter/json.go b/formatter/json.go index dc68e50d..a86e3f20 100644 --- a/formatter/json.go +++ b/formatter/json.go @@ -30,3 +30,12 @@ func ToStandardJSON(i interface{}) (string, error) { } return string(b), nil } + +// ToCompressedJSON return a string with the JSON representation of the interface{} +func ToCompressedJSON(i interface{}) (string, error) { + b, err := json.Marshal(i) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/tests/e2e/testdata/ls-out-json.golden b/tests/e2e/testdata/ls-out-json.golden index 319e7f8e..34d53cb0 100644 --- a/tests/e2e/testdata/ls-out-json.golden +++ b/tests/e2e/testdata/ls-out-json.golden @@ -1,16 +1 @@ -[ - { - "Name": "default", - "Metadata": { - "Description": "Current DOCKER_HOST based configuration", - "StackOrchestrator": "swarm", - "Type": "moby" - }, - "Endpoints": { - "docker": { - "Host": "unix:///var/run/docker.sock" - }, - "kubernetes": {} - } - } -] \ No newline at end of file +{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","KubernetesEndpoint":"","Type":"moby","Name":"default","StackOrchestrator":"swarm"} diff --git a/tests/e2e/testdata/ps-out-example-json.golden b/tests/e2e/testdata/ps-out-example-json.golden index 34699ba3..c34e5d21 100644 --- a/tests/e2e/testdata/ps-out-example-json.golden +++ b/tests/e2e/testdata/ps-out-example-json.golden @@ -1,30 +1,16 @@ [ { "ID": "id", - "Status": "", "Image": "nginx", + "Status": "", "Command": "", - "CPUTime": 0, - "CPULimit": 0, - "MemoryUsage": 0, - "MemoryLimit": 0, - "PidsCurrent": 0, - "PidsLimit": 0, - "Platform": "", - "RestartPolicyCondition": "" + "Ports": [] }, { "ID": "1234", - "Status": "", "Image": "alpine", + "Status": "", "Command": "", - "CPUTime": 0, - "CPULimit": 0, - "MemoryUsage": 0, - "MemoryLimit": 0, - "PidsCurrent": 0, - "PidsLimit": 0, - "Platform": "", - "RestartPolicyCondition": "" + "Ports": [] } ] \ No newline at end of file From b8a1e6c8885d3aa81fdc57d5edcb3d45787f8e85 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Oct 2020 05:16:02 +0200 Subject: [PATCH 11/15] Change JSON output to individual lines Signed-off-by: Ulysses Souza --- cli/cmd/context/ls.go | 16 ++++--------- formatter/formatter.go | 24 +++++++++++++++---- formatter/formatter_test.go | 19 ++++++++------- tests/e2e/testdata/ps-out-example-json.golden | 18 ++------------ 4 files changed, 35 insertions(+), 42 deletions(-) diff --git a/cli/cmd/context/ls.go b/cli/cmd/context/ls.go index 5e9cd21d..47ca3dea 100644 --- a/cli/cmd/context/ls.go +++ b/cli/cmd/context/ls.go @@ -93,20 +93,12 @@ func runList(cmd *cobra.Command, opts lsOpts) error { return nil } - view := viewFromContextList(contexts, currentContext) - - if opts.json || opts.format == formatter.JSON { - for _, l := range view { - outJSON, err := formatter.ToCompressedJSON(l) - if err != nil { - return err - } - _, _ = fmt.Fprintln(os.Stdout, outJSON) - } - return nil + if opts.json { + opts.format = formatter.JSON } - return formatter.Print(view, formatter.PRETTY, os.Stdout, + view := viewFromContextList(contexts, currentContext) + return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) { for _, c := range view { contextName := c.Name diff --git a/formatter/formatter.go b/formatter/formatter.go index 46af7a45..6686319f 100644 --- a/formatter/formatter.go +++ b/formatter/formatter.go @@ -19,6 +19,7 @@ package formatter import ( "fmt" "io" + "reflect" "strings" "github.com/pkg/errors" @@ -27,16 +28,29 @@ import ( ) // Print prints formatted lists in different formats -func Print(list interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error { +func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error { switch strings.ToLower(format) { case PRETTY, "": return PrintPrettySection(outWriter, writerFn, headers...) case JSON: - outJSON, err := ToStandardJSON(list) - if err != nil { - return err + switch reflect.TypeOf(toJSON).Kind() { + case reflect.Slice: + s := reflect.ValueOf(toJSON) + for i := 0; i < s.Len(); i++ { + obj := s.Index(i).Interface() + jsonLine, err := ToCompressedJSON(obj) + if err != nil { + return err + } + _, _ = fmt.Fprintln(outWriter, jsonLine) + } + default: + outJSON, err := ToStandardJSON(toJSON) + if err != nil { + return err + } + _, _ = fmt.Fprintln(outWriter, outJSON) } - _, _ = fmt.Fprint(outWriter, outJSON) default: return errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format) } diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go index 21668ae1..6657a015 100644 --- a/formatter/formatter_test.go +++ b/formatter/formatter_test.go @@ -34,8 +34,12 @@ type testStruct struct { func TestPrint(t *testing.T) { testList := []testStruct{ { - Name: "myName", - Status: "myStatus", + Name: "myName1", + Status: "myStatus1", + }, + { + Name: "myName2", + Status: "myStatus2", }, } @@ -45,7 +49,7 @@ func TestPrint(t *testing.T) { _, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status) } }, "NAME", "STATUS")) - assert.Equal(t, b.String(), "NAME STATUS\nmyName myStatus\n") + assert.Equal(t, b.String(), "NAME STATUS\nmyName1 myStatus1\nmyName2 myStatus2\n") b.Reset() assert.NilError(t, Print(testList, JSON, b, func(w io.Writer) { @@ -53,10 +57,7 @@ func TestPrint(t *testing.T) { _, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status) } }, "NAME", "STATUS")) - assert.Equal(t, b.String(), `[ - { - "Name": "myName", - "Status": "myStatus" - } -]`) + assert.Equal(t, b.String(), `{"Name":"myName1","Status":"myStatus1"} +{"Name":"myName2","Status":"myStatus2"} +`) } diff --git a/tests/e2e/testdata/ps-out-example-json.golden b/tests/e2e/testdata/ps-out-example-json.golden index c34e5d21..2f7ddef9 100644 --- a/tests/e2e/testdata/ps-out-example-json.golden +++ b/tests/e2e/testdata/ps-out-example-json.golden @@ -1,16 +1,2 @@ -[ - { - "ID": "id", - "Image": "nginx", - "Status": "", - "Command": "", - "Ports": [] - }, - { - "ID": "1234", - "Image": "alpine", - "Status": "", - "Command": "", - "Ports": [] - } -] \ No newline at end of file +{"ID":"id","Image":"nginx","Status":"","Command":"","Ports":[]} +{"ID":"1234","Image":"alpine","Status":"","Command":"","Ports":[]} From 8c51b8b67dbcbe9e3f1d76b745d564e11c7c780a Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Oct 2020 05:21:06 +0200 Subject: [PATCH 12/15] Fix lint Signed-off-by: Ulysses Souza --- formatter/pretty.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formatter/pretty.go b/formatter/pretty.go index 9f014fe9..bb85dede 100644 --- a/formatter/pretty.go +++ b/formatter/pretty.go @@ -26,7 +26,7 @@ import ( // PrintPrettySection prints a tabbed section on the writer parameter func PrintPrettySection(out io.Writer, printer func(writer io.Writer), headers ...string) error { w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(headers, "\t")) + _, _ = fmt.Fprintln(w, strings.Join(headers, "\t")) printer(w) return w.Flush() } From 83f21b9293b7bee233a8532eed0e622c67476c8e Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Oct 2020 10:44:15 +0200 Subject: [PATCH 13/15] Remove unused files Signed-off-by: Ulysses Souza --- cli/cmd/compose/testdata/compose-list-out-json.golden | 7 ------- cli/cmd/compose/testdata/compose-list-out.golden | 2 -- cli/cmd/testdata/secrets-out-json.golden | 8 -------- 3 files changed, 17 deletions(-) delete mode 100644 cli/cmd/compose/testdata/compose-list-out-json.golden delete mode 100644 cli/cmd/compose/testdata/compose-list-out.golden delete mode 100644 cli/cmd/testdata/secrets-out-json.golden diff --git a/cli/cmd/compose/testdata/compose-list-out-json.golden b/cli/cmd/compose/testdata/compose-list-out-json.golden deleted file mode 100644 index 219a65d9..00000000 --- a/cli/cmd/compose/testdata/compose-list-out-json.golden +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "ID": "123", - "Name": "myName123", - "Status": "Running" - } -] \ No newline at end of file diff --git a/cli/cmd/compose/testdata/compose-list-out.golden b/cli/cmd/compose/testdata/compose-list-out.golden deleted file mode 100644 index fab2b912..00000000 --- a/cli/cmd/compose/testdata/compose-list-out.golden +++ /dev/null @@ -1,2 +0,0 @@ -NAME STATUS -myName123 Running diff --git a/cli/cmd/testdata/secrets-out-json.golden b/cli/cmd/testdata/secrets-out-json.golden deleted file mode 100644 index 55a32b3e..00000000 --- a/cli/cmd/testdata/secrets-out-json.golden +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "ID": "123", - "Name": "secret123", - "Labels": null, - "Description": "secret 1,2,3" - } -] \ No newline at end of file From 74e86ab06a12f9c1102060c9f6d3a913176dd8ce Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Fri, 2 Oct 2020 18:53:22 +0200 Subject: [PATCH 14/15] Fix HTML escape encoding Signed-off-by: Ulysses Souza --- cli/cmd/inspect.go | 2 +- formatter/formatter.go | 4 ++-- formatter/json.go | 22 ++++++++++------------ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cli/cmd/inspect.go b/cli/cmd/inspect.go index 20997d86..2e964799 100644 --- a/cli/cmd/inspect.go +++ b/cli/cmd/inspect.go @@ -56,7 +56,7 @@ func runInspect(ctx context.Context, id string) error { if err != nil { return err } - fmt.Println(j) + fmt.Print(j) return nil } diff --git a/formatter/formatter.go b/formatter/formatter.go index 6686319f..52cd9d0d 100644 --- a/formatter/formatter.go +++ b/formatter/formatter.go @@ -38,11 +38,11 @@ func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func s := reflect.ValueOf(toJSON) for i := 0; i < s.Len(); i++ { obj := s.Index(i).Interface() - jsonLine, err := ToCompressedJSON(obj) + jsonLine, err := ToJSON(obj, "", "") if err != nil { return err } - _, _ = fmt.Fprintln(outWriter, jsonLine) + _, _ = fmt.Fprint(outWriter, jsonLine) } default: outJSON, err := ToStandardJSON(toJSON) diff --git a/formatter/json.go b/formatter/json.go index a86e3f20..afadb0c8 100644 --- a/formatter/json.go +++ b/formatter/json.go @@ -17,6 +17,7 @@ package formatter import ( + "bytes" "encoding/json" ) @@ -24,18 +25,15 @@ const standardIndentation = " " // ToStandardJSON return a string with the JSON representation of the interface{} func ToStandardJSON(i interface{}) (string, error) { - b, err := json.MarshalIndent(i, "", standardIndentation) - if err != nil { - return "", err - } - return string(b), nil + return ToJSON(i, "", standardIndentation) } -// ToCompressedJSON return a string with the JSON representation of the interface{} -func ToCompressedJSON(i interface{}) (string, error) { - b, err := json.Marshal(i) - if err != nil { - return "", err - } - return string(b), nil +// ToJSON return a string with the JSON representation of the interface{} +func ToJSON(i interface{}, prefix string, indentation string) (string, error) { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent(prefix, indentation) + err := encoder.Encode(i) + return buffer.String(), err } From 344db7027401eca6ed214d7e97eab9a7a7c33b29 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Fri, 2 Oct 2020 19:04:43 +0200 Subject: [PATCH 15/15] Fix version command on bypassing to moby cli Signed-off-by: Ulysses Souza --- cli/cmd/version.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 0353dcdf..3a50f179 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -61,7 +61,10 @@ func runVersion(cmd *cobra.Command, version string) { case formatter.JSON, "{{json.}}": // Try to catch full JSON formats versionString = strings.Replace(getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...), `"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, displayedVersion), 1) + default: + versionString = getOutFromMoby(cmd) } + fmt.Print(versionString) }