Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions cmd/compose/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type upOptions struct {
noAttach []string
timestamp bool
wait bool
log bool
waitTimeout int
watch bool
navigationMenu bool
Expand Down Expand Up @@ -171,6 +172,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services")
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services")
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
flags.BoolVar(&up.log, "log", false, "Run in attached mode and stream service logs")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine we attach to containers during wait phase by default, this seems a reasonable behavior. Users who prefer the legacy behavior (maybe in a script) can configure compose to detach or redirect output

flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration in seconds to wait for the project to be running|healthy")
flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")
Expand All @@ -194,11 +196,25 @@ func validateFlags(up *upOptions, create *createOptions) error {
if up.cascadeStop && up.cascadeFail {
return fmt.Errorf("--abort-on-container-failure cannot be combined with --abort-on-container-exit")
}

if up.log {
if !up.wait {
return fmt.Errorf("--log should be combined with --wait")
}

if up.Detach {
return fmt.Errorf("--detach should not be combined with --log")
}
}

if up.wait {
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
up.Detach = true

if !up.log {
up.Detach = true
}
}
if create.Build && create.noBuild {
return fmt.Errorf("--build and --no-build are incompatible")
Expand Down Expand Up @@ -297,7 +313,6 @@ func runUp(
var attach []string
if !upOptions.Detach {
consumer = formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)

var attachSet utils.Set[string]
if len(upOptions.attach) != 0 {
// services are passed explicitly with --attach, verify they're valid and then use them as-is
Expand Down Expand Up @@ -339,6 +354,7 @@ func runUp(
OnExit: upOptions.OnExit(),
Wait: upOptions.wait,
WaitTimeout: timeout,
Log: upOptions.log,
Watch: upOptions.watch,
Services: services,
NavigationMenu: upOptions.navigationMenu && display.Mode != "plain" && dockerCli.In().IsTerminal(),
Expand Down
11 changes: 9 additions & 2 deletions cmd/compose/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"

"github.com/docker/compose/v5/cmd/formatter"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
Expand All @@ -34,6 +35,7 @@ type waitOptions struct {
services []string

downProject bool
log bool
}

func waitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *BackendOptions) *cobra.Command {
Expand All @@ -58,22 +60,27 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
}

cmd.Flags().BoolVar(&opts.downProject, "down-project", false, "Drops project when the first container stops")
cmd.Flags().BoolVar(&opts.log, "log", false, "Shows the logs of the service")

return cmd
}

func runWait(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts *waitOptions) (int64, error) {
_, name, err := opts.projectOrName(ctx, dockerCli)
project, name, err := opts.projectOrName(ctx, dockerCli)
if err != nil {
return 0, err
}

consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
if err != nil {
return 0, err
}
return backend.Wait(ctx, name, api.WaitOptions{

return backend.Wait(ctx, name, consumer, api.WaitOptions{
Services: opts.services,
DownProjectOnContainerExit: opts.downProject,
Log: opts.log,
Project: project,
})
}
5 changes: 4 additions & 1 deletion pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ type Compose interface {
// Viz generates a graphviz graph of the project services
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
// Wait blocks until at least one of the services' container exits
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
Wait(ctx context.Context, projectName string, consumer LogConsumer, options WaitOptions) (int64, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a breaking change in the API, better pass LogConsumer as a new attribute in WaitOptions struct

// Scale manages numbers of container instances running per service
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
// Export a service container's filesystem as a tar archive
Expand Down Expand Up @@ -165,6 +165,8 @@ type WaitOptions struct {
Services []string
// Executes a down when a container exits
DownProjectOnContainerExit bool
Log bool
Project *types.Project
}

type VizOptions struct {
Expand Down Expand Up @@ -293,6 +295,7 @@ type StartOptions struct {
ExitCodeFrom string
// Wait won't return until containers reached the running|healthy state
Wait bool
Log bool
WaitTimeout time.Duration
// Services passed in the command line to be started
Services []string
Expand Down
10 changes: 10 additions & 0 deletions pkg/compose/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ func (s *composeService) start(ctx context.Context, projectName string, options
}
return err
}

if options.Log {
s.Logs(ctx, projectName, options.Attach, api.LogOptions{
Project: options.Project,
Services: options.Services,
Follow: false,
Tail: "all",
Timestamps: false,
})
}
}

return nil
Expand Down
17 changes: 16 additions & 1 deletion pkg/compose/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ import (
"github.com/docker/compose/v5/pkg/api"
)

func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
func (s *composeService) Wait(
ctx context.Context,
projectName string,
consumer api.LogConsumer,
options api.WaitOptions,
) (int64, error) {
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)

if err != nil {
return 0, err
}
Expand All @@ -36,6 +42,15 @@ func (s *composeService) Wait(ctx context.Context, projectName string, options a

eg, waitCtx := errgroup.WithContext(ctx)
var statusCode int64

if options.Log {
s.Logs(ctx, projectName, consumer, api.LogOptions{
Project: options.Project,
Services: options.Services,
Follow: false,
})
}

for _, ctr := range containers {
eg.Go(func() error {
var err error
Expand Down
3 changes: 3 additions & 0 deletions pkg/e2e/fixtures/start-stop/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ services:
image: nginx:alpine
another:
image: nginx:alpine
hello:
image: alpine
command: echo please-see-me
4 changes: 3 additions & 1 deletion pkg/e2e/fixtures/wait/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ services:
infinity:
image: alpine
command: sleep infinity

hello:
image: alpine
command: sh -c "echo hello"
18 changes: 18 additions & 0 deletions pkg/e2e/start_stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"fmt"
"strings"
"testing"
"time"

testify "github.com/stretchr/testify/assert"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"gotest.tools/v3/poll"
)

func TestStartStop(t *testing.T) {
Expand Down Expand Up @@ -243,6 +245,22 @@ func TestStartStopMultipleServices(t *testing.T) {
}
}

func TestStartLogService(t *testing.T) {
c := NewParallelCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-start-log-svc",
"COMPOSE_FILE=./fixtures/start-stop/compose.yaml"))

t.Run("run wait log", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "up", "hello", "--wait", "--log")
res := icmd.StartCmd(cmd)
t.Cleanup(func() {
_ = res.Cmd.Process.Kill()
})

poll.WaitOn(t, expectOutput(res, "please-see-me"), poll.WithDelay(1000*time.Millisecond), poll.WithTimeout(2*time.Second))
})
}

func TestStartSingleServiceAndDependency(t *testing.T) {
cli := NewParallelCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-start-single-deps",
Expand Down
20 changes: 20 additions & 0 deletions pkg/e2e/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,23 @@ func TestWaitAndDrop(t *testing.T) {
res := c.RunDockerCmd(t, "ps", "--all")
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
}

func TestWaitLog(t *testing.T) {
const projectName = "e2e-wait-and-log"
c := NewParallelCLI(t)

cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()

t.Run("up", func(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d", "hello")
})

t.Run("logs", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "wait", "hello", "--log")
res.Assert(t, icmd.Expected{Out: `hello`})
})
}
Loading