|
@@ -18,18 +18,21 @@ package compose
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"context"
|
|
"context"
|
|
|
- "errors"
|
|
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"os"
|
|
"os"
|
|
|
|
|
+ "os/signal"
|
|
|
"path/filepath"
|
|
"path/filepath"
|
|
|
|
|
+ "syscall"
|
|
|
|
|
|
|
|
"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/compose"
|
|
|
"github.com/docker/compose-cli/api/context/store"
|
|
"github.com/docker/compose-cli/api/context/store"
|
|
|
"github.com/docker/compose-cli/api/progress"
|
|
"github.com/docker/compose-cli/api/progress"
|
|
|
|
|
+ "github.com/docker/compose-cli/cli/cmd"
|
|
|
"github.com/docker/compose-cli/cli/formatter"
|
|
"github.com/docker/compose-cli/cli/formatter"
|
|
|
|
|
|
|
|
"github.com/compose-spec/compose-go/types"
|
|
"github.com/compose-spec/compose-go/types"
|
|
|
|
|
+ "github.com/sirupsen/logrus"
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/cobra"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -49,6 +52,8 @@ type upOptions struct {
|
|
|
forceRecreate bool
|
|
forceRecreate bool
|
|
|
noRecreate bool
|
|
noRecreate bool
|
|
|
noStart bool
|
|
noStart bool
|
|
|
|
|
+ cascadeStop bool
|
|
|
|
|
+ exitCodeFrom string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (o upOptions) recreateStrategy() string {
|
|
func (o upOptions) recreateStrategy() string {
|
|
@@ -73,6 +78,12 @@ 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.exitCodeFrom != "" {
|
|
|
|
|
+ opts.cascadeStop = true
|
|
|
|
|
+ }
|
|
|
|
|
+ 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 +106,8 @@ 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")
|
|
|
|
|
+ flags.StringVar(&opts.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return upCmd
|
|
return upCmd
|
|
@@ -120,6 +133,13 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if opts.exitCodeFrom != "" {
|
|
|
|
|
+ _, err := project.GetService(opts.exitCodeFrom)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
|
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
|
|
err := c.ComposeService().Create(ctx, project, compose.CreateOptions{
|
|
err := c.ComposeService().Create(ctx, project, compose.CreateOptions{
|
|
|
RemoveOrphans: opts.removeOrphans,
|
|
RemoveOrphans: opts.removeOrphans,
|
|
@@ -129,7 +149,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
|
|
|
return "", err
|
|
return "", err
|
|
|
}
|
|
}
|
|
|
if opts.Detach {
|
|
if opts.Detach {
|
|
|
- err = c.ComposeService().Start(ctx, project, nil)
|
|
|
|
|
|
|
+ err = c.ComposeService().Start(ctx, project, compose.StartOptions{})
|
|
|
}
|
|
}
|
|
|
return "", err
|
|
return "", err
|
|
|
})
|
|
})
|
|
@@ -145,13 +165,38 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- err = c.ComposeService().Start(ctx, project, formatter.NewLogConsumer(ctx, os.Stdout))
|
|
|
|
|
- if errors.Is(ctx.Err(), context.Canceled) {
|
|
|
|
|
- fmt.Println("Gracefully stopping...")
|
|
|
|
|
- ctx = context.Background()
|
|
|
|
|
- _, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
|
|
|
|
|
|
+ queue := make(chan compose.ContainerEvent)
|
|
|
|
|
+ printer := printer{
|
|
|
|
|
+ queue: queue,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ stopFunc := func() error {
|
|
|
|
|
+ ctx := context.Background()
|
|
|
|
|
+ _, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
|
|
|
return "", c.ComposeService().Stop(ctx, project)
|
|
return "", c.ComposeService().Stop(ctx, project)
|
|
|
})
|
|
})
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ signalChan := make(chan os.Signal, 1)
|
|
|
|
|
+ signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ <-signalChan
|
|
|
|
|
+ fmt.Println("Gracefully stopping...")
|
|
|
|
|
+ stopFunc() // nolint:errcheck
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ err = c.ComposeService().Start(ctx, project, compose.StartOptions{
|
|
|
|
|
+ Attach: func(event compose.ContainerEvent) {
|
|
|
|
|
+ queue <- event
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ exitCode, err := printer.run(ctx, opts.cascadeStop, opts.exitCodeFrom, stopFunc)
|
|
|
|
|
+ if exitCode != 0 {
|
|
|
|
|
+ return cmd.ExitCodeError{ExitCode: exitCode}
|
|
|
}
|
|
}
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -196,3 +241,37 @@ func setup(ctx context.Context, opts composeOptions, services []string) (*client
|
|
|
|
|
|
|
|
return c, project, nil
|
|
return c, project, nil
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+type printer struct {
|
|
|
|
|
+ queue chan compose.ContainerEvent
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (p printer) run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) { //nolint:unparam
|
|
|
|
|
+ consumer := formatter.NewLogConsumer(ctx, os.Stdout)
|
|
|
|
|
+ var aborting bool
|
|
|
|
|
+ for {
|
|
|
|
|
+ event := <-p.queue
|
|
|
|
|
+ switch event.Type {
|
|
|
|
|
+ case compose.ContainerEventExit:
|
|
|
|
|
+ if !aborting {
|
|
|
|
|
+ consumer.Status(event.Service, event.Source, fmt.Sprintf("exited with code %d", event.ExitCode))
|
|
|
|
|
+ }
|
|
|
|
|
+ if cascadeStop && !aborting {
|
|
|
|
|
+ aborting = true
|
|
|
|
|
+ fmt.Println("Aborting on container exit...")
|
|
|
|
|
+ err := stopFn()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return 0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if exitCodeFrom == "" || exitCodeFrom == event.Service {
|
|
|
|
|
+ logrus.Error(event.ExitCode)
|
|
|
|
|
+ return event.ExitCode, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ case compose.ContainerEventLog:
|
|
|
|
|
+ if !aborting {
|
|
|
|
|
+ consumer.Log(event.Service, event.Source, event.Line)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|