don't assume os.Stdout and rely on dockerCLI.streams

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2022-12-15 09:35:26 +01:00 committed by Nicolas De loof
parent dacf24374d
commit 24f83271f2
21 changed files with 138 additions and 104 deletions

View File

@ -73,7 +73,7 @@ var printerModes = []string{
buildx.PrinterModeQuiet,
}
func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func buildCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := buildOptions{
ProjectOptions: p,
}
@ -82,7 +82,7 @@ func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
Short: "Build or rebuild services",
PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.memory != "" {
fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
fmt.Fprintln(streams.Err(), "WARNING --memory is ignored as not supported in buildkit.")
}
if opts.quiet {
opts.progress = buildx.PrinterModeQuiet

View File

@ -31,7 +31,6 @@ import (
"github.com/docker/buildx/util/logutil"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -243,7 +242,7 @@ func RunningAsStandalone() bool {
}
// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
func RootCommand(streams api.Streams, backend api.Service) *cobra.Command { //nolint:gocyclo
// 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"
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
@ -305,7 +304,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
if verbose {
logrus.SetLevel(logrus.TraceLevel)
}
formatter.SetANSIMode(ansi)
formatter.SetANSIMode(streams, ansi)
switch ansi {
case "never":
progress.Mode = progress.ModePlain
@ -333,27 +332,27 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
}
c.AddCommand(
upCommand(&opts, backend),
upCommand(&opts, streams, backend),
downCommand(&opts, backend),
startCommand(&opts, backend),
restartCommand(&opts, backend),
stopCommand(&opts, backend),
psCommand(&opts, backend),
listCommand(backend),
logsCommand(&opts, backend),
convertCommand(&opts, backend),
psCommand(&opts, streams, backend),
listCommand(streams, backend),
logsCommand(&opts, streams, backend),
convertCommand(&opts, streams, backend),
killCommand(&opts, backend),
runCommand(&opts, dockerCli, backend),
runCommand(&opts, streams, backend),
removeCommand(&opts, backend),
execCommand(&opts, dockerCli, backend),
execCommand(&opts, streams, backend),
pauseCommand(&opts, backend),
unpauseCommand(&opts, backend),
topCommand(&opts, backend),
eventsCommand(&opts, backend),
portCommand(&opts, backend),
imagesCommand(&opts, backend),
topCommand(&opts, streams, backend),
eventsCommand(&opts, streams, backend),
portCommand(&opts, streams, backend),
imagesCommand(&opts, streams, backend),
versionCommand(),
buildCommand(&opts, backend),
buildCommand(&opts, streams, backend),
pushCommand(&opts, backend),
pullCommand(&opts, backend),
createCommand(&opts, backend),

View File

@ -50,7 +50,7 @@ type convertOptions struct {
noConsistency bool
}
func convertCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func convertCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := convertOptions{
ProjectOptions: p,
}
@ -73,22 +73,22 @@ func convertCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.services {
return runServices(opts)
return runServices(streams, opts)
}
if opts.volumes {
return runVolumes(opts)
return runVolumes(streams, opts)
}
if opts.hash != "" {
return runHash(opts)
return runHash(streams, opts)
}
if opts.profiles {
return runProfiles(opts, args)
return runProfiles(streams, opts, args)
}
if opts.images {
return runConfigImages(opts, args)
return runConfigImages(streams, opts, args)
}
return runConvert(ctx, backend, opts, args)
return runConvert(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -110,7 +110,7 @@ func convertCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return cmd
}
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
func runConvert(ctx context.Context, streams api.Streams, backend api.Service, opts convertOptions, services []string) error {
var content []byte
project, err := opts.ToProject(services,
cli.WithInterpolation(!opts.noInterpolate),
@ -139,7 +139,7 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
return nil
}
var out io.Writer = os.Stdout
var out io.Writer = streams.Out()
if opts.Output != "" && len(content) > 0 {
file, err := os.Create(opts.Output)
if err != nil {
@ -151,29 +151,29 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
return err
}
func runServices(opts convertOptions) error {
func runServices(streams api.Streams, opts convertOptions) error {
project, err := opts.ToProject(nil)
if err != nil {
return err
}
return project.WithServices(project.ServiceNames(), func(s types.ServiceConfig) error {
fmt.Println(s.Name)
fmt.Fprintln(streams.Out(), s.Name)
return nil
})
}
func runVolumes(opts convertOptions) error {
func runVolumes(streams api.Streams, opts convertOptions) error {
project, err := opts.ToProject(nil)
if err != nil {
return err
}
for n := range project.Volumes {
fmt.Println(n)
fmt.Fprintln(streams.Out(), n)
}
return nil
}
func runHash(opts convertOptions) error {
func runHash(streams api.Streams, opts convertOptions) error {
var services []string
if opts.hash != "*" {
services = append(services, strings.Split(opts.hash, ",")...)
@ -187,12 +187,12 @@ func runHash(opts convertOptions) error {
if err != nil {
return err
}
fmt.Printf("%s %s\n", s.Name, hash)
fmt.Fprintf(streams.Out(), "%s %s\n", s.Name, hash)
}
return nil
}
func runProfiles(opts convertOptions, services []string) error {
func runProfiles(streams api.Streams, opts convertOptions, services []string) error {
set := map[string]struct{}{}
project, err := opts.ToProject(services)
if err != nil {
@ -209,21 +209,21 @@ func runProfiles(opts convertOptions, services []string) error {
}
sort.Strings(profiles)
for _, p := range profiles {
fmt.Println(p)
fmt.Fprintln(streams.Out(), p)
}
return nil
}
func runConfigImages(opts convertOptions, services []string) error {
func runConfigImages(streams api.Streams, opts convertOptions, services []string) error {
project, err := opts.ToProject(services)
if err != nil {
return err
}
for _, s := range project.Services {
if s.Image != "" {
fmt.Println(s.Image)
fmt.Fprintln(streams.Out(), s.Image)
} else {
fmt.Printf("%s%s%s\n", project.Name, api.Separator, s.Name)
fmt.Fprintf(streams.Out(), "%s%s%s\n", project.Name, api.Separator, s.Name)
}
}
return nil

View File

@ -31,7 +31,7 @@ type eventsOpts struct {
json bool
}
func eventsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func eventsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := eventsOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@ -41,7 +41,7 @@ func eventsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
Use: "events [OPTIONS] [SERVICE...]",
Short: "Receive real time events from containers.",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, backend, opts, args)
return runEvents(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -50,7 +50,7 @@ func eventsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return cmd
}
func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error {
func runEvents(ctx context.Context, streams api.Streams, backend api.Service, opts eventsOpts, services []string) error {
name, err := opts.toProjectName()
if err != nil {
return err
@ -71,9 +71,9 @@ func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, servic
if err != nil {
return err
}
fmt.Println(string(marshal))
fmt.Fprintln(streams.Out(), string(marshal))
} else {
fmt.Println(event)
fmt.Fprintln(streams.Out(), event)
}
return nil
},

View File

@ -21,7 +21,6 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/spf13/cobra"
@ -43,7 +42,7 @@ type execOpts struct {
interactive bool
}
func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := execOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@ -69,7 +68,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"os"
"sort"
"strings"
@ -39,7 +38,7 @@ type imageOptions struct {
Format string
}
func imagesCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func imagesCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := imageOptions{
ProjectOptions: p,
}
@ -47,7 +46,7 @@ func imagesCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
Use: "images [OPTIONS] [SERVICE...]",
Short: "List images used by the created containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, backend, opts, args)
return runImages(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -56,7 +55,7 @@ func imagesCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return imgCmd
}
func runImages(ctx context.Context, backend api.Service, opts imageOptions, services []string) error {
func runImages(ctx context.Context, streams api.Streams, backend api.Service, opts imageOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
@ -81,7 +80,7 @@ func runImages(ctx context.Context, backend api.Service, opts imageOptions, serv
}
}
for _, img := range ids {
fmt.Println(img)
fmt.Fprintln(streams.Out(), img)
}
return nil
}
@ -90,7 +89,7 @@ func runImages(ctx context.Context, backend api.Service, opts imageOptions, serv
return images[i].ContainerName < images[j].ContainerName
})
return formatter.Print(images, opts.Format, os.Stdout,
return formatter.Print(images, opts.Format, streams.Out(),
func(w io.Writer) {
for _, img := range images {
id := stringid.TruncateID(img.ID)

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/docker/compose/v2/cmd/formatter"
@ -38,13 +37,13 @@ type lsOptions struct {
Filter opts.FilterOpt
}
func listCommand(backend api.Service) *cobra.Command {
func listCommand(streams api.Streams, backend api.Service) *cobra.Command {
lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
lsCmd := &cobra.Command{
Use: "ls [OPTIONS]",
Short: "List running compose projects",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runList(ctx, backend, lsOpts)
return runList(ctx, streams, backend, lsOpts)
}),
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion(),
@ -61,7 +60,7 @@ var acceptedListFilters = map[string]bool{
"name": true,
}
func runList(ctx context.Context, backend api.Service, lsOpts lsOptions) error {
func runList(ctx context.Context, streams api.Streams, backend api.Service, lsOpts lsOptions) error {
filters := lsOpts.Filter.Value()
err := filters.Validate(acceptedListFilters)
if err != nil {
@ -74,7 +73,7 @@ func runList(ctx context.Context, backend api.Service, lsOpts lsOptions) error {
}
if lsOpts.Quiet {
for _, s := range stackList {
fmt.Println(s.Name)
fmt.Fprintln(streams.Out(), s.Name)
}
return nil
}
@ -91,7 +90,7 @@ func runList(ctx context.Context, backend api.Service, lsOpts lsOptions) error {
}
view := viewFromStackList(stackList)
return formatter.Print(view, lsOpts.Format, os.Stdout, func(w io.Writer) {
return formatter.Print(view, lsOpts.Format, streams.Out(), func(w io.Writer) {
for _, stack := range view {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
}

View File

@ -18,7 +18,6 @@ package compose
import (
"context"
"os"
"github.com/spf13/cobra"
@ -38,7 +37,7 @@ type logsOptions struct {
timestamps bool
}
func logsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func logsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := logsOptions{
ProjectOptions: p,
}
@ -46,7 +45,7 @@ func logsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
Use: "logs [OPTIONS] [SERVICE...]",
Short: "View output from containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runLogs(ctx, backend, opts, args)
return runLogs(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -61,12 +60,12 @@ func logsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return logsCmd
}
func runLogs(ctx context.Context, backend api.Service, opts logsOptions, services []string) error {
func runLogs(ctx context.Context, streams api.Streams, backend api.Service, opts logsOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}
consumer := formatter.NewLogConsumer(ctx, os.Stdout, os.Stderr, !opts.noColor, !opts.noPrefix, false)
consumer := formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !opts.noColor, !opts.noPrefix, false)
return backend.Logs(ctx, name, consumer, api.LogOptions{
Project: project,
Services: services,

View File

@ -34,7 +34,7 @@ type portOptions struct {
index int
}
func portCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := portOptions{
ProjectOptions: p,
}
@ -52,7 +52,7 @@ func portCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPort(ctx, backend, opts, args[0])
return runPort(ctx, streams, backend, opts, args[0])
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -61,7 +61,7 @@ func portCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return cmd
}
func runPort(ctx context.Context, backend api.Service, opts portOptions, service string) error {
func runPort(ctx context.Context, streams api.Streams, backend api.Service, opts portOptions, service string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
@ -74,6 +74,6 @@ func runPort(ctx context.Context, backend api.Service, opts portOptions, service
return err
}
fmt.Printf("%s:%d\n", ip, port)
fmt.Fprintf(streams.Out(), "%s:%d\n", ip, port)
return nil
}

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
@ -67,7 +66,7 @@ func (p *psOptions) parseFilter() error {
return nil
}
func psCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func psCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := psOptions{
ProjectOptions: p,
}
@ -78,7 +77,7 @@ func psCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return opts.parseFilter()
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPs(ctx, backend, args, opts)
return runPs(ctx, streams, backend, args, opts)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -92,7 +91,7 @@ func psCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
return psCmd
}
func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error {
func runPs(ctx context.Context, streams api.Streams, backend api.Service, services []string, opts psOptions) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
@ -126,7 +125,7 @@ SERVICES:
if opts.Quiet {
for _, c := range containers {
fmt.Println(c.ID)
fmt.Fprintln(streams.Out(), c.ID)
}
return nil
}
@ -138,11 +137,11 @@ SERVICES:
services = append(services, s.Service)
}
}
fmt.Println(strings.Join(services, "\n"))
fmt.Fprintln(streams.Out(), strings.Join(services, "\n"))
return nil
}
return formatter.Print(containers, opts.Format, os.Stdout,
return formatter.Print(containers, opts.Format, streams.Out(),
writer(containers),
"NAME", "IMAGE", "COMMAND", "SERVICE", "CREATED", "STATUS", "PORTS")
}

View File

@ -18,10 +18,12 @@ package compose
import (
"context"
"io"
"os"
"path/filepath"
"testing"
"github.com/docker/cli/cli/streams"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/golang/mock/gomock"
@ -30,10 +32,6 @@ import (
func TestPsTable(t *testing.T) {
ctx := context.Background()
origStdout := os.Stdout
t.Cleanup(func() {
os.Stdout = origStdout
})
dir := t.TempDir()
out := filepath.Join(dir, "output.txt")
f, err := os.Create(out)
@ -42,7 +40,6 @@ func TestPsTable(t *testing.T) {
}
defer func() { _ = f.Close() }()
os.Stdout = f
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@ -72,7 +69,7 @@ func TestPsTable(t *testing.T) {
}).AnyTimes()
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
err = runPs(ctx, backend, nil, opts)
err = runPs(ctx, stream{out: streams.NewOut(f)}, backend, nil, opts)
assert.NoError(t, err)
_, err = f.Seek(0, 0)
@ -83,3 +80,21 @@ func TestPsTable(t *testing.T) {
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
}

View File

@ -24,7 +24,6 @@ import (
cgo "github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -108,7 +107,7 @@ func (opts runOptions) apply(project *types.Project) error {
return nil
}
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := runOptions{
composeOptions: &composeOptions{
ProjectOptions: p,
@ -151,7 +150,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")

View File

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
@ -34,7 +33,7 @@ type topOptions struct {
*ProjectOptions
}
func topCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func topCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := topOptions{
ProjectOptions: p,
}
@ -42,14 +41,14 @@ func topCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
Use: "top [SERVICES...]",
Short: "Display the running processes",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runTop(ctx, backend, opts, args)
return runTop(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(p),
}
return topCmd
}
func runTop(ctx context.Context, backend api.Service, opts topOptions, services []string) error {
func runTop(ctx context.Context, streams api.Streams, backend api.Service, opts topOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
@ -64,8 +63,8 @@ func runTop(ctx context.Context, backend api.Service, opts topOptions, services
})
for _, container := range containers {
fmt.Printf("%s\n", container.Name)
err := psPrinter(os.Stdout, func(w io.Writer) {
fmt.Fprintf(streams.Out(), "%s\n", container.Name)
err := psPrinter(streams.Out(), func(w io.Writer) {
for _, proc := range container.Processes {
info := []interface{}{}
for _, p := range proc {

View File

@ -19,7 +19,6 @@ package compose
import (
"context"
"fmt"
"os"
"strconv"
"strings"
@ -87,7 +86,7 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
return nil
}
func upCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
up := upOptions{}
create := createOptions{}
upCmd := &cobra.Command{
@ -102,7 +101,7 @@ func upCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
if create.ignoreOrphans && create.removeOrphans {
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
}
return runUp(ctx, backend, create, up, project, services)
return runUp(ctx, streams, backend, create, up, project, services)
}),
ValidArgsFunction: completeServiceNames(p),
}
@ -158,7 +157,7 @@ func validateFlags(up *upOptions, create *createOptions) error {
return nil
}
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
func runUp(ctx context.Context, streams api.Streams, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
if len(project.Services) == 0 {
return fmt.Errorf("no service selected")
}
@ -172,7 +171,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
var consumer api.LogConsumer
if !upOptions.Detach {
consumer = formatter.NewLogConsumer(ctx, os.Stdout, os.Stderr, !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
}
attachTo := services

View File

@ -18,10 +18,9 @@ package formatter
import (
"fmt"
"os"
"strconv"
"github.com/mattn/go-isatty"
"github.com/docker/compose/v2/pkg/api"
)
var names = []string{
@ -47,20 +46,20 @@ const (
)
// SetANSIMode configure formatter for colored output on ANSI-compliant console
func SetANSIMode(ansi string) {
if !useAnsi(ansi) {
func SetANSIMode(streams api.Streams, ansi string) {
if !useAnsi(streams, ansi) {
nextColor = func() colorFunc {
return monochrome
}
}
}
func useAnsi(ansi string) bool {
func useAnsi(streams api.Streams, ansi string) bool {
switch ansi {
case Always:
return true
case Auto:
return isatty.IsTerminal(os.Stdout.Fd())
return streams.Out().IsTerminal()
}
return false
}

2
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/golang/mock v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/mattn/go-isatty v0.0.16
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12
github.com/moby/buildkit v0.10.4 // replaced; see replace rule for actual version
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f

29
pkg/api/io.go Normal file
View File

@ -0,0 +1,29 @@
/*
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 api
import (
"io"
"github.com/docker/cli/cli/streams"
)
type Streams interface {
Out() *streams.Out
Err() io.Writer
In() *streams.In
}

View File

@ -48,7 +48,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
names = append(names, getContainerNameWithoutProject(c))
}
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
fmt.Fprintf(s.stdout(), "Attaching to %s\n", strings.Join(names, ", "))
for _, container := range containers {
err := s.attachContainer(ctx, container, listener)

View File

@ -94,7 +94,6 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
if cascadeStop {
if !aborting {
aborting = true
fmt.Println("Aborting on container exit...")
err := stopFn()
if err != nil {
return 0, err

View File

@ -59,7 +59,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
}
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
if options.Force {
fmt.Println(msg)
fmt.Fprintln(s.stdout(), msg)
} else {
confirm, err := prompt.User{}.Confirm(msg, false)
if err != nil {

View File

@ -55,6 +55,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
stopFunc := func() error {
fmt.Fprintln(s.stderr(), "Aborting on container exit...")
ctx := context.Background()
return progress.Run(ctx, func(ctx context.Context) error {
go func() {
@ -74,7 +75,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
go func() {
<-signalChan
printer.Cancel()
fmt.Println("Gracefully stopping... (press Ctrl+C again to force)")
fmt.Fprintln(s.stderr(), "Gracefully stopping... (press Ctrl+C again to force)")
stopFunc() //nolint:errcheck
}()