mirror of https://github.com/docker/compose.git
introduce cascade stop "--abort-on-container-exit" option
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
7a7114fb5f
commit
f3d093cb54
|
@ -67,8 +67,8 @@ type CreateOptions struct {
|
||||||
type StartOptions struct {
|
type StartOptions struct {
|
||||||
// Attach will attach to container and pipe stdout/stderr to LogConsumer
|
// Attach will attach to container and pipe stdout/stderr to LogConsumer
|
||||||
Attach LogConsumer
|
Attach LogConsumer
|
||||||
// CascadeStop will run `Stop` on any container exit
|
// Listener will get notified on container events
|
||||||
CascadeStop bool
|
Listener Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpOptions group options of the Up API
|
// UpOptions group options of the Up API
|
||||||
|
@ -185,5 +185,14 @@ type Stack struct {
|
||||||
// LogConsumer is a callback to process log messages from services
|
// LogConsumer is a callback to process log messages from services
|
||||||
type LogConsumer interface {
|
type LogConsumer interface {
|
||||||
Log(service, container, message string)
|
Log(service, container, message string)
|
||||||
Exit(service, container string, exitCode int)
|
Status(service, container, message string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener get notified on container Events
|
||||||
|
type Listener chan Event
|
||||||
|
|
||||||
|
// Event let us know a Container exited
|
||||||
|
type Event struct {
|
||||||
|
Service string
|
||||||
|
Status int
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,12 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/docker/compose-cli/api/compose"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/client"
|
"github.com/docker/compose-cli/api/client"
|
||||||
|
"github.com/docker/compose-cli/api/compose"
|
||||||
"github.com/docker/compose-cli/api/progress"
|
"github.com/docker/compose-cli/api/progress"
|
||||||
"github.com/docker/compose-cli/cli/formatter"
|
"github.com/docker/compose-cli/cli/formatter"
|
||||||
)
|
)
|
||||||
|
|
|
@ -49,6 +49,7 @@ type upOptions struct {
|
||||||
forceRecreate bool
|
forceRecreate bool
|
||||||
noRecreate bool
|
noRecreate bool
|
||||||
noStart bool
|
noStart bool
|
||||||
|
cascadeStop bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o upOptions) recreateStrategy() string {
|
func (o upOptions) recreateStrategy() string {
|
||||||
|
@ -73,6 +74,9 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
switch contextType {
|
switch contextType {
|
||||||
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
|
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
|
||||||
|
if opts.cascadeStop && opts.Detach {
|
||||||
|
return fmt.Errorf("--abort-on-container-exit and --detach are incompatible")
|
||||||
|
}
|
||||||
if opts.forceRecreate && opts.noRecreate {
|
if opts.forceRecreate && opts.noRecreate {
|
||||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||||
}
|
}
|
||||||
|
@ -95,6 +99,7 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
|
||||||
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
||||||
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||||
flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.")
|
flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.")
|
||||||
|
flags.BoolVar(&opts.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
|
||||||
}
|
}
|
||||||
|
|
||||||
return upCmd
|
return upCmd
|
||||||
|
@ -145,9 +150,25 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
listener := make(chan compose.Event)
|
||||||
|
go func() {
|
||||||
|
var aborting bool
|
||||||
|
for {
|
||||||
|
<-listener
|
||||||
|
if opts.cascadeStop && !aborting {
|
||||||
|
aborting = true
|
||||||
|
fmt.Println("Aborting on container exit...")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = c.ComposeService().Start(ctx, project, compose.StartOptions{
|
err = c.ComposeService().Start(ctx, project, compose.StartOptions{
|
||||||
Attach: formatter.NewLogConsumer(ctx, os.Stdout),
|
Attach: formatter.NewLogConsumer(ctx, os.Stdout),
|
||||||
|
Listener: listener,
|
||||||
})
|
})
|
||||||
|
|
||||||
if errors.Is(ctx.Err(), context.Canceled) {
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
fmt.Println("Gracefully stopping...")
|
fmt.Println("Gracefully stopping...")
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
|
|
@ -51,9 +51,10 @@ func (l *logConsumer) Log(service, container, message string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logConsumer) Exit(service, container string, exitCode int) {
|
func (l *logConsumer) Status(service, container, msg string) {
|
||||||
msg := fmt.Sprintf("%s exited with code %d\n", container, exitCode)
|
cf := l.getColorFunc(service)
|
||||||
l.writer.Write([]byte(l.getColorFunc(service)(msg)))
|
buf := bytes.NewBufferString(fmt.Sprintf("%s %s \n", cf(container), cf(msg)))
|
||||||
|
l.writer.Write(buf.Bytes()) // nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logConsumer) getColorFunc(service string) colorFunc {
|
func (l *logConsumer) getColorFunc(service string) colorFunc {
|
||||||
|
|
|
@ -55,8 +55,8 @@ func (a *allowListLogConsumer) Log(service, container, message string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *allowListLogConsumer) Exit(service, container string, exitCode int) {
|
func (a *allowListLogConsumer) Status(service, container, message string) {
|
||||||
if a.allowList[service] {
|
if a.allowList[service] {
|
||||||
a.delegate.Exit(service, container, exitCode)
|
a.delegate.Status(service, container, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,10 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, con
|
||||||
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
s.attachContainer(ctx, container, consumer, project)
|
err := s.attachContainer(ctx, container, consumer, project)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
|
|
@ -18,11 +18,12 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/docker/docker/api/types/container"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/compose"
|
"github.com/docker/compose-cli/api/compose"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,7 +61,14 @@ func (s *composeService) Start(ctx context.Context, project *types.Project, opti
|
||||||
statusC, errC := s.apiClient.ContainerWait(ctx, c.ID, container.WaitConditionNotRunning)
|
statusC, errC := s.apiClient.ContainerWait(ctx, c.ID, container.WaitConditionNotRunning)
|
||||||
select {
|
select {
|
||||||
case status := <-statusC:
|
case status := <-statusC:
|
||||||
options.Attach.Exit(c.Labels[serviceLabel], getContainerNameWithoutProject(c), int(status.StatusCode))
|
service := c.Labels[serviceLabel]
|
||||||
|
options.Attach.Status(service, getContainerNameWithoutProject(c), fmt.Sprintf("exited with code %d", status.StatusCode))
|
||||||
|
if options.Listener != nil {
|
||||||
|
options.Listener <- compose.Event{
|
||||||
|
Service: service,
|
||||||
|
Status: int(status.StatusCode),
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
case err := <-errC:
|
case err := <-errC:
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue