Skip to content

Commit

Permalink
Merge pull request #10742 from ulyssessouza/add-wait
Browse files Browse the repository at this point in the history
Add `docker compose wait`
  • Loading branch information
ulyssessouza committed Jul 2, 2023
2 parents c496c23 + edd76bf commit fa3e16c
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
pullCommand(&opts, backend),
createCommand(&opts, backend),
copyCommand(&opts, backend),
waitCommand(&opts, backend),
alphaCommand(&opts, backend),
)

Expand Down
72 changes: 72 additions & 0 deletions cmd/compose/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Copyright 2023 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 compose

import (
"context"
"os"

"github.com/docker/cli/cli"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)

type waitOptions struct {
*ProjectOptions

services []string

downProject bool
}

func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := waitOptions{
ProjectOptions: p,
}

var statusCode int64
var err error
cmd := &cobra.Command{
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
Short: "Block until the first service container stops",
Args: cli.RequiresMinArgs(1),
RunE: Adapt(func(ctx context.Context, services []string) error {
opts.services = services
statusCode, err = runWait(ctx, backend, &opts)
return err
}),
PostRun: func(cmd *cobra.Command, args []string) {
os.Exit(int(statusCode))
},
}

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

return cmd
}

func runWait(ctx context.Context, backend api.Service, opts *waitOptions) (int64, error) {
_, name, err := opts.projectOrName()
if err != nil {
return 0, err
}

return backend.Wait(ctx, name, api.WaitOptions{
Services: opts.services,
DownProjectOnContainerExit: opts.downProject,
})
}
1 change: 1 addition & 0 deletions docs/reference/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Define and run multi-container applications with Docker.
| [`unpause`](compose_unpause.md) | Unpause services |
| [`up`](compose_up.md) | Create and start containers |
| [`version`](compose_version.md) | Show the Docker Compose version information |
| [`wait`](compose_wait.md) | Block until the first service container stops |


### Options
Expand Down
15 changes: 15 additions & 0 deletions docs/reference/compose_wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# docker compose wait

<!---MARKER_GEN_START-->
Block until the first service container stops

### Options

| Name | Type | Default | Description |
|:-----------------|:-----|:--------|:---------------------------------------------|
| `--down-project` | | | Drops project when the first container stops |
| `--dry-run` | | | Execute command in dry run mode |


<!---MARKER_GEN_END-->

2 changes: 2 additions & 0 deletions docs/reference/docker_compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ cname:
- docker compose unpause
- docker compose up
- docker compose version
- docker compose wait
clink:
- docker_compose_build.yaml
- docker_compose_config.yaml
Expand All @@ -197,6 +198,7 @@ clink:
- docker_compose_unpause.yaml
- docker_compose_up.yaml
- docker_compose_version.yaml
- docker_compose_wait.yaml
options:
- option: ansi
value_type: string
Expand Down
34 changes: 34 additions & 0 deletions docs/reference/docker_compose_wait.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
command: docker compose wait
short: Block until the first service container stops
long: Block until the first service container stops
usage: docker compose wait SERVICE [SERVICE...] [OPTIONS]
pname: docker compose
plink: docker_compose.yaml
options:
- option: down-project
value_type: bool
default_value: "false"
description: Drops project when the first container stops
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool
default_value: "false"
description: Execute command in dry run mode
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false

9 changes: 9 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ type Service interface {
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
// 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)
}

type WaitOptions struct {
// Services passed in the command line to be waited
Services []string
// Executes a down when a container exits
DownProjectOnContainerExit bool
}

type VizOptions struct {
Expand Down
12 changes: 11 additions & 1 deletion pkg/api/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type ServiceProxy struct {
MaxConcurrencyFn func(parallel int)
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
interceptors []Interceptor
}

Expand Down Expand Up @@ -95,6 +96,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
s.MaxConcurrencyFn = service.MaxConcurrency
s.DryRunModeFn = service.DryRunMode
s.VizFn = service.Viz
s.WaitFn = service.Wait
return s
}

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

// Viz implements Viz interface
// Viz implements Service interface
func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error) {
if s.VizFn == nil {
return "", ErrNotImplemented
}
return s.VizFn(ctx, project, options)
}

// Wait implements Service interface
func (s *ServiceProxy) Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error) {
if s.WaitFn == nil {
return 0, ErrNotImplemented
}
return s.WaitFn(ctx, projectName, options)
}

func (s *ServiceProxy) MaxConcurrency(i int) {
s.MaxConcurrencyFn(i)
}
Expand Down
67 changes: 67 additions & 0 deletions pkg/compose/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
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 compose

import (
"context"
"fmt"

"github.com/docker/compose/v2/pkg/api"
"golang.org/x/sync/errgroup"
)

func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)
if err != nil {
return 0, err
}
if len(containers) == 0 {
return 0, fmt.Errorf("no containers for project %q", projectName)
}

eg, waitCtx := errgroup.WithContext(ctx)
var statusCode int64
for _, c := range containers {
c := c
eg.Go(func() error {
var err error
resultC, errC := s.dockerCli.Client().ContainerWait(waitCtx, c.ID, "")

select {
case result := <-resultC:
fmt.Fprintf(s.dockerCli.Out(), "container %q exited with status code %d\n", c.ID, result.StatusCode)
statusCode = result.StatusCode
case err = <-errC:
}

return err
})
}

err = eg.Wait()
if err != nil {
return 42, err // Ignore abort flag in case of error in wait
}

if options.DownProjectOnContainerExit {
return statusCode, s.Down(ctx, projectName, api.DownOptions{
RemoveOrphans: true,
})
}

return statusCode, err
}
11 changes: 11 additions & 0 deletions pkg/e2e/fixtures/wait/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
faster:
image: alpine
command: sleep 2
slower:
image: alpine
command: sleep 5
infinity:
image: alpine
command: sleep infinity

72 changes: 72 additions & 0 deletions pkg/e2e/wait_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
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 e2e

import (
"strings"
"testing"
"time"

"gotest.tools/v3/assert"
)

func TestWaitOnFaster(t *testing.T) {
const projectName = "e2e-wait-faster"
c := NewParallelCLI(t)

c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster")
}

func TestWaitOnSlower(t *testing.T) {
const projectName = "e2e-wait-slower"
c := NewParallelCLI(t)

c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower")
}

func TestWaitOnInfinity(t *testing.T) {
const projectName = "e2e-wait-infinity"
c := NewParallelCLI(t)

c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")

finished := make(chan struct{})
ticker := time.NewTicker(7 * time.Second)
go func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
finished <- struct{}{}
}()

select {
case <-finished:
t.Fatal("wait infinity should not finish")
case <-ticker.C:
}
}

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

c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster")

res := c.RunDockerCmd(t, "ps", "--all")
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
}
15 changes: 15 additions & 0 deletions pkg/mocks/mock_docker_compose_api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fa3e16c

Please sign in to comment.