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(compose_up): add --abort-on-container-exit flag #2873

Merged
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
29 changes: 19 additions & 10 deletions cmd/nerdctl/compose_up.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func newComposeUpCommand() *cobra.Command {
SilenceUsage: true,
SilenceErrors: true,
}
composeUpCommand.Flags().BoolP("detach", "d", false, "Detached mode: Run containers in the background")
composeUpCommand.Flags().Bool("abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d.")
composeUpCommand.Flags().BoolP("detach", "d", false, "Detached mode: Run containers in the background. Incompatible with --abort-on-container-exit.")
composeUpCommand.Flags().Bool("no-build", false, "Don't build an image, even if it's missing.")
composeUpCommand.Flags().Bool("no-color", false, "Produce monochrome output")
composeUpCommand.Flags().Bool("no-log-prefix", false, "Don't print prefix in logs")
Expand All @@ -57,6 +58,13 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
if err != nil {
return err
}
abortOnContainerExit, err := cmd.Flags().GetBool("abort-on-container-exit")
if detach && abortOnContainerExit {
return fmt.Errorf("--abort-on-container-exit flag is incompatible with flag --detach")
}
if err != nil {
return err
}
noBuild, err := cmd.Flags().GetBool("no-build")
if err != nil {
return err
Expand Down Expand Up @@ -121,15 +129,16 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
}

uo := composer.UpOptions{
Detach: detach,
NoBuild: noBuild,
NoColor: noColor,
NoLogPrefix: noLogPrefix,
ForceBuild: build,
IPFS: enableIPFS,
QuietPull: quietPull,
RemoveOrphans: removeOrphans,
Scale: scale,
AbortOnContainerExit: abortOnContainerExit,
Detach: detach,
NoBuild: noBuild,
NoColor: noColor,
NoLogPrefix: noLogPrefix,
ForceBuild: build,
IPFS: enableIPFS,
QuietPull: quietPull,
RemoveOrphans: removeOrphans,
Scale: scale,
}
return c.Up(ctx, uo, services)
}
45 changes: 45 additions & 0 deletions cmd/nerdctl/compose_up_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"

"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
)

func TestComposeUp(t *testing.T) {
Expand Down Expand Up @@ -585,3 +586,47 @@ services:
psCmd.AssertOutContains(serviceRegular)
psCmd.AssertOutNotContains(serviceProfiled)
}

func TestComposeUpAbortOnContainerExit(t *testing.T) {
base := testutil.NewBase(t)
serviceRegular := "regular"
serviceProfiled := "exited"
dockerComposeYAML := fmt.Sprintf(`
services:
%s:
image: %s
ports:
- 8080:80
%s:
image: %s
entrypoint: /bin/sh -c "exit 1"
`, serviceRegular, testutil.NginxAlpineImage, serviceProfiled, testutil.BusyboxImage)
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()

// here we run 'compose up --abort-on-container-exit' command
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "--abort-on-container-exit").AssertExitCode(1)
time.Sleep(3 * time.Second)
psCmd := base.Cmd("ps", "-a", "--format={{.Names}}", "--filter", "status=exited")

psCmd.AssertOutContains(serviceRegular)
psCmd.AssertOutContains(serviceProfiled)
base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()

// this time we run 'compose up' command without --abort-on-container-exit flag
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK()
time.Sleep(3 * time.Second)
psCmd = base.Cmd("ps", "-a", "--format={{.Names}}", "--filter", "status=exited")

// this time the regular service should not be listed in the output
psCmd.AssertOutNotContains(serviceRegular)
psCmd.AssertOutContains(serviceProfiled)
base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK()

// in this sub-test we are ensuring that flags '-d' and '--abort-on-container-exit' cannot be ran together
c := base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "--abort-on-container-exit")
expected := icmd.Expected{
ExitCode: 1,
}
c.Assert(expected)
}
3 changes: 2 additions & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1383,7 +1383,8 @@ Usage: `nerdctl compose up [OPTIONS] [SERVICE...]`

Flags:

- :whale: `-d, --detach`: Detached mode: Run containers in the background
- :whale: `--abort-on-container-exit`: Stops all containers if any container was stopped. Incompatible with `-d`.
- :whale: `-d, --detach`: Detached mode: Run containers in the background. Incompatible with `--abort-on-container-exit`.
- :whale: `--no-build`: Don't build an image, even if it's missing.
- :whale: `--no-color`: Produce monochrome output
- :whale: `--no-log-prefix`: Don't print prefix in logs
Expand Down
21 changes: 15 additions & 6 deletions pkg/composer/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package composer

import (
"context"
"fmt"
"os"
"os/exec"
"os/signal"
Expand All @@ -32,11 +33,12 @@ import (
)

type LogsOptions struct {
Follow bool
Timestamps bool
Tail string
NoColor bool
NoLogPrefix bool
AbortOnContainerExit bool
Follow bool
Timestamps bool
Tail string
NoColor bool
NoLogPrefix bool
}

func (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string) error {
Expand Down Expand Up @@ -133,6 +135,7 @@ func (c *Composer) logs(ctx context.Context, containers []containerd.Container,
signal.Notify(interruptChan, os.Interrupt)

logsEOFMap := make(map[string]struct{}) // key: container name
var containerError error
selectLoop:
for {
// Wait for Ctrl-C, or `nerdctl compose down` in another terminal
Expand All @@ -144,6 +147,12 @@ selectLoop:
if lo.Follow {
// When `nerdctl logs -f` has exited, we can assume that the container has exited
log.G(ctx).Infof("Container %q exited", containerName)
// In case a container has exited and the parameter --abort-on-container-exit,
// we break the loop and set an error, so we can exit the program with 1
if lo.AbortOnContainerExit {
containerError = fmt.Errorf("container %q exited", containerName)
break selectLoop
}
} else {
log.G(ctx).Debugf("Logs for container %q reached EOF", containerName)
}
Expand All @@ -167,5 +176,5 @@ selectLoop:
}
}

return nil
return containerError
}
19 changes: 10 additions & 9 deletions pkg/composer/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ import (
)

type UpOptions struct {
Detach bool
NoBuild bool
NoColor bool
NoLogPrefix bool
ForceBuild bool
IPFS bool
QuietPull bool
RemoveOrphans bool
Scale map[string]uint64 // map of service name to replicas
AbortOnContainerExit bool
Detach bool
NoBuild bool
NoColor bool
NoLogPrefix bool
ForceBuild bool
IPFS bool
QuietPull bool
RemoveOrphans bool
Scale map[string]uint64 // map of service name to replicas
}

func (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) error {
Expand Down
12 changes: 9 additions & 3 deletions pkg/composer/up_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ func (c *Composer) upServices(ctx context.Context, parsedServices []*servicepars
return nil
}

// this is used to stop containers in case --abort-on-container-exit flag is set.
// c.Logs returns an error, so we don't need Ctrl-c to reach the "Stopping containers (forcibly)"
if uo.AbortOnContainerExit {
defer c.stopContainersFromParsedServices(ctx, containers)
}
log.G(ctx).Info("Attaching to logs")
lo := LogsOptions{
Follow: true,
NoColor: uo.NoColor,
NoLogPrefix: uo.NoLogPrefix,
AbortOnContainerExit: uo.AbortOnContainerExit,
Follow: true,
NoColor: uo.NoColor,
NoLogPrefix: uo.NoLogPrefix,
}
if err := c.Logs(ctx, lo, services); err != nil {
return err
Expand Down
Loading