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 <ulyssessouza@gmail.com>
This commit is contained in:
Ulysses Souza 2020-09-28 17:08:27 +02:00
Родитель abd6af6386
Коммит 8961805412
23 изменённых файлов: 665 добавлений и 118 удалений

Просмотреть файл

@ -33,6 +33,7 @@ type composeOptions struct {
WorkingDir string
ConfigPaths []string
Environment []string
Format string
}
func (o *composeOptions) toProjectName() (string, error) {

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

7
cli/cmd/compose/testdata/compose-list-out-json.golden поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
[
{
"ID": "123",
"Name": "myName123",
"Status": "Running"
}
]

2
cli/cmd/compose/testdata/compose-list-out.golden поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
NAME STATUS
myName123 Running

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

8
cli/cmd/testdata/secrets-out-json.golden поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
[
{
"ID": "123",
"Name": "secret123",
"Labels": null,
"Description": "secret 1,2,3"
}
]

Просмотреть файл

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

190
cli/cmd/version_test.go Normal file
Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

6
cli/cmd/volume/testdata/volumes-out-json.golden поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
[
{
"ID": "volume/123",
"Description": "volume 123"
}
]

Просмотреть файл

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

24
formatter/consts.go Normal file
Просмотреть файл

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

32
formatter/pretty.go Normal file
Просмотреть файл

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

1
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
)

Просмотреть файл

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

16
tests/e2e/testdata/ls-out-json.golden поставляемый Normal file
Просмотреть файл

@ -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": {}
}
}
]

30
tests/e2e/testdata/ps-out-example-json.golden поставляемый Normal file
Просмотреть файл

@ -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": ""
}
]