Skip to content

Commit fa3e16c

Browse files
authored
Merge pull request docker#10742 from ulyssessouza/add-wait
Add `docker compose wait`
2 parents c496c23 + edd76bf commit fa3e16c

File tree

12 files changed

+310
-1
lines changed

12 files changed

+310
-1
lines changed

cmd/compose/compose.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
431431
pullCommand(&opts, backend),
432432
createCommand(&opts, backend),
433433
copyCommand(&opts, backend),
434+
waitCommand(&opts, backend),
434435
alphaCommand(&opts, backend),
435436
)
436437

cmd/compose/wait.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2023 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package compose
18+
19+
import (
20+
"context"
21+
"os"
22+
23+
"github.com/docker/cli/cli"
24+
"github.com/docker/compose/v2/pkg/api"
25+
"github.com/spf13/cobra"
26+
)
27+
28+
type waitOptions struct {
29+
*ProjectOptions
30+
31+
services []string
32+
33+
downProject bool
34+
}
35+
36+
func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
37+
opts := waitOptions{
38+
ProjectOptions: p,
39+
}
40+
41+
var statusCode int64
42+
var err error
43+
cmd := &cobra.Command{
44+
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
45+
Short: "Block until the first service container stops",
46+
Args: cli.RequiresMinArgs(1),
47+
RunE: Adapt(func(ctx context.Context, services []string) error {
48+
opts.services = services
49+
statusCode, err = runWait(ctx, backend, &opts)
50+
return err
51+
}),
52+
PostRun: func(cmd *cobra.Command, args []string) {
53+
os.Exit(int(statusCode))
54+
},
55+
}
56+
57+
cmd.Flags().BoolVar(&opts.downProject, "down-project", false, "Drops project when the first container stops")
58+
59+
return cmd
60+
}
61+
62+
func runWait(ctx context.Context, backend api.Service, opts *waitOptions) (int64, error) {
63+
_, name, err := opts.projectOrName()
64+
if err != nil {
65+
return 0, err
66+
}
67+
68+
return backend.Wait(ctx, name, api.WaitOptions{
69+
Services: opts.services,
70+
DownProjectOnContainerExit: opts.downProject,
71+
})
72+
}

docs/reference/compose.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Define and run multi-container applications with Docker.
3333
| [`unpause`](compose_unpause.md) | Unpause services |
3434
| [`up`](compose_up.md) | Create and start containers |
3535
| [`version`](compose_version.md) | Show the Docker Compose version information |
36+
| [`wait`](compose_wait.md) | Block until the first service container stops |
3637

3738

3839
### Options

docs/reference/compose_wait.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# docker compose wait
2+
3+
<!---MARKER_GEN_START-->
4+
Block until the first service container stops
5+
6+
### Options
7+
8+
| Name | Type | Default | Description |
9+
|:-----------------|:-----|:--------|:---------------------------------------------|
10+
| `--down-project` | | | Drops project when the first container stops |
11+
| `--dry-run` | | | Execute command in dry run mode |
12+
13+
14+
<!---MARKER_GEN_END-->
15+

docs/reference/docker_compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ cname:
171171
- docker compose unpause
172172
- docker compose up
173173
- docker compose version
174+
- docker compose wait
174175
clink:
175176
- docker_compose_build.yaml
176177
- docker_compose_config.yaml
@@ -197,6 +198,7 @@ clink:
197198
- docker_compose_unpause.yaml
198199
- docker_compose_up.yaml
199200
- docker_compose_version.yaml
201+
- docker_compose_wait.yaml
200202
options:
201203
- option: ansi
202204
value_type: string
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
command: docker compose wait
2+
short: Block until the first service container stops
3+
long: Block until the first service container stops
4+
usage: docker compose wait SERVICE [SERVICE...] [OPTIONS]
5+
pname: docker compose
6+
plink: docker_compose.yaml
7+
options:
8+
- option: down-project
9+
value_type: bool
10+
default_value: "false"
11+
description: Drops project when the first container stops
12+
deprecated: false
13+
hidden: false
14+
experimental: false
15+
experimentalcli: false
16+
kubernetes: false
17+
swarm: false
18+
inherited_options:
19+
- option: dry-run
20+
value_type: bool
21+
default_value: "false"
22+
description: Execute command in dry run mode
23+
deprecated: false
24+
hidden: false
25+
experimental: false
26+
experimentalcli: false
27+
kubernetes: false
28+
swarm: false
29+
deprecated: false
30+
experimental: false
31+
experimentalcli: false
32+
kubernetes: false
33+
swarm: false
34+

pkg/api/api.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ type Service interface {
8484
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
8585
// Viz generates a graphviz graph of the project services
8686
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
87+
// Wait blocks until at least one of the services' container exits
88+
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
89+
}
90+
91+
type WaitOptions struct {
92+
// Services passed in the command line to be waited
93+
Services []string
94+
// Executes a down when a container exits
95+
DownProjectOnContainerExit bool
8796
}
8897

8998
type VizOptions struct {

pkg/api/proxy.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ServiceProxy struct {
5454
MaxConcurrencyFn func(parallel int)
5555
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
5656
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
57+
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
5758
interceptors []Interceptor
5859
}
5960

@@ -95,6 +96,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
9596
s.MaxConcurrencyFn = service.MaxConcurrency
9697
s.DryRunModeFn = service.DryRunMode
9798
s.VizFn = service.Viz
99+
s.WaitFn = service.Wait
98100
return s
99101
}
100102

@@ -325,14 +327,22 @@ func (s *ServiceProxy) Watch(ctx context.Context, project *types.Project, servic
325327
return s.WatchFn(ctx, project, services, options)
326328
}
327329

328-
// Viz implements Viz interface
330+
// Viz implements Service interface
329331
func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error) {
330332
if s.VizFn == nil {
331333
return "", ErrNotImplemented
332334
}
333335
return s.VizFn(ctx, project, options)
334336
}
335337

338+
// Wait implements Service interface
339+
func (s *ServiceProxy) Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error) {
340+
if s.WaitFn == nil {
341+
return 0, ErrNotImplemented
342+
}
343+
return s.WaitFn(ctx, projectName, options)
344+
}
345+
336346
func (s *ServiceProxy) MaxConcurrency(i int) {
337347
s.MaxConcurrencyFn(i)
338348
}

pkg/compose/wait.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package compose
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/docker/compose/v2/pkg/api"
24+
"golang.org/x/sync/errgroup"
25+
)
26+
27+
func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
28+
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)
29+
if err != nil {
30+
return 0, err
31+
}
32+
if len(containers) == 0 {
33+
return 0, fmt.Errorf("no containers for project %q", projectName)
34+
}
35+
36+
eg, waitCtx := errgroup.WithContext(ctx)
37+
var statusCode int64
38+
for _, c := range containers {
39+
c := c
40+
eg.Go(func() error {
41+
var err error
42+
resultC, errC := s.dockerCli.Client().ContainerWait(waitCtx, c.ID, "")
43+
44+
select {
45+
case result := <-resultC:
46+
fmt.Fprintf(s.dockerCli.Out(), "container %q exited with status code %d\n", c.ID, result.StatusCode)
47+
statusCode = result.StatusCode
48+
case err = <-errC:
49+
}
50+
51+
return err
52+
})
53+
}
54+
55+
err = eg.Wait()
56+
if err != nil {
57+
return 42, err // Ignore abort flag in case of error in wait
58+
}
59+
60+
if options.DownProjectOnContainerExit {
61+
return statusCode, s.Down(ctx, projectName, api.DownOptions{
62+
RemoveOrphans: true,
63+
})
64+
}
65+
66+
return statusCode, err
67+
}

pkg/e2e/fixtures/wait/compose.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
faster:
3+
image: alpine
4+
command: sleep 2
5+
slower:
6+
image: alpine
7+
command: sleep 5
8+
infinity:
9+
image: alpine
10+
command: sleep infinity
11+

0 commit comments

Comments
 (0)