mirror of https://github.com/docker/compose.git
Introduce --abort-on-container-failure
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
2658c372a7
commit
29692b5921
|
@ -46,6 +46,7 @@ type upOptions struct {
|
|||
noStart bool
|
||||
noDeps bool
|
||||
cascadeStop bool
|
||||
cascadeFail bool
|
||||
exitCodeFrom string
|
||||
noColor bool
|
||||
noPrefix bool
|
||||
|
@ -89,6 +90,17 @@ func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli, experimenta
|
|||
}
|
||||
}
|
||||
|
||||
func (opts upOptions) OnExit() api.Cascade {
|
||||
switch {
|
||||
case opts.cascadeStop:
|
||||
return api.CascadeStop
|
||||
case opts.cascadeFail:
|
||||
return api.CascadeFail
|
||||
default:
|
||||
return api.CascadeIgnore
|
||||
}
|
||||
}
|
||||
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, experiments *experimental.State) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
|
@ -131,6 +143,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
|||
flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them")
|
||||
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
|
||||
flags.BoolVar(&up.cascadeFail, "abort-on-container-failure", false, "Stops all containers if any container exited with failure. Incompatible with -d")
|
||||
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running")
|
||||
flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps")
|
||||
|
@ -152,9 +165,12 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
|||
|
||||
//nolint:gocyclo
|
||||
func validateFlags(up *upOptions, create *createOptions) error {
|
||||
if up.exitCodeFrom != "" {
|
||||
if up.exitCodeFrom != "" && !up.cascadeFail {
|
||||
up.cascadeStop = true
|
||||
}
|
||||
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.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
|
||||
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
|
@ -164,8 +180,8 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
|||
if create.Build && create.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || up.cascadeFail || len(up.attach) > 0) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach or --attach-dependencies")
|
||||
}
|
||||
if create.forceRecreate && create.noRecreate {
|
||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||
|
@ -278,7 +294,7 @@ func runUp(
|
|||
Attach: consumer,
|
||||
AttachTo: attach,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
OnExit: upOptions.OnExit(),
|
||||
Wait: upOptions.wait,
|
||||
WaitTimeout: timeout,
|
||||
Watch: upOptions.watch,
|
||||
|
|
|
@ -104,10 +104,9 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
|||
QuietPull: buildOpts.quiet,
|
||||
},
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: nil,
|
||||
CascadeStop: false,
|
||||
Services: services,
|
||||
Project: project,
|
||||
Attach: nil,
|
||||
Services: services,
|
||||
},
|
||||
}
|
||||
if err := backend.Up(ctx, project, upOpts); err != nil {
|
||||
|
|
|
@ -5,34 +5,35 @@ Create and start containers
|
|||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------|
|
||||
| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
|
||||
| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
|
||||
| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
|
||||
| `--attach-dependencies` | | | Automatically attach to log output of dependent services |
|
||||
| `--build` | | | Build images before starting containers |
|
||||
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
|
||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
|
||||
| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services |
|
||||
| `--no-build` | | | Don't build an image, even if it's policy |
|
||||
| `--no-color` | | | Produce monochrome output |
|
||||
| `--no-deps` | | | Don't start linked services |
|
||||
| `--no-log-prefix` | | | Don't print prefix in logs |
|
||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--no-start` | | | Don't start the services after creating them |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `--quiet-pull` | | | Pull without printing progress information |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
|
||||
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
||||
| `--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` | | | Show timestamps |
|
||||
| `--wait` | | | Wait for 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` | | | Watch source code and rebuild/refresh containers when files are updated. |
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------|
|
||||
| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
|
||||
| `--abort-on-container-failure` | | | Stops all containers if any container exited with failure. Incompatible with -d |
|
||||
| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
|
||||
| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
|
||||
| `--attach-dependencies` | | | Automatically attach to log output of dependent services |
|
||||
| `--build` | | | Build images before starting containers |
|
||||
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
|
||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
|
||||
| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services |
|
||||
| `--no-build` | | | Don't build an image, even if it's policy |
|
||||
| `--no-color` | | | Produce monochrome output |
|
||||
| `--no-deps` | | | Don't start linked services |
|
||||
| `--no-log-prefix` | | | Don't print prefix in logs |
|
||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--no-start` | | | Don't start the services after creating them |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `--quiet-pull` | | | Pull without printing progress information |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
|
||||
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
||||
| `--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` | | | Show timestamps |
|
||||
| `--wait` | | | Wait for 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` | | | Watch source code and rebuild/refresh containers when files are updated. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
|
|
@ -35,6 +35,17 @@ options:
|
|||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: abort-on-container-failure
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: |
|
||||
Stops all containers if any container exited with failure. Incompatible with -d
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: always-recreate-deps
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
|
|
@ -209,8 +209,8 @@ type StartOptions struct {
|
|||
Attach LogConsumer
|
||||
// AttachTo set the services to attach to
|
||||
AttachTo []string
|
||||
// CascadeStop stops the application when a container stops
|
||||
CascadeStop bool
|
||||
// OnExit defines behavior when a container stops
|
||||
OnExit Cascade
|
||||
// ExitCodeFrom return exit code from specified service
|
||||
ExitCodeFrom string
|
||||
// Wait won't return until containers reached the running|healthy state
|
||||
|
@ -222,6 +222,14 @@ type StartOptions struct {
|
|||
NavigationMenu bool
|
||||
}
|
||||
|
||||
type Cascade int
|
||||
|
||||
const (
|
||||
CascadeIgnore Cascade = iota
|
||||
CascadeStop Cascade = iota
|
||||
CascadeFail Cascade = iota
|
||||
)
|
||||
|
||||
// RestartOptions group options of the Restart API
|
||||
type RestartOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
|
|
|
@ -80,7 +80,7 @@ func (s *composeService) Logs(
|
|||
containers = containers.filter(isRunning())
|
||||
printer := newLogPrinter(consumer)
|
||||
eg.Go(func() error {
|
||||
_, err := printer.Run(false, "", nil)
|
||||
_, err := printer.Run(api.CascadeIgnore, "", nil)
|
||||
return err
|
||||
})
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
// logPrinter watch application containers an collect their logs
|
||||
type logPrinter interface {
|
||||
HandleEvent(event api.ContainerEvent)
|
||||
Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Run(cascade api.Cascade, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Cancel()
|
||||
Stop()
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func (p *printer) HandleEvent(event api.ContainerEvent) {
|
|||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
var (
|
||||
aborting bool
|
||||
exitCode int
|
||||
|
@ -115,7 +115,7 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|||
delete(containers, id)
|
||||
}
|
||||
|
||||
if cascadeStop {
|
||||
if cascade == api.CascadeStop {
|
||||
if !aborting {
|
||||
aborting = true
|
||||
err := stopFn()
|
||||
|
@ -123,14 +123,24 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|||
return 0, err
|
||||
}
|
||||
}
|
||||
if event.Type == api.ContainerEventExit {
|
||||
if exitCodeFrom == "" {
|
||||
exitCodeFrom = event.Service
|
||||
}
|
||||
if exitCodeFrom == event.Service {
|
||||
exitCode = event.ExitCode
|
||||
}
|
||||
if event.Type == api.ContainerEventExit {
|
||||
if cascade == api.CascadeFail && event.ExitCode != 0 {
|
||||
exitCodeFrom = event.Service
|
||||
if !aborting {
|
||||
aborting = true
|
||||
err := stopFn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if cascade == api.CascadeStop && exitCodeFrom == "" {
|
||||
exitCodeFrom = event.Service
|
||||
}
|
||||
if exitCodeFrom == event.Service {
|
||||
exitCode = event.ExitCode
|
||||
}
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
// Last container terminated, done
|
||||
|
|
|
@ -134,7 +134,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
|||
|
||||
var exitCode int
|
||||
eg.Go(func() error {
|
||||
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, func() error {
|
||||
code, err := printer.Run(options.Start.OnExit, options.Start.ExitCodeFrom, func() error {
|
||||
fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.Stop(ctx, project.Name, api.StopOptions{
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCascadeStop(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-cascade-stop"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cascade/compose.yaml", "--project-name", projectName,
|
||||
"up", "--abort-on-container-exit")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "exit-1 exited with code 0"), res.Combined())
|
||||
}
|
||||
|
||||
func TestCascadeFail(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-cascade-fail"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade/compose.yaml", "--project-name", projectName,
|
||||
"up", "--abort-on-container-failure")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "exit-1 exited with code 0"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "fail-1 exited with code 1"), res.Combined())
|
||||
assert.Equal(t, res.ExitCode, 1)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
services:
|
||||
exit:
|
||||
image: alpine
|
||||
command: /bin/true
|
||||
|
||||
fail:
|
||||
image: alpine
|
||||
command: sh -c "sleep 0.1 && /bin/false"
|
Loading…
Reference in New Issue