mirror of
https://github.com/docker/compose.git
synced 2025-07-27 07:34:10 +02:00
align docker compose ps with docker CLI to support --format
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
19f66918cc
commit
1054792b47
@ -275,7 +275,7 @@ func RunningAsStandalone() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RootCommand returns the compose command with its child commands
|
// RootCommand returns the compose command with its child commands
|
||||||
func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
|
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
|
||||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||||
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||||
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
|
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
|
||||||
@ -307,7 +307,7 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
|
|||||||
return cmd.Help()
|
return cmd.Help()
|
||||||
}
|
}
|
||||||
if version {
|
if version {
|
||||||
return versionCommand(streams).Execute()
|
return versionCommand(dockerCli).Execute()
|
||||||
}
|
}
|
||||||
_ = cmd.Help()
|
_ = cmd.Help()
|
||||||
return dockercli.StatusError{
|
return dockercli.StatusError{
|
||||||
@ -345,11 +345,11 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
|
|||||||
ansi = v
|
ansi = v
|
||||||
}
|
}
|
||||||
|
|
||||||
formatter.SetANSIMode(streams, ansi)
|
formatter.SetANSIMode(dockerCli, ansi)
|
||||||
|
|
||||||
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
|
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
|
||||||
ui.NoColor()
|
ui.NoColor()
|
||||||
formatter.SetANSIMode(streams, formatter.Never)
|
formatter.SetANSIMode(dockerCli, formatter.Never)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ansi {
|
switch ansi {
|
||||||
@ -426,26 +426,26 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.AddCommand(
|
c.AddCommand(
|
||||||
upCommand(&opts, streams, backend),
|
upCommand(&opts, dockerCli, backend),
|
||||||
downCommand(&opts, backend),
|
downCommand(&opts, backend),
|
||||||
startCommand(&opts, backend),
|
startCommand(&opts, backend),
|
||||||
restartCommand(&opts, backend),
|
restartCommand(&opts, backend),
|
||||||
stopCommand(&opts, backend),
|
stopCommand(&opts, backend),
|
||||||
psCommand(&opts, streams, backend),
|
psCommand(&opts, dockerCli, backend),
|
||||||
listCommand(streams, backend),
|
listCommand(dockerCli, backend),
|
||||||
logsCommand(&opts, streams, backend),
|
logsCommand(&opts, dockerCli, backend),
|
||||||
configCommand(&opts, streams, backend),
|
configCommand(&opts, dockerCli, backend),
|
||||||
killCommand(&opts, backend),
|
killCommand(&opts, backend),
|
||||||
runCommand(&opts, streams, backend),
|
runCommand(&opts, dockerCli, backend),
|
||||||
removeCommand(&opts, backend),
|
removeCommand(&opts, backend),
|
||||||
execCommand(&opts, streams, backend),
|
execCommand(&opts, dockerCli, backend),
|
||||||
pauseCommand(&opts, backend),
|
pauseCommand(&opts, backend),
|
||||||
unpauseCommand(&opts, backend),
|
unpauseCommand(&opts, backend),
|
||||||
topCommand(&opts, streams, backend),
|
topCommand(&opts, dockerCli, backend),
|
||||||
eventsCommand(&opts, streams, backend),
|
eventsCommand(&opts, dockerCli, backend),
|
||||||
portCommand(&opts, streams, backend),
|
portCommand(&opts, dockerCli, backend),
|
||||||
imagesCommand(&opts, streams, backend),
|
imagesCommand(&opts, dockerCli, backend),
|
||||||
versionCommand(streams),
|
versionCommand(dockerCli),
|
||||||
buildCommand(&opts, &progress, backend),
|
buildCommand(&opts, &progress, backend),
|
||||||
pushCommand(&opts, backend),
|
pushCommand(&opts, backend),
|
||||||
pullCommand(&opts, backend),
|
pullCommand(&opts, backend),
|
||||||
|
@ -19,22 +19,18 @@ package compose
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/compose/v2/cmd/formatter"
|
"github.com/docker/compose/v2/cmd/formatter"
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
|
|
||||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/go-units"
|
cliformatter "github.com/docker/cli/cli/command/formatter"
|
||||||
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type psOptions struct {
|
type psOptions struct {
|
||||||
@ -66,7 +62,7 @@ func (p *psOptions) parseFilter() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func psCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
|
func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||||
opts := psOptions{
|
opts := psOptions{
|
||||||
ProjectOptions: p,
|
ProjectOptions: p,
|
||||||
}
|
}
|
||||||
@ -77,12 +73,12 @@ func psCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cob
|
|||||||
return opts.parseFilter()
|
return opts.parseFilter()
|
||||||
},
|
},
|
||||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||||
return runPs(ctx, streams, backend, args, opts)
|
return runPs(ctx, dockerCli, backend, args, opts)
|
||||||
}),
|
}),
|
||||||
ValidArgsFunction: completeServiceNames(p),
|
ValidArgsFunction: completeServiceNames(p),
|
||||||
}
|
}
|
||||||
flags := psCmd.Flags()
|
flags := psCmd.Flags()
|
||||||
flags.StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
|
flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
|
||||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
|
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
|
||||||
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||||
@ -91,7 +87,7 @@ func psCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cob
|
|||||||
return psCmd
|
return psCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPs(ctx context.Context, streams api.Streams, backend api.Service, services []string, opts psOptions) error {
|
func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error {
|
||||||
project, name, err := opts.projectOrName(services...)
|
project, name, err := opts.projectOrName(services...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -125,38 +121,32 @@ func runPs(ctx context.Context, streams api.Streams, backend api.Service, servic
|
|||||||
|
|
||||||
if opts.Quiet {
|
if opts.Quiet {
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
fmt.Fprintln(streams.Out(), c.ID)
|
fmt.Fprintln(dockerCli.Out(), c.ID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Services {
|
if opts.Services {
|
||||||
services := []string{}
|
services := []string{}
|
||||||
for _, s := range containers {
|
for _, c := range containers {
|
||||||
if !utils.StringContains(services, s.Service) {
|
s := c.Service
|
||||||
services = append(services, s.Service)
|
if !utils.StringContains(services, s) {
|
||||||
|
services = append(services, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(streams.Out(), strings.Join(services, "\n"))
|
fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatter.Print(containers, opts.Format, streams.Out(),
|
if opts.Format == "" {
|
||||||
writer(containers),
|
opts.Format = dockerCli.ConfigFile().PsFormat
|
||||||
"NAME", "IMAGE", "COMMAND", "SERVICE", "CREATED", "STATUS", "PORTS")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func writer(containers []api.ContainerSummary) func(w io.Writer) {
|
containerCtx := cliformatter.Context{
|
||||||
return func(w io.Writer) {
|
Output: dockerCli.Out(),
|
||||||
for _, container := range containers {
|
Format: formatter.NewContainerFormat(opts.Format, opts.Quiet, false),
|
||||||
ports := displayablePorts(container)
|
|
||||||
createdAt := time.Unix(container.Created, 0)
|
|
||||||
created := units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
|
||||||
status := container.Status
|
|
||||||
command := formatter2.Ellipsis(container.Command, 20)
|
|
||||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", container.Name, container.Image, strconv.Quote(command), container.Service, created, status, ports)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return formatter.ContainerWrite(containerCtx, containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
|
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
|
||||||
@ -177,21 +167,3 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayablePorts(c api.ContainerSummary) string {
|
|
||||||
if c.Publishers == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ports := make([]types.Port, len(c.Publishers))
|
|
||||||
for i, pub := range c.Publishers {
|
|
||||||
ports[i] = types.Port{
|
|
||||||
IP: pub.URL,
|
|
||||||
PrivatePort: uint16(pub.TargetPort),
|
|
||||||
PublicPort: uint16(pub.PublishedPort),
|
|
||||||
Type: pub.Protocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatter2.DisplayablePorts(ports)
|
|
||||||
}
|
|
||||||
|
@ -18,11 +18,11 @@ package compose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/mocks"
|
"github.com/docker/compose/v2/pkg/mocks"
|
||||||
@ -69,7 +69,11 @@ func TestPsTable(t *testing.T) {
|
|||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
|
|
||||||
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
|
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
|
||||||
err = runPs(ctx, stream{out: streams.NewOut(f)}, backend, nil, opts)
|
stdout := streams.NewOut(f)
|
||||||
|
cli := mocks.NewMockCli(ctrl)
|
||||||
|
cli.EXPECT().Out().Return(stdout).AnyTimes()
|
||||||
|
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
|
||||||
|
err = runPs(ctx, cli, backend, nil, opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = f.Seek(0, 0)
|
_, err = f.Seek(0, 0)
|
||||||
@ -80,21 +84,3 @@ func TestPsTable(t *testing.T) {
|
|||||||
|
|
||||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||||
}
|
}
|
||||||
|
|
||||||
type stream struct {
|
|
||||||
out *streams.Out
|
|
||||||
err io.Writer
|
|
||||||
in *streams.In
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) Out() *streams.Out {
|
|
||||||
return s.out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) Err() io.Writer {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) In() *streams.In {
|
|
||||||
return s.in
|
|
||||||
}
|
|
||||||
|
196
cmd/formatter/container.go
Normal file
196
cmd/formatter/container.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultContainerTableFormat = "table {{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Service}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
|
||||||
|
nameHeader = "NAME"
|
||||||
|
serviceHeader = "SERVICE"
|
||||||
|
commandHeader = "COMMAND"
|
||||||
|
runningForHeader = "CREATED"
|
||||||
|
mountsHeader = "MOUNTS"
|
||||||
|
localVolumes = "LOCAL VOLUMES"
|
||||||
|
networksHeader = "NETWORKS"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewContainerFormat returns a Format for rendering using a Context
|
||||||
|
func NewContainerFormat(source string, quiet bool, size bool) formatter.Format {
|
||||||
|
switch source {
|
||||||
|
case formatter.TableFormatKey, "": // table formatting is the default if none is set.
|
||||||
|
if quiet {
|
||||||
|
return formatter.DefaultQuietFormat
|
||||||
|
}
|
||||||
|
format := defaultContainerTableFormat
|
||||||
|
if size {
|
||||||
|
format += `\t{{.Size}}`
|
||||||
|
}
|
||||||
|
return formatter.Format(format)
|
||||||
|
case formatter.RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `container_id: {{.ID}}`
|
||||||
|
}
|
||||||
|
format := `container_id: {{.ID}}
|
||||||
|
image: {{.Image}}
|
||||||
|
command: {{.Command}}
|
||||||
|
created_at: {{.CreatedAt}}
|
||||||
|
state: {{- pad .State 1 0}}
|
||||||
|
status: {{- pad .Status 1 0}}
|
||||||
|
names: {{.Names}}
|
||||||
|
labels: {{- pad .Labels 1 0}}
|
||||||
|
ports: {{- pad .Ports 1 0}}
|
||||||
|
`
|
||||||
|
if size {
|
||||||
|
format += `size: {{.Size}}\n`
|
||||||
|
}
|
||||||
|
return formatter.Format(format)
|
||||||
|
default: // custom format
|
||||||
|
if quiet {
|
||||||
|
return formatter.DefaultQuietFormat
|
||||||
|
}
|
||||||
|
return formatter.Format(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerWrite renders the context for a list of containers
|
||||||
|
func ContainerWrite(ctx formatter.Context, containers []api.ContainerSummary) error {
|
||||||
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
|
for _, container := range containers {
|
||||||
|
err := format(&ContainerContext{trunc: ctx.Trunc, c: container})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ctx.Write(NewContainerContext(), render)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerContext is a struct used for rendering a list of containers in a Go template.
|
||||||
|
type ContainerContext struct {
|
||||||
|
formatter.HeaderContext
|
||||||
|
trunc bool
|
||||||
|
c api.ContainerSummary
|
||||||
|
|
||||||
|
// FieldsUsed is used in the pre-processing step to detect which fields are
|
||||||
|
// used in the template. It's currently only used to detect use of the .Size
|
||||||
|
// field which (if used) automatically sets the '--size' option when making
|
||||||
|
// the API call.
|
||||||
|
FieldsUsed map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContainerContext creates a new context for rendering containers
|
||||||
|
func NewContainerContext() *ContainerContext {
|
||||||
|
containerCtx := ContainerContext{}
|
||||||
|
containerCtx.Header = formatter.SubHeaderContext{
|
||||||
|
"ID": formatter.ContainerIDHeader,
|
||||||
|
"Name": nameHeader,
|
||||||
|
"Service": serviceHeader,
|
||||||
|
"Image": formatter.ImageHeader,
|
||||||
|
"Command": commandHeader,
|
||||||
|
"CreatedAt": formatter.CreatedAtHeader,
|
||||||
|
"RunningFor": runningForHeader,
|
||||||
|
"Ports": formatter.PortsHeader,
|
||||||
|
"State": formatter.StateHeader,
|
||||||
|
"Status": formatter.StatusHeader,
|
||||||
|
"Size": formatter.SizeHeader,
|
||||||
|
"Labels": formatter.LabelsHeader,
|
||||||
|
}
|
||||||
|
return &containerCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON makes ContainerContext implement json.Marshaler
|
||||||
|
func (c *ContainerContext) MarshalJSON() ([]byte, error) {
|
||||||
|
return formatter.MarshalJSON(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the container's ID as a string. Depending on the `--no-trunc`
|
||||||
|
// option being set, the full or truncated ID is returned.
|
||||||
|
func (c *ContainerContext) ID() string {
|
||||||
|
if c.trunc {
|
||||||
|
return stringid.TruncateID(c.c.ID)
|
||||||
|
}
|
||||||
|
return c.c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Name() string {
|
||||||
|
return c.c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Service() string {
|
||||||
|
return c.c.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Image() string {
|
||||||
|
return c.c.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Command() string {
|
||||||
|
return c.c.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) CreatedAt() string {
|
||||||
|
return time.Unix(c.c.Created, 0).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) RunningFor() string {
|
||||||
|
createdAt := time.Unix(c.c.Created, 0)
|
||||||
|
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) ExitCode() int {
|
||||||
|
return c.c.ExitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) State() string {
|
||||||
|
return c.c.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Status() string {
|
||||||
|
return c.c.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Health() string {
|
||||||
|
return c.c.Health
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Publishers() api.PortPublishers {
|
||||||
|
return c.c.Publishers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContainerContext) Ports() string {
|
||||||
|
var ports []types.Port
|
||||||
|
for _, publisher := range c.c.Publishers {
|
||||||
|
ports = append(ports, types.Port{
|
||||||
|
IP: publisher.URL,
|
||||||
|
PrivatePort: uint16(publisher.TargetPort),
|
||||||
|
PublicPort: uint16(publisher.PublishedPort),
|
||||||
|
Type: publisher.Protocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return formatter.DisplayablePorts(ports)
|
||||||
|
}
|
@ -6,11 +6,11 @@ List containers
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------|:--------------|:--------|:--------------------------------------------------------------------------------------------------------------|
|
|:----------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
|
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
|
||||||
| `--dry-run` | | | Execute command in dry run mode |
|
| `--dry-run` | | | Execute command in dry run mode |
|
||||||
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
|
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
|
||||||
| [`--format`](#format) | `string` | `table` | Format the output. Values: [table \| json] |
|
| [`--format`](#format) | `string` | `table` | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||||
| `-q`, `--quiet` | | | Only display IDs |
|
| `-q`, `--quiet` | | | Only display IDs |
|
||||||
| `--services` | | | Display services |
|
| `--services` | | | Display services |
|
||||||
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
|
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
|
||||||
|
@ -46,7 +46,13 @@ options:
|
|||||||
- option: format
|
- option: format
|
||||||
value_type: string
|
value_type: string
|
||||||
default_value: table
|
default_value: table
|
||||||
description: 'Format the output. Values: [table | json]'
|
description: |-
|
||||||
|
Format output using a custom template:
|
||||||
|
'table': Print output in table format with column headers (default)
|
||||||
|
'table TEMPLATE': Print output in table format using the given Go template
|
||||||
|
'json': Print in JSON format
|
||||||
|
'TEMPLATE': Print output using the given Go template.
|
||||||
|
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates
|
||||||
details_url: '#format'
|
details_url: '#format'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
hidden: false
|
hidden: false
|
||||||
|
@ -392,7 +392,7 @@ type PortPublisher struct {
|
|||||||
type ContainerSummary struct {
|
type ContainerSummary struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Image any
|
Image string
|
||||||
Command string
|
Command string
|
||||||
Project string
|
Project string
|
||||||
Service string
|
Service string
|
||||||
|
@ -29,11 +29,10 @@ import (
|
|||||||
func RequireServiceState(t testing.TB, cli *CLI, service string, state string) {
|
func RequireServiceState(t testing.TB, cli *CLI, service string, state string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
psRes := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
|
psRes := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
|
||||||
var psOut []map[string]interface{}
|
var svc map[string]interface{}
|
||||||
require.NoError(t, json.Unmarshal([]byte(psRes.Stdout()), &psOut),
|
require.NoError(t, json.Unmarshal([]byte(psRes.Stdout()), &svc),
|
||||||
"Invalid `compose ps` JSON output")
|
"Invalid `compose ps` JSON output")
|
||||||
|
|
||||||
for _, svc := range psOut {
|
|
||||||
require.Equal(t, service, svc["Service"],
|
require.Equal(t, service, svc["Service"],
|
||||||
"Found ps output for unexpected service")
|
"Found ps output for unexpected service")
|
||||||
require.Equalf(t,
|
require.Equalf(t,
|
||||||
@ -42,5 +41,4 @@ func RequireServiceState(t testing.TB, cli *CLI, service string, state string) {
|
|||||||
"Service %q (%s) not in expected state",
|
"Service %q (%s) not in expected state",
|
||||||
service, svc["Name"],
|
service, svc["Name"],
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -361,13 +361,14 @@ func IsHealthy(service string) func(res *icmd.Result) bool {
|
|||||||
Health string `json:"health"`
|
Health string `json:"health"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := []state{}
|
decoder := json.NewDecoder(strings.NewReader(res.Stdout()))
|
||||||
err := json.Unmarshal([]byte(res.Stdout()), &ps)
|
for decoder.More() {
|
||||||
|
ps := state{}
|
||||||
|
err := decoder.Decode(&ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, state := range ps {
|
if ps.Name == service && ps.Health == "healthy" {
|
||||||
if state.Name == service && state.Health == "healthy" {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,16 +138,14 @@ func urlForService(t testing.TB, cli *CLI, service string, targetPort int) strin
|
|||||||
func publishedPortForService(t testing.TB, cli *CLI, service string, targetPort int) int {
|
func publishedPortForService(t testing.TB, cli *CLI, service string, targetPort int) int {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
res := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
|
res := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
|
||||||
var psOut []struct {
|
var svc struct {
|
||||||
Publishers []struct {
|
Publishers []struct {
|
||||||
TargetPort int
|
TargetPort int
|
||||||
PublishedPort int
|
PublishedPort int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require.NoError(t, json.Unmarshal([]byte(res.Stdout()), &psOut),
|
require.NoError(t, json.Unmarshal([]byte(res.Stdout()), &svc),
|
||||||
"Failed to parse `%s` output", res.Cmd.String())
|
"Failed to parse `%s` output", res.Cmd.String())
|
||||||
require.Len(t, psOut, 1, "Expected exactly 1 service")
|
|
||||||
svc := psOut[0]
|
|
||||||
for _, pp := range svc.Publishers {
|
for _, pp := range svc.Publishers {
|
||||||
if pp.TargetPort == targetPort {
|
if pp.TargetPort == targetPort {
|
||||||
return pp.PublishedPort
|
return pp.PublishedPort
|
||||||
|
@ -63,8 +63,12 @@ func TestPs(t *testing.T) {
|
|||||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps",
|
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps",
|
||||||
"--format", "json")
|
"--format", "json")
|
||||||
var output []api.ContainerSummary
|
var output []api.ContainerSummary
|
||||||
err := json.Unmarshal([]byte(res.Stdout()), &output)
|
dec := json.NewDecoder(strings.NewReader(res.Stdout()))
|
||||||
require.NoError(t, err, "Failed to unmarshal ps JSON output")
|
for dec.More() {
|
||||||
|
var s api.ContainerSummary
|
||||||
|
require.NoError(t, dec.Decode(&s), "Failed to unmarshal ps JSON output")
|
||||||
|
output = append(output, s)
|
||||||
|
}
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
assert.Equal(t, 2, len(output))
|
assert.Equal(t, 2, len(output))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user