mirror of
https://github.com/docker/compose.git
synced 2025-07-27 07:34:10 +02:00
Merge pull request #689 from ulyssessouza/json-out
Add json output format to several commands
This commit is contained in:
commit
97576db803
@ -53,7 +53,7 @@ func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var res []containers.Container
|
res := []containers.Container{}
|
||||||
for _, group := range containerGroups {
|
for _, group := range containerGroups {
|
||||||
if group.Containers == nil || len(*group.Containers) == 0 {
|
if group.Containers == nil || len(*group.Containers) == 0 {
|
||||||
return nil, fmt.Errorf("no containers found in ACI container group %s", *group.Name)
|
return nil, fmt.Errorf("no containers found in ACI container group %s", *group.Name)
|
||||||
|
@ -33,6 +33,7 @@ type composeOptions struct {
|
|||||||
WorkingDir string
|
WorkingDir string
|
||||||
ConfigPaths []string
|
ConfigPaths []string
|
||||||
Environment []string
|
Environment []string
|
||||||
|
Format string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *composeOptions) toProjectName() (string, error) {
|
func (o *composeOptions) toProjectName() (string, error) {
|
||||||
|
@ -23,8 +23,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/client"
|
"github.com/docker/compose-cli/api/client"
|
||||||
|
"github.com/docker/compose-cli/api/compose"
|
||||||
|
"github.com/docker/compose-cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listCommand() *cobra.Command {
|
func listCommand() *cobra.Command {
|
||||||
@ -35,10 +38,15 @@ func listCommand() *cobra.Command {
|
|||||||
return runList(cmd.Context(), opts)
|
return runList(cmd.Context(), opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
lsCmd.Flags().StringVarP(&opts.Name, "project-name", "p", "", "Project name")
|
addComposeCommonFlags(lsCmd.Flags(), &opts)
|
||||||
return lsCmd
|
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 {
|
func runList(ctx context.Context, opts composeOptions) error {
|
||||||
c, err := client.New(ctx)
|
c, err := client.New(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,10 +57,26 @@ func runList(ctx context.Context, opts composeOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = printSection(os.Stdout, func(w io.Writer) {
|
view := viewFromStackList(stackList)
|
||||||
for _, stack := range stackList {
|
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
|
||||||
fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
|
for _, stack := range view {
|
||||||
|
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
|
||||||
}
|
}
|
||||||
}, "NAME", "STATUS")
|
}, "NAME", "STATUS")
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/client"
|
"github.com/docker/compose-cli/api/client"
|
||||||
|
"github.com/docker/compose-cli/api/compose"
|
||||||
|
"github.com/docker/compose-cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func psCommand() *cobra.Command {
|
func psCommand() *cobra.Command {
|
||||||
@ -37,10 +38,9 @@ func psCommand() *cobra.Command {
|
|||||||
return runPs(cmd.Context(), opts)
|
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().StringVar(&opts.WorkingDir, "workdir", "", "Work dir")
|
||||||
psCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
psCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||||
|
addComposeCommonFlags(psCmd.Flags(), &opts)
|
||||||
return psCmd
|
return psCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,17 +59,34 @@ func runPs(ctx context.Context, opts composeOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = printSection(os.Stdout, func(w io.Writer) {
|
view := viewFromServiceStatusList(serviceList)
|
||||||
for _, service := range serviceList {
|
return formatter.Print(view, opts.Format, os.Stdout,
|
||||||
fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", "))
|
func(w io.Writer) {
|
||||||
|
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")
|
},
|
||||||
return err
|
"ID", "NAME", "REPLICAS", "PORTS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSection(out io.Writer, printer func(io.Writer), headers ...string) error {
|
type serviceStatusView struct {
|
||||||
w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0)
|
ID string
|
||||||
fmt.Fprintln(w, strings.Join(headers, "\t"))
|
Name string
|
||||||
printer(w)
|
Replicas int
|
||||||
return w.Flush()
|
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
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,13 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/cli/mobycli"
|
"github.com/docker/compose-cli/cli/mobycli"
|
||||||
@ -58,7 +58,8 @@ func listCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only show context names")
|
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().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]. (Default: pretty)")
|
||||||
|
_ = cmd.Flags().MarkHidden("json")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -68,7 +69,7 @@ func runList(cmd *cobra.Command, opts lsOpts) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.format != "" {
|
if opts.format != "" && opts.format != formatter.JSON && opts.format != formatter.PRETTY {
|
||||||
mobycli.Exec(cmd.Root())
|
mobycli.Exec(cmd.Root())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -93,35 +94,27 @@ func runList(cmd *cobra.Command, opts lsOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.json {
|
if opts.json {
|
||||||
j, err := formatter.ToStandardJSON(contexts)
|
opts.format = formatter.JSON
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(j)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
view := viewFromContextList(contexts, currentContext)
|
||||||
fmt.Fprintln(w, "NAME\tTYPE\tDESCRIPTION\tDOCKER ENDPOINT\tKUBERNETES ENDPOINT\tORCHESTRATOR")
|
return formatter.Print(view, opts.format, os.Stdout,
|
||||||
format := "%s\t%s\t%s\t%s\t%s\t%s\n"
|
func(w io.Writer) {
|
||||||
|
for _, c := range view {
|
||||||
for _, c := range contexts {
|
|
||||||
contextName := c.Name
|
contextName := c.Name
|
||||||
if c.Name == currentContext {
|
if c.Current {
|
||||||
contextName += " *"
|
contextName += " *"
|
||||||
}
|
}
|
||||||
|
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||||
fmt.Fprintf(w,
|
|
||||||
format,
|
|
||||||
contextName,
|
contextName,
|
||||||
c.Type(),
|
c.Type,
|
||||||
c.Metadata.Description,
|
c.Description,
|
||||||
getEndpoint("docker", c.Endpoints),
|
c.DockerEndpoint,
|
||||||
getEndpoint("kubernetes", c.Endpoints),
|
c.KubernetesEndpoint,
|
||||||
c.Metadata.StackOrchestrator)
|
c.StackOrchestrator)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return w.Flush()
|
"NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEndpoint(name string, meta map[string]interface{}) string {
|
func getEndpoint(name string, meta map[string]interface{}) string {
|
||||||
@ -141,3 +134,29 @@ func getEndpoint(name string, meta map[string]interface{}) string {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contextView struct {
|
||||||
|
Current bool
|
||||||
|
Description string
|
||||||
|
DockerEndpoint string
|
||||||
|
KubernetesEndpoint 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 {
|
||||||
|
retList[i] = contextView{
|
||||||
|
Current: c.Name == currentContext,
|
||||||
|
Description: c.Metadata.Description,
|
||||||
|
DockerEndpoint: getEndpoint("docker", c.Endpoints),
|
||||||
|
KubernetesEndpoint: getEndpoint("kubernetes", c.Endpoints),
|
||||||
|
Name: c.Name,
|
||||||
|
Type: c.Type(),
|
||||||
|
StackOrchestrator: c.Metadata.StackOrchestrator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retList
|
||||||
|
}
|
||||||
|
@ -56,7 +56,7 @@ func runInspect(ctx context.Context, id string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(j)
|
fmt.Print(j)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -36,13 +36,7 @@ type psOpts struct {
|
|||||||
all bool
|
all bool
|
||||||
quiet bool
|
quiet bool
|
||||||
json bool
|
json bool
|
||||||
}
|
format string
|
||||||
|
|
||||||
func (o psOpts) validate() error {
|
|
||||||
if o.quiet && o.json {
|
|
||||||
return errors.New(`cannot combine "quiet" and "json" options`)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PsCommand lists containers
|
// PsCommand lists containers
|
||||||
@ -59,50 +53,52 @@ func PsCommand() *cobra.Command {
|
|||||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
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().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().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") // Legacy. This is used by VSCode Docker extension
|
||||||
|
|
||||||
return cmd
|
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 {
|
func runPs(ctx context.Context, opts psOpts) error {
|
||||||
err := opts.validate()
|
err := opts.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := client.New(ctx)
|
c, err := client.New(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot connect to backend")
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err, "fetch containers")
|
return errors.Wrap(err, "fetch containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.quiet {
|
if opts.quiet {
|
||||||
for _, c := range containers {
|
for _, c := range containerList {
|
||||||
fmt.Println(c.ID)
|
fmt.Println(c.ID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.json {
|
if opts.json {
|
||||||
j, err := formatter2.ToStandardJSON(containers)
|
opts.format = formatter2.JSON
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(j)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
view := viewFromContainerList(containerList)
|
||||||
fmt.Fprintf(w, "CONTAINER ID\tIMAGE\tCOMMAND\tSTATUS\tPORTS\n")
|
return formatter2.Print(view, opts.format, os.Stdout, func(w io.Writer) {
|
||||||
format := "%s\t%s\t%s\t%s\t%s\n"
|
for _, c := range view {
|
||||||
for _, container := range containers {
|
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status,
|
||||||
fmt.Fprintf(w, format, container.ID, container.Image, container.Command, container.Status, strings.Join(formatter.PortsToStrings(container.Ports, fqdn(container)), ", "))
|
strings.Join(c.Ports, ", "))
|
||||||
}
|
}
|
||||||
|
}, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS")
|
||||||
return w.Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fqdn(container containers.Container) string {
|
func fqdn(container containers.Container) string {
|
||||||
@ -112,3 +108,25 @@ func fqdn(container containers.Container) string {
|
|||||||
}
|
}
|
||||||
return fqdn
|
return fqdn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type containerView struct {
|
||||||
|
ID string
|
||||||
|
Image string
|
||||||
|
Status string
|
||||||
|
Command 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,
|
||||||
|
Status: c.Status,
|
||||||
|
Command: c.Command,
|
||||||
|
Ports: formatter.PortsToStrings(c.Ports, fqdn(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retList
|
||||||
|
}
|
||||||
|
@ -20,13 +20,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/client"
|
"github.com/docker/compose-cli/api/client"
|
||||||
"github.com/docker/compose-cli/api/secrets"
|
"github.com/docker/compose-cli/api/secrets"
|
||||||
|
"github.com/docker/compose-cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type createSecretOptions struct {
|
type createSecretOptions struct {
|
||||||
@ -105,7 +104,12 @@ func inspectSecret() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type listSecretsOpts struct {
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
func listSecrets() *cobra.Command {
|
func listSecrets() *cobra.Command {
|
||||||
|
var opts listSecretsOpts
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
@ -115,17 +119,40 @@ func listSecrets() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
list, err := c.SecretsService().ListSecrets(cmd.Context())
|
secretsList, err := c.SecretsService().ListSecrets(cmd.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printList(os.Stdout, list)
|
view := viewFromSecretList(secretsList)
|
||||||
return nil
|
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")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
|
||||||
return cmd
|
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 {
|
type deleteSecretOptions struct {
|
||||||
recover bool
|
recover bool
|
||||||
}
|
}
|
||||||
@ -148,18 +175,3 @@ func deleteSecret() *cobra.Command {
|
|||||||
cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.")
|
cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.")
|
||||||
return cmd
|
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
|
|
||||||
}
|
|
||||||
}, "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
|
|
||||||
}
|
|
||||||
|
@ -18,43 +18,96 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/cli/cmd/mobyflags"
|
"github.com/docker/compose-cli/cli/cmd/mobyflags"
|
||||||
"github.com/docker/compose-cli/cli/mobycli"
|
"github.com/docker/compose-cli/cli/mobycli"
|
||||||
|
"github.com/docker/compose-cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const formatOpt = "format"
|
||||||
|
|
||||||
// VersionCommand command to display version
|
// VersionCommand command to display version
|
||||||
func VersionCommand(version string) *cobra.Command {
|
func VersionCommand(version string) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Show the Docker version information",
|
Short: "Show the Docker version information",
|
||||||
Args: cobra.MaximumNArgs(0),
|
Args: cobra.MaximumNArgs(0),
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
return runVersion(cmd, version)
|
runVersion(cmd, version)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// define flags for backward compatibility with com.docker.cli
|
// define flags for backward compatibility with com.docker.cli
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringP("format", "f", "", "Format the output using the given Go template")
|
flags.StringP(formatOpt, "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
|
||||||
flags.String("kubeconfig", "", "Kubernetes config file")
|
flags.String("kubeconfig", "", "Kubernetes config file")
|
||||||
mobyflags.AddMobyFlagsForRetrocompatibility(flags)
|
mobyflags.AddMobyFlagsForRetrocompatibility(flags)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVersion(cmd *cobra.Command, version string) error {
|
func runVersion(cmd *cobra.Command, version string) {
|
||||||
|
var versionString string
|
||||||
|
format := strings.ToLower(strings.ReplaceAll(cmd.Flag(formatOpt).Value.String(), " ", ""))
|
||||||
displayedVersion := strings.TrimPrefix(version, "v")
|
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.}}": // 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
// Still, technically the [] byte versionResult could be nil, just let the original command display what it has to display
|
||||||
if versionResult == nil {
|
if versionResult == nil {
|
||||||
mobycli.Exec(cmd.Root())
|
mobycli.Exec(cmd.Root())
|
||||||
return nil
|
return ""
|
||||||
}
|
}
|
||||||
var s string = string(versionResult)
|
return string(versionResult)
|
||||||
fmt.Print(strings.Replace(s, "\n Version:", "\n Cloud integration "+displayedVersion+"\n Version:", 1))
|
}
|
||||||
return nil
|
|
||||||
|
func fixedPrettyArgs(oArgs []string) []string {
|
||||||
|
args := make([]string, 0)
|
||||||
|
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 {
|
||||||
|
args := make([]string, 0)
|
||||||
|
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
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: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -20,16 +20,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/client"
|
"github.com/docker/compose-cli/api/client"
|
||||||
"github.com/docker/compose-cli/api/volumes"
|
"github.com/docker/compose-cli/api/volumes"
|
||||||
|
"github.com/docker/compose-cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type listVolumeOpts struct {
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
func listVolume() *cobra.Command {
|
func listVolume() *cobra.Command {
|
||||||
|
var opts listVolumeOpts
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Short: "list available volumes in context.",
|
Short: "list available volumes in context.",
|
||||||
@ -43,24 +47,30 @@ func listVolume() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printList(os.Stdout, vols)
|
view := viewFromVolumeList(vols)
|
||||||
return nil
|
return formatter.Print(view, opts.format, os.Stdout, func(w io.Writer) {
|
||||||
},
|
for _, vol := range view {
|
||||||
}
|
|
||||||
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)
|
_, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description)
|
||||||
}
|
}
|
||||||
}, "ID", "DESCRIPTION")
|
}, "ID", "DESCRIPTION")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&opts.format, "format", formatter.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)")
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSection(out io.Writer, printer func(io.Writer), headers ...string) {
|
type volumeView struct {
|
||||||
w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0)
|
ID string
|
||||||
_, _ = fmt.Fprintln(w, strings.Join(headers, "\t"))
|
Description string
|
||||||
printer(w)
|
}
|
||||||
_ = w.Flush()
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
2
cli/cmd/volume/testdata/volumes-out.golden
vendored
2
cli/cmd/volume/testdata/volumes-out.golden
vendored
@ -1,2 +0,0 @@
|
|||||||
ID DESCRIPTION
|
|
||||||
volume/123 volume 123
|
|
@ -112,7 +112,10 @@ func IsDefaultContextCommand(dockerCommand string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExecSilent executes a command and do redirect output to stdOut, return output
|
// ExecSilent executes a command and do redirect output to stdOut, return output
|
||||||
func ExecSilent(ctx context.Context) ([]byte, error) {
|
func ExecSilent(ctx context.Context, args ...string) ([]byte, error) {
|
||||||
cmd := exec.CommandContext(ctx, ComDockerCli, os.Args[1:]...)
|
if len(args) == 0 {
|
||||||
|
args = os.Args[1:]
|
||||||
|
}
|
||||||
|
cmd := exec.CommandContext(ctx, ComDockerCli, args...)
|
||||||
return cmd.CombinedOutput()
|
return cmd.CombinedOutput()
|
||||||
}
|
}
|
||||||
|
@ -14,26 +14,11 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package cmd
|
package formatter
|
||||||
|
|
||||||
import (
|
const (
|
||||||
"bytes"
|
// JSON is the constant for Json formats on list commands
|
||||||
"testing"
|
JSON = "json"
|
||||||
|
// PRETTY is the constant for default formats on list commands
|
||||||
"gotest.tools/v3/golden"
|
PRETTY = "pretty"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/secrets"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintList(t *testing.T) {
|
|
||||||
secrets := []secrets.Secret{
|
|
||||||
{
|
|
||||||
ID: "123",
|
|
||||||
Name: "secret123",
|
|
||||||
Description: "secret 1,2,3",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
printList(out, secrets)
|
|
||||||
golden.Assert(t, out.String(), "secrets-out.golden")
|
|
||||||
}
|
|
58
formatter/formatter.go
Normal file
58
formatter/formatter.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/docker/compose-cli/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Print prints formatted lists in different formats
|
||||||
|
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:
|
||||||
|
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 := ToJSON(obj, "", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprint(outWriter, jsonLine)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
outJSON, err := ToStandardJSON(toJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(outWriter, outJSON)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
63
formatter/formatter_test.go
Normal file
63
formatter/formatter_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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: "myName1",
|
||||||
|
Status: "myStatus1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "myName2",
|
||||||
|
Status: "myStatus2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}, "NAME", "STATUS"))
|
||||||
|
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) {
|
||||||
|
for _, t := range testList {
|
||||||
|
_, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status)
|
||||||
|
}
|
||||||
|
}, "NAME", "STATUS"))
|
||||||
|
assert.Equal(t, b.String(), `{"Name":"myName1","Status":"myStatus1"}
|
||||||
|
{"Name":"myName2","Status":"myStatus2"}
|
||||||
|
`)
|
||||||
|
}
|
@ -16,15 +16,24 @@
|
|||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
const standardIndentation = " "
|
const standardIndentation = " "
|
||||||
|
|
||||||
// ToStandardJSON return a string with the JSON representation of the interface{}
|
// ToStandardJSON return a string with the JSON representation of the interface{}
|
||||||
func ToStandardJSON(i interface{}) (string, error) {
|
func ToStandardJSON(i interface{}) (string, error) {
|
||||||
b, err := json.MarshalIndent(i, "", standardIndentation)
|
return ToJSON(i, "", standardIndentation)
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
|
||||||
}
|
// ToJSON return a string with the JSON representation of the interface{}
|
||||||
return string(b), nil
|
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
|
||||||
}
|
}
|
||||||
|
@ -14,25 +14,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package volume
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"testing"
|
"io"
|
||||||
|
"strings"
|
||||||
"gotest.tools/v3/golden"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/volumes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintList(t *testing.T) {
|
// PrintPrettySection prints a tabbed section on the writer parameter
|
||||||
secrets := []volumes.Volume{
|
func PrintPrettySection(out io.Writer, printer func(writer io.Writer), headers ...string) error {
|
||||||
{
|
w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0)
|
||||||
ID: "volume/123",
|
_, _ = fmt.Fprintln(w, strings.Join(headers, "\t"))
|
||||||
Description: "volume 123",
|
printer(w)
|
||||||
},
|
return w.Flush()
|
||||||
}
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
printList(out, secrets)
|
|
||||||
golden.Assert(t, out.String(), "volumes-out.golden")
|
|
||||||
}
|
}
|
1
go.mod
1
go.mod
@ -62,5 +62,6 @@ require (
|
|||||||
google.golang.org/protobuf v1.25.0
|
google.golang.org/protobuf v1.25.0
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/ini.v1 v1.61.0
|
gopkg.in/ini.v1 v1.61.0
|
||||||
|
gotest.tools v2.2.0+incompatible
|
||||||
gotest.tools/v3 v3.0.2
|
gotest.tools/v3 v3.0.2
|
||||||
)
|
)
|
||||||
|
1
go.sum
1
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.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/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/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=
|
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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -75,6 +75,12 @@ func TestContextDefault(t *testing.T) {
|
|||||||
t.Run("ls", func(t *testing.T) {
|
t.Run("ls", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("context", "ls")
|
res := c.RunDockerCmd("context", "ls")
|
||||||
golden.Assert(t, res.Stdout(), GoldenFile("ls-out-default"))
|
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) {
|
t.Run("inspect", func(t *testing.T) {
|
||||||
@ -416,6 +422,26 @@ func TestVersion(t *testing.T) {
|
|||||||
res.Assert(t, icmd.Expected{Out: `"Client":`})
|
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) {
|
t.Run("delegate version flag", func(t *testing.T) {
|
||||||
c.RunDockerCmd("context", "create", "example", "test-example")
|
c.RunDockerCmd("context", "create", "example", "test-example")
|
||||||
c.RunDockerCmd("context", "use", "test-example")
|
c.RunDockerCmd("context", "use", "test-example")
|
||||||
@ -440,6 +466,12 @@ func TestMockBackend(t *testing.T) {
|
|||||||
t.Run("ps", func(t *testing.T) {
|
t.Run("ps", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("ps")
|
res := c.RunDockerCmd("ps")
|
||||||
golden.Assert(t, res.Stdout(), "ps-out-example.golden")
|
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) {
|
t.Run("ps quiet", func(t *testing.T) {
|
||||||
|
1
tests/e2e/testdata/ls-out-json.golden
vendored
Normal file
1
tests/e2e/testdata/ls-out-json.golden
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"Current":true,"Description":"Current DOCKER_HOST based configuration","DockerEndpoint":"unix:///var/run/docker.sock","KubernetesEndpoint":"","Type":"moby","Name":"default","StackOrchestrator":"swarm"}
|
2
tests/e2e/testdata/ps-out-example-json.golden
vendored
Normal file
2
tests/e2e/testdata/ps-out-example-json.golden
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{"ID":"id","Image":"nginx","Status":"","Command":"","Ports":[]}
|
||||||
|
{"ID":"1234","Image":"alpine","Status":"","Command":"","Ports":[]}
|
@ -33,8 +33,8 @@ type portGroup struct {
|
|||||||
// PortsToStrings returns a human readable published ports
|
// PortsToStrings returns a human readable published ports
|
||||||
func PortsToStrings(ports []containers.Port, fqdn string) []string {
|
func PortsToStrings(ports []containers.Port, fqdn string) []string {
|
||||||
groupMap := make(map[string]*portGroup)
|
groupMap := make(map[string]*portGroup)
|
||||||
|
result := []string{}
|
||||||
var (
|
var (
|
||||||
result []string
|
|
||||||
hostMappings []string
|
hostMappings []string
|
||||||
groupMapKeys []string
|
groupMapKeys []string
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user