cli: option to write status messages on stdout (#10549)

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De loof 2023-05-11 18:45:00 +02:00 committed by GitHub
parent 0363d9260a
commit a14abb9044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 44 additions and 26 deletions

View File

@ -52,7 +52,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
_, err := s.build(ctx, project, options) _, err := s.build(ctx, project, options)
return err return err
}, s.stderr(), "Building") }, s.stdinfo(), "Building")
} }
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo

View File

@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"strconv" "strconv"
"strings" "strings"
@ -40,6 +41,15 @@ import (
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
) )
var stdioToStdout bool
func init() {
out, ok := os.LookupEnv("COMPOSE_STATUS_STDOUT")
if ok {
stdioToStdout, _ = strconv.ParseBool(out)
}
}
// NewComposeService create a local implementation of the compose.Service API // NewComposeService create a local implementation of the compose.Service API
func NewComposeService(dockerCli command.Cli) api.Service { func NewComposeService(dockerCli command.Cli) api.Service {
return &composeService{ return &composeService{
@ -97,6 +107,13 @@ func (s *composeService) stderr() io.Writer {
return s.dockerCli.Err() return s.dockerCli.Err()
} }
func (s *composeService) stdinfo() io.Writer {
if stdioToStdout {
return s.dockerCli.Out()
}
return s.dockerCli.Err()
}
func getCanonicalContainerName(c moby.Container) string { func getCanonicalContainerName(c moby.Container) string {
if len(c.Names) == 0 { if len(c.Names) == 0 {
// corner case, sometime happens on removal. return short ID as a safeguard value // corner case, sometime happens on removal. return short ID as a safeguard value

View File

@ -46,7 +46,7 @@ const (
func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error { func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.copy(ctx, projectName, options) return s.copy(ctx, projectName, options)
}, s.stderr(), "Copying") }, s.stdinfo(), "Copying")
} }
func (s *composeService) copy(ctx context.Context, projectName string, options api.CopyOptions) error { func (s *composeService) copy(ctx context.Context, projectName string, options api.CopyOptions) error {

View File

@ -51,7 +51,7 @@ import (
func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error { func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.create(ctx, project, options) return s.create(ctx, project, options)
}, s.stderr(), "Creating") }, s.stdinfo(), "Creating")
} }
func (s *composeService) create(ctx context.Context, project *types.Project, options api.CreateOptions) error { func (s *composeService) create(ctx context.Context, project *types.Project, options api.CreateOptions) error {

View File

@ -41,7 +41,7 @@ type downOp func() error
func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error { func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
return progress.Run(ctx, func(ctx context.Context) error { return progress.Run(ctx, func(ctx context.Context) error {
return s.down(ctx, strings.ToLower(projectName), options) return s.down(ctx, strings.ToLower(projectName), options)
}, s.stderr()) }, s.stdinfo())
} }
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error { func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {

View File

@ -31,7 +31,7 @@ import (
func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error { func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.kill(ctx, strings.ToLower(projectName), options) return s.kill(ctx, strings.ToLower(projectName), options)
}, s.stderr(), "Killing") }, s.stdinfo(), "Killing")
} }
func (s *composeService) kill(ctx context.Context, projectName string, options api.KillOptions) error { func (s *composeService) kill(ctx context.Context, projectName string, options api.KillOptions) error {
@ -57,7 +57,8 @@ func (s *composeService) kill(ctx context.Context, projectName string, options a
containers = containers.filter(isService(project.ServiceNames()...)) containers = containers.filter(isService(project.ServiceNames()...))
} }
if len(containers) == 0 { if len(containers) == 0 {
fmt.Fprintf(s.stderr(), "no container to kill") fmt.Fprintf(s.stdinfo(), "no container to kill")
return nil
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)

View File

@ -30,7 +30,7 @@ import (
func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error { func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.pause(ctx, strings.ToLower(projectName), options) return s.pause(ctx, strings.ToLower(projectName), options)
}, s.stderr(), "Pausing") }, s.stdinfo(), "Pausing")
} }
func (s *composeService) pause(ctx context.Context, projectName string, options api.PauseOptions) error { func (s *composeService) pause(ctx context.Context, projectName string, options api.PauseOptions) error {
@ -62,7 +62,7 @@ func (s *composeService) pause(ctx context.Context, projectName string, options
func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error { func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error {
return progress.Run(ctx, func(ctx context.Context) error { return progress.Run(ctx, func(ctx context.Context) error {
return s.unPause(ctx, strings.ToLower(projectName), options) return s.unPause(ctx, strings.ToLower(projectName), options)
}, s.stderr()) }, s.stdinfo())
} }
func (s *composeService) unPause(ctx context.Context, projectName string, options api.PauseOptions) error { func (s *composeService) unPause(ctx context.Context, projectName string, options api.PauseOptions) error {

View File

@ -44,7 +44,7 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project, optio
} }
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.pull(ctx, project, options) return s.pull(ctx, project, options)
}, s.stderr(), "Pulling") }, s.stdinfo(), "Pulling")
} }
func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error { //nolint:gocyclo func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error { //nolint:gocyclo
@ -305,7 +305,7 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
} }
} }
return err return err
}, s.stderr()) }, s.stdinfo())
} }
func isServiceImageToBuild(service types.ServiceConfig, services []types.ServiceConfig) bool { func isServiceImageToBuild(service types.ServiceConfig, services []types.ServiceConfig) bool {

View File

@ -42,7 +42,7 @@ func (s *composeService) Push(ctx context.Context, project *types.Project, optio
} }
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.push(ctx, project, options) return s.push(ctx, project, options)
}, s.stderr(), "Pushing") }, s.stdinfo(), "Pushing")
} }
func (s *composeService) push(ctx context.Context, project *types.Project, options api.PushOptions) error { func (s *composeService) push(ctx context.Context, project *types.Project, options api.PushOptions) error {

View File

@ -75,10 +75,10 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
stoppedContainers.forEach(func(c moby.Container) { stoppedContainers.forEach(func(c moby.Container) {
names = append(names, getCanonicalContainerName(c)) names = append(names, getCanonicalContainerName(c))
}) })
fmt.Fprintln(s.stderr(), names) fmt.Fprintln(s.stdinfo(), names)
if len(names) == 0 { if len(names) == 0 {
fmt.Fprintln(s.stderr(), "No stopped containers") fmt.Fprintln(s.stdinfo(), "No stopped containers")
return nil return nil
} }
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", ")) msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
@ -95,7 +95,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
} }
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.remove(ctx, stoppedContainers, options) return s.remove(ctx, stoppedContainers, options)
}, s.stderr(), "Removing") }, s.stdinfo(), "Removing")
} }
func (s *composeService) remove(ctx context.Context, containers Containers, options api.RemoveOptions) error { func (s *composeService) remove(ctx context.Context, containers Containers, options api.RemoveOptions) error {

View File

@ -31,7 +31,7 @@ import (
func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error { func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.restart(ctx, strings.ToLower(projectName), options) return s.restart(ctx, strings.ToLower(projectName), options)
}, s.stderr(), "Restarting") }, s.stdinfo(), "Restarting")
} }
func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error { func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {

View File

@ -38,7 +38,7 @@ import (
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error { func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
return progress.Run(ctx, func(ctx context.Context) error { return progress.Run(ctx, func(ctx context.Context) error {
return s.start(ctx, strings.ToLower(projectName), options, nil) return s.start(ctx, strings.ToLower(projectName), options, nil)
}, s.stderr()) }, s.stdinfo())
} }
func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error { func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error {

View File

@ -28,7 +28,7 @@ import (
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error { func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.stop(ctx, strings.ToLower(projectName), options) return s.stop(ctx, strings.ToLower(projectName), options)
}, s.stderr(), "Stopping") }, s.stdinfo(), "Stopping")
} }
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error { func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {

View File

@ -40,7 +40,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return s.start(ctx, project.Name, options.Start, nil) return s.start(ctx, project.Name, options.Start, nil)
} }
return nil return nil
}, s.stderr()) }, s.stdinfo())
if err != nil { if err != nil {
return err return err
} }
@ -59,7 +59,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
stopFunc := func() error { stopFunc := func() error {
fmt.Fprintln(s.stderr(), "Aborting on container exit...") fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
ctx := context.Background() ctx := context.Background()
return progress.Run(ctx, func(ctx context.Context) error { return progress.Run(ctx, func(ctx context.Context) error {
go func() { go func() {
@ -74,7 +74,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
Services: options.Create.Services, Services: options.Create.Services,
Project: project, Project: project,
}) })
}, s.stderr()) }, s.stdinfo())
} }
var isTerminated bool var isTerminated bool
@ -83,7 +83,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
<-signalChan <-signalChan
isTerminated = true isTerminated = true
printer.Cancel() printer.Cancel()
fmt.Fprintln(s.stderr(), "Gracefully stopping... (press Ctrl+C again to force)") fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
eg.Go(stopFunc) eg.Go(stopFunc)
}() }()

View File

@ -155,7 +155,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
return err return err
} }
fmt.Fprintf(s.stderr(), "watching %s\n", bc) fmt.Fprintf(s.stdinfo(), "watching %s\n", bc)
err = watcher.Start() err = watcher.Start()
if err != nil { if err != nil {
return err return err
@ -207,7 +207,7 @@ WATCH:
continue continue
} }
fmt.Fprintf(s.stderr(), "change detected on %s\n", hostPath) fmt.Fprintf(s.stdinfo(), "change detected on %s\n", hostPath)
f := fileMapping{ f := fileMapping{
HostPath: hostPath, HostPath: hostPath,
@ -283,7 +283,7 @@ func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Proje
} }
fmt.Fprintf( fmt.Fprintf(
s.stderr(), s.stdinfo(),
"Rebuilding %s after changes were detected:%s\n", "Rebuilding %s after changes were detected:%s\n",
strings.Join(serviceNames, ", "), strings.Join(serviceNames, ", "),
strings.Join(append([]string{""}, allPaths.Elements()...), "\n - "), strings.Join(append([]string{""}, allPaths.Elements()...), "\n - "),
@ -319,7 +319,7 @@ func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project,
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(s.stderr(), "%s updated\n", opt.ContainerPath) fmt.Fprintf(s.stdinfo(), "%s updated\n", opt.ContainerPath)
} else if errors.Is(statErr, fs.ErrNotExist) { } else if errors.Is(statErr, fs.ErrNotExist) {
_, err := s.Exec(ctx, project.Name, api.RunOptions{ _, err := s.Exec(ctx, project.Name, api.RunOptions{
Service: opt.Service, Service: opt.Service,
@ -329,7 +329,7 @@ func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project,
if err != nil { if err != nil {
logrus.Warnf("failed to delete %q from %s: %v", opt.ContainerPath, opt.Service, err) logrus.Warnf("failed to delete %q from %s: %v", opt.ContainerPath, opt.Service, err)
} }
fmt.Fprintf(s.stderr(), "%s deleted from container\n", opt.ContainerPath) fmt.Fprintf(s.stdinfo(), "%s deleted from container\n", opt.ContainerPath)
} }
} }
} }