Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(up): Add --wait-services to up command #12172

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
9 changes: 6 additions & 3 deletions cmd/compose/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type upOptions struct {
noAttach []string
timestamp bool
wait bool
waitServices []string
waitTimeout int
watch bool
navigationMenu bool
Expand Down Expand Up @@ -161,7 +162,8 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
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.wait, "wait", false, "Wait for all services to be running|healthy. Implies detached mode.")
flags.StringArrayVar(&up.waitServices, "wait-services", []string{}, "Wait for specified services to be running|healthy. Implies detached mode.")
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration 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 @@ -177,9 +179,9 @@ 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.wait {
if up.wait || len(up.waitServices) > 0 {
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")
return fmt.Errorf("--wait|--wait-services cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
up.Detach = true
}
Expand Down Expand Up @@ -301,6 +303,7 @@ func runUp(
ExitCodeFrom: upOptions.exitCodeFrom,
OnExit: upOptions.OnExit(),
Wait: upOptions.wait,
WaitServices: upOptions.waitServices,
WaitTimeout: timeout,
Watch: upOptions.watch,
Services: services,
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/compose_up.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
| `--timestamps` | `bool` | | Show timestamps |
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
| `--wait` | `bool` | | Wait for all services to be running\|healthy. Implies detached mode. |
| `--wait-services` | `stringArray` | | Wait for specified services to be running\|healthy. Implies detached mode. |
| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy |
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |

Expand Down
14 changes: 13 additions & 1 deletion docs/reference/docker_compose_up.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,19 @@ options:
- option: wait
value_type: bool
default_value: "false"
description: Wait for services to be running|healthy. Implies detached mode.
description: |
Wait for all services to be running|healthy. Implies detached mode.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: wait-services
value_type: stringArray
default_value: '[]'
description: |
Wait for specified services to be running|healthy. Implies detached mode.
deprecated: false
hidden: false
experimental: false
Expand Down
5 changes: 4 additions & 1 deletion pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ type StartOptions struct {
// ExitCodeFrom return exit code from specified service
ExitCodeFrom string
// Wait won't return until containers reached the running|healthy state
Wait bool
Wait bool
// WaitServices defines the services to wait for
WaitServices []string
// WaitTimeout set delay to wait for container to reach running state
WaitTimeout time.Duration
// Services passed in the command line to be started
Services []string
Expand Down
52 changes: 34 additions & 18 deletions pkg/compose/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,46 @@ func (s *composeService) start(ctx context.Context, projectName string, options
return err
}

if options.Wait {
depends := types.DependsOnConfig{}
for _, s := range project.Services {
depends[s.Name] = types.ServiceDependency{
Condition: getDependencyCondition(s, project),
Required: true,
}
err = s.waitIfNeeded(ctx, project, options, containers)
if err != nil {
return err
}

return eg.Wait()
}

func (s *composeService) waitIfNeeded(ctx context.Context, project *types.Project, options api.StartOptions, containers Containers) error {
if !options.Wait && len(options.WaitServices) == 0 {
return nil
}

depends := types.DependsOnConfig{}
for _, s := range project.Services {
if len(options.WaitServices) > 0 && !utils.Contains(options.WaitServices, s.Name) {
continue
}
if options.WaitTimeout > 0 {
withTimeout, cancel := context.WithTimeout(ctx, options.WaitTimeout)
ctx = withTimeout
defer cancel()

depends[s.Name] = types.ServiceDependency{
Condition: getDependencyCondition(s, project),
Required: true,
}
}

err = s.waitDependencies(ctx, project, project.Name, depends, containers, 0)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return fmt.Errorf("application not healthy after %s", options.WaitTimeout)
}
return err
if options.WaitTimeout > 0 {
withTimeout, cancel := context.WithTimeout(ctx, options.WaitTimeout)
ctx = withTimeout
defer cancel()
}

err := s.waitDependencies(ctx, project, project.Name, depends, containers, 0)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return fmt.Errorf("application not healthy after %s", options.WaitTimeout)
}
return err
}

return eg.Wait()
return nil
}

// getDependencyCondition checks if service is depended on by other services
Expand Down
Loading