فهرست منبع

introduce cascade stop "--abort-on-container-exit" option

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 4 سال پیش
والد
کامیت
f3d093cb54
8فایلهای تغییر یافته به همراه56 افزوده شده و 13 حذف شده
  1. 12 3
      api/compose/api.go
  2. 1 1
      cli/cmd/compose/start.go
  3. 22 1
      cli/cmd/compose/up.go
  4. 4 3
      cli/formatter/logs.go
  5. 2 2
      ecs/logs.go
  6. 4 1
      local/compose/attach.go
  7. 1 0
      local/compose/containers.go
  8. 10 2
      local/compose/start.go

+ 12 - 3
api/compose/api.go

@@ -67,8 +67,8 @@ type CreateOptions struct {
 type StartOptions struct {
 	// Attach will attach to container and pipe stdout/stderr to LogConsumer
 	Attach LogConsumer
-	// CascadeStop will run `Stop` on any container exit
-	CascadeStop bool
+	// Listener will get notified on container events
+	Listener Listener
 }
 
 // UpOptions group options of the Up API
@@ -185,5 +185,14 @@ type Stack struct {
 // LogConsumer is a callback to process log messages from services
 type LogConsumer interface {
 	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
 }

+ 1 - 1
cli/cmd/compose/start.go

@@ -18,12 +18,12 @@ package compose
 
 import (
 	"context"
-	"github.com/docker/compose-cli/api/compose"
 	"os"
 
 	"github.com/spf13/cobra"
 
 	"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/cli/formatter"
 )

+ 22 - 1
cli/cmd/compose/up.go

@@ -49,6 +49,7 @@ type upOptions struct {
 	forceRecreate bool
 	noRecreate    bool
 	noStart       bool
+	cascadeStop   bool
 }
 
 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 {
 			switch contextType {
 			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 {
 					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.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.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
 	}
 
 	return upCmd
@@ -145,9 +150,25 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
 		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{
-		Attach: formatter.NewLogConsumer(ctx, os.Stdout),
+		Attach:   formatter.NewLogConsumer(ctx, os.Stdout),
+		Listener: listener,
 	})
+
 	if errors.Is(ctx.Err(), context.Canceled) {
 		fmt.Println("Gracefully stopping...")
 		ctx = context.Background()

+ 4 - 3
cli/formatter/logs.go

@@ -51,9 +51,10 @@ func (l *logConsumer) Log(service, container, message string) {
 	}
 }
 
-func (l *logConsumer) Exit(service, container string, exitCode int) {
-	msg := fmt.Sprintf("%s exited with code %d\n", container, exitCode)
-	l.writer.Write([]byte(l.getColorFunc(service)(msg)))
+func (l *logConsumer) Status(service, container, msg string) {
+	cf := l.getColorFunc(service)
+	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 {

+ 2 - 2
ecs/logs.go

@@ -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] {
-		a.delegate.Exit(service, container, exitCode)
+		a.delegate.Status(service, container, message)
 	}
 }

+ 4 - 1
local/compose/attach.go

@@ -44,7 +44,10 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, con
 	fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
 
 	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
 }

+ 1 - 0
local/compose/containers.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+
 	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"

+ 10 - 2
local/compose/start.go

@@ -18,11 +18,12 @@ package compose
 
 import (
 	"context"
-	"github.com/docker/docker/api/types/container"
+	"fmt"
 
 	"github.com/docker/compose-cli/api/compose"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/docker/api/types/container"
 	"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)
 			select {
 			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
 			case err := <-errC:
 				return err