mirror of
https://github.com/docker/compose.git
synced 2025-07-25 22:54:54 +02:00
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
|
noStart bool
|
||||||
noDeps bool
|
noDeps bool
|
||||||
cascadeStop bool
|
cascadeStop bool
|
||||||
|
cascadeFail bool
|
||||||
exitCodeFrom string
|
exitCodeFrom string
|
||||||
noColor bool
|
noColor bool
|
||||||
noPrefix 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 {
|
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, experiments *experimental.State) *cobra.Command {
|
||||||
up := upOptions{}
|
up := upOptions{}
|
||||||
create := createOptions{}
|
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(&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.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.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.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.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")
|
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
|
//nolint:gocyclo
|
||||||
func validateFlags(up *upOptions, create *createOptions) error {
|
func validateFlags(up *upOptions, create *createOptions) error {
|
||||||
if up.exitCodeFrom != "" {
|
if up.exitCodeFrom != "" && !up.cascadeFail {
|
||||||
up.cascadeStop = true
|
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.wait {
|
||||||
if up.attachDependencies || up.cascadeStop || len(up.attach) > 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 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 {
|
if create.Build && create.noBuild {
|
||||||
return fmt.Errorf("--build and --no-build are incompatible")
|
return fmt.Errorf("--build and --no-build are incompatible")
|
||||||
}
|
}
|
||||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
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, --attach or --attach-dependencies")
|
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 {
|
if create.forceRecreate && create.noRecreate {
|
||||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||||
@ -278,7 +294,7 @@ func runUp(
|
|||||||
Attach: consumer,
|
Attach: consumer,
|
||||||
AttachTo: attach,
|
AttachTo: attach,
|
||||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||||
CascadeStop: upOptions.cascadeStop,
|
OnExit: upOptions.OnExit(),
|
||||||
Wait: upOptions.wait,
|
Wait: upOptions.wait,
|
||||||
WaitTimeout: timeout,
|
WaitTimeout: timeout,
|
||||||
Watch: upOptions.watch,
|
Watch: upOptions.watch,
|
||||||
|
@ -104,10 +104,9 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
|||||||
QuietPull: buildOpts.quiet,
|
QuietPull: buildOpts.quiet,
|
||||||
},
|
},
|
||||||
Start: api.StartOptions{
|
Start: api.StartOptions{
|
||||||
Project: project,
|
Project: project,
|
||||||
Attach: nil,
|
Attach: nil,
|
||||||
CascadeStop: false,
|
Services: services,
|
||||||
Services: services,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := backend.Up(ctx, project, upOpts); err != nil {
|
if err := backend.Up(ctx, project, upOpts); err != nil {
|
||||||
|
@ -5,34 +5,35 @@ Create and start containers
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:-----------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------|
|
|:-------------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------|
|
||||||
| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
|
| `--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. |
|
| `--abort-on-container-failure` | | | Stops all containers if any container exited with failure. Incompatible with -d |
|
||||||
| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
|
| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
|
||||||
| `--attach-dependencies` | | | Automatically attach to log output of dependent services |
|
| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
|
||||||
| `--build` | | | Build images before starting containers |
|
| `--attach-dependencies` | | | Automatically attach to log output of dependent services |
|
||||||
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
|
| `--build` | | | Build images before starting containers |
|
||||||
| `--dry-run` | | | Execute command in dry run mode |
|
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
|
||||||
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
|
| `--dry-run` | | | Execute command in dry run mode |
|
||||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
|
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
|
||||||
| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services |
|
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
|
||||||
| `--no-build` | | | Don't build an image, even if it's policy |
|
| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services |
|
||||||
| `--no-color` | | | Produce monochrome output |
|
| `--no-build` | | | Don't build an image, even if it's policy |
|
||||||
| `--no-deps` | | | Don't start linked services |
|
| `--no-color` | | | Produce monochrome output |
|
||||||
| `--no-log-prefix` | | | Don't print prefix in logs |
|
| `--no-deps` | | | Don't start linked services |
|
||||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
| `--no-log-prefix` | | | Don't print prefix in logs |
|
||||||
| `--no-start` | | | Don't start the services after creating them |
|
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
| `--no-start` | | | Don't start the services after creating them |
|
||||||
| `--quiet-pull` | | | Pull without printing progress information |
|
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
|
| `--quiet-pull` | | | Pull without printing progress information |
|
||||||
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
|
||||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
||||||
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
|
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||||
| `--timestamps` | | | Show timestamps |
|
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
|
||||||
| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
|
| `--timestamps` | | | Show timestamps |
|
||||||
| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy |
|
| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||||
| `-w`, `--watch` | | | Watch source code and rebuild/refresh containers when files are updated. |
|
| `--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-->
|
<!---MARKER_GEN_END-->
|
||||||
|
@ -35,6 +35,17 @@ options:
|
|||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: 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
|
- option: always-recreate-deps
|
||||||
value_type: bool
|
value_type: bool
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
|
@ -209,8 +209,8 @@ type StartOptions struct {
|
|||||||
Attach LogConsumer
|
Attach LogConsumer
|
||||||
// AttachTo set the services to attach to
|
// AttachTo set the services to attach to
|
||||||
AttachTo []string
|
AttachTo []string
|
||||||
// CascadeStop stops the application when a container stops
|
// OnExit defines behavior when a container stops
|
||||||
CascadeStop bool
|
OnExit Cascade
|
||||||
// ExitCodeFrom return exit code from specified service
|
// ExitCodeFrom return exit code from specified service
|
||||||
ExitCodeFrom string
|
ExitCodeFrom string
|
||||||
// Wait won't return until containers reached the running|healthy state
|
// Wait won't return until containers reached the running|healthy state
|
||||||
@ -222,6 +222,14 @@ type StartOptions struct {
|
|||||||
NavigationMenu bool
|
NavigationMenu bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cascade int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CascadeIgnore Cascade = iota
|
||||||
|
CascadeStop Cascade = iota
|
||||||
|
CascadeFail Cascade = iota
|
||||||
|
)
|
||||||
|
|
||||||
// RestartOptions group options of the Restart API
|
// RestartOptions group options of the Restart API
|
||||||
type RestartOptions struct {
|
type RestartOptions struct {
|
||||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
// 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())
|
containers = containers.filter(isRunning())
|
||||||
printer := newLogPrinter(consumer)
|
printer := newLogPrinter(consumer)
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
_, err := printer.Run(false, "", nil)
|
_, err := printer.Run(api.CascadeIgnore, "", nil)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
// logPrinter watch application containers an collect their logs
|
// logPrinter watch application containers an collect their logs
|
||||||
type logPrinter interface {
|
type logPrinter interface {
|
||||||
HandleEvent(event api.ContainerEvent)
|
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()
|
Cancel()
|
||||||
Stop()
|
Stop()
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func (p *printer) HandleEvent(event api.ContainerEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//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 (
|
var (
|
||||||
aborting bool
|
aborting bool
|
||||||
exitCode int
|
exitCode int
|
||||||
@ -115,7 +115,7 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|||||||
delete(containers, id)
|
delete(containers, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cascadeStop {
|
if cascade == api.CascadeStop {
|
||||||
if !aborting {
|
if !aborting {
|
||||||
aborting = true
|
aborting = true
|
||||||
err := stopFn()
|
err := stopFn()
|
||||||
@ -123,14 +123,24 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if event.Type == api.ContainerEventExit {
|
}
|
||||||
if exitCodeFrom == "" {
|
if event.Type == api.ContainerEventExit {
|
||||||
exitCodeFrom = event.Service
|
if cascade == api.CascadeFail && event.ExitCode != 0 {
|
||||||
}
|
exitCodeFrom = event.Service
|
||||||
if exitCodeFrom == event.Service {
|
if !aborting {
|
||||||
exitCode = event.ExitCode
|
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 {
|
if len(containers) == 0 {
|
||||||
// Last container terminated, done
|
// Last container terminated, done
|
||||||
|
@ -134,7 +134,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
|||||||
|
|
||||||
var exitCode int
|
var exitCode int
|
||||||
eg.Go(func() error {
|
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...")
|
fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
|
||||||
return progress.Run(ctx, func(ctx context.Context) error {
|
return progress.Run(ctx, func(ctx context.Context) error {
|
||||||
return s.Stop(ctx, project.Name, api.StopOptions{
|
return s.Stop(ctx, project.Name, api.StopOptions{
|
||||||
|
53
pkg/e2e/cascade_test.go
Normal file
53
pkg/e2e/cascade_test.go
Normal file
@ -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)
|
||||||
|
}
|
8
pkg/e2e/fixtures/cascade/compose.yaml
Normal file
8
pkg/e2e/fixtures/cascade/compose.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
exit:
|
||||||
|
image: alpine
|
||||||
|
command: /bin/true
|
||||||
|
|
||||||
|
fail:
|
||||||
|
image: alpine
|
||||||
|
command: sh -c "sleep 0.1 && /bin/false"
|
Loading…
x
Reference in New Issue
Block a user