diff --git a/api/compose/api.go b/api/compose/api.go index d1b6ddf29..a7156497b 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -85,6 +85,8 @@ type CreateOptions struct { RecreateDependencies string // Inherit reuse anonymous volumes from previous container Inherit bool + // Timeout set delay to wait for container to gracelfuly stop before sending SIGKILL + Timeout *time.Duration } // StartOptions group options of the Start API diff --git a/cli/cmd/compose/pull.go b/cli/cmd/compose/pull.go index d7b8b7ddb..0bbd6e418 100644 --- a/cli/cmd/compose/pull.go +++ b/cli/cmd/compose/pull.go @@ -28,7 +28,8 @@ import ( type pullOptions struct { *projectOptions composeOptions - quiet bool + quiet bool + includeDeps bool } func pullCommand(p *projectOptions) *cobra.Command { @@ -43,6 +44,7 @@ func pullCommand(p *projectOptions) *cobra.Command { }, } cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information") + cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies") return cmd } @@ -57,6 +59,19 @@ func runPull(ctx context.Context, opts pullOptions, services []string) error { return err } + if !opts.includeDeps { + enabled, err := project.GetServices(services...) + if err != nil { + return err + } + for _, s := range project.Services { + if !contains(services, s.Name) { + project.DisabledServices = append(project.DisabledServices, s) + } + } + project.Services = enabled + } + if opts.quiet { return c.ComposeService().Pull(ctx, project) } diff --git a/cli/cmd/compose/run.go b/cli/cmd/compose/run.go index e3a7af70e..4eacd0708 100644 --- a/cli/cmd/compose/run.go +++ b/cli/cmd/compose/run.go @@ -93,7 +93,11 @@ func runRun(ctx context.Context, opts runOptions) error { if err != nil { return err } - project.DisabledServices = append(project.DisabledServices, project.Services...) + for _, s := range project.Services { + if s.Name != opts.Service { + project.DisabledServices = append(project.DisabledServices, s) + } + } project.Services = types.Services{enabled} } diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go index 4416f91b4..a97e0d09c 100644 --- a/cli/cmd/compose/up.go +++ b/cli/cmd/compose/up.go @@ -27,17 +27,17 @@ import ( "syscall" "time" + "github.com/compose-spec/compose-go/types" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" + "github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/context/store" "github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/cli/cmd" "github.com/docker/compose-cli/cli/formatter" - - "github.com/compose-spec/compose-go/types" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" ) // composeOptions hold options common to `up` and `run` to run compose project @@ -69,26 +69,74 @@ type upOptions struct { noInherit bool } -func (o upOptions) recreateStrategy() string { - if o.noRecreate { +func (opts upOptions) recreateStrategy() string { + if opts.noRecreate { return compose.RecreateNever } - if o.forceRecreate { + if opts.forceRecreate { return compose.RecreateForce } return compose.RecreateDiverged } -func (o upOptions) dependenciesRecreateStrategy() string { - if o.noRecreate { +func (opts upOptions) dependenciesRecreateStrategy() string { + if opts.noRecreate { return compose.RecreateNever } - if o.recreateDeps { + if opts.recreateDeps { return compose.RecreateForce } return compose.RecreateDiverged } +func (opts upOptions) GetTimeout() *time.Duration { + if opts.timeChanged { + t := time.Duration(opts.timeout) * time.Second + return &t + } + return nil +} + +func (opts upOptions) apply(project *types.Project, services []string) error { + if opts.noDeps { + enabled, err := project.GetServices(services...) + if err != nil { + return err + } + for _, s := range project.Services { + if !contains(services, s.Name) { + project.DisabledServices = append(project.DisabledServices, s) + } + } + project.Services = enabled + } + + if opts.exitCodeFrom != "" { + _, err := project.GetService(opts.exitCodeFrom) + if err != nil { + return err + } + } + + for _, scale := range opts.scale { + split := strings.Split(scale, "=") + if len(split) != 2 { + return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale) + } + name := split[0] + replicas, err := strconv.Atoi(split[1]) + if err != nil { + return err + } + err = setServiceScale(project, name, replicas) + if err != nil { + return err + } + } + + return nil +} + func upCommand(p *projectOptions, contextType string) *cobra.Command { opts := upOptions{ composeOptions: &composeOptions{ @@ -157,7 +205,7 @@ func runUp(ctx context.Context, opts upOptions, services []string) error { return err } - err = applyScaleOpt(opts.scale, project) + err = opts.apply(project, services) if err != nil { return err } @@ -176,35 +224,11 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro return err } - if opts.noDeps { - enabled, err := project.GetServices(services...) - if err != nil { - return err - } - project.DisabledServices = append(project.DisabledServices, project.Services...) - project.Services = enabled - } - - err = applyScaleOpt(opts.scale, project) + err = opts.apply(project, services) if err != nil { return err } - if opts.exitCodeFrom != "" { - _, err := project.GetService(opts.exitCodeFrom) - if err != nil { - return err - } - } - - if opts.timeChanged { - timeoutValue := types.Duration(time.Duration(opts.timeout) * time.Second) - for i, s := range project.Services { - s.StopGracePeriod = &timeoutValue - project.Services[i] = s - } - } - _, err = progress.Run(ctx, func(ctx context.Context) (string, error) { err := c.ComposeService().Create(ctx, project, compose.CreateOptions{ Services: services, @@ -212,6 +236,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro Recreate: opts.recreateStrategy(), RecreateDependencies: opts.dependenciesRecreateStrategy(), Inherit: !opts.noInherit, + Timeout: opts.GetTimeout(), }) if err != nil { return "", err @@ -282,25 +307,6 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro return err } -func applyScaleOpt(opts []string, project *types.Project) error { - for _, scale := range opts { - split := strings.Split(scale, "=") - if len(split) != 2 { - return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale) - } - name := split[0] - replicas, err := strconv.Atoi(split[1]) - if err != nil { - return err - } - err = setServiceScale(project, name, replicas) - if err != nil { - return err - } - } - return nil -} - func setServiceScale(project *types.Project, name string, replicas int) error { for i, s := range project.Services { if s.Name == name { diff --git a/cli/cmd/compose/up_test.go b/cli/cmd/compose/up_test.go index 03dac57e0..566a68476 100644 --- a/cli/cmd/compose/up_test.go +++ b/cli/cmd/compose/up_test.go @@ -34,7 +34,8 @@ func TestApplyScaleOpt(t *testing.T) { }, }, } - err := applyScaleOpt([]string{"foo=2"}, &p) + opt := upOptions{scale: []string{"foo=2"}} + err := opt.apply(&p, nil) assert.NilError(t, err) foo, err := p.GetService("foo") assert.NilError(t, err) diff --git a/local/compose/convergence.go b/local/compose/convergence.go index aaf8bc0a2..fe0571a56 100644 --- a/local/compose/convergence.go +++ b/local/compose/convergence.go @@ -42,7 +42,7 @@ const ( "Remove the custom name to scale the service.\n" ) -func (s *composeService) ensureScale(ctx context.Context, project *types.Project, service types.ServiceConfig) (*errgroup.Group, []moby.Container, error) { +func (s *composeService) ensureScale(ctx context.Context, project *types.Project, service types.ServiceConfig, timeout *time.Duration) (*errgroup.Group, []moby.Container, error) { cState, err := GetContextContainerState(ctx) if err != nil { return nil, nil, err @@ -73,7 +73,7 @@ func (s *composeService) ensureScale(ctx context.Context, project *types.Project for i := scale; i < len(actual); i++ { container := actual[i] eg.Go(func() error { - err := s.apiClient.ContainerStop(ctx, container.ID, nil) + err := s.apiClient.ContainerStop(ctx, container.ID, timeout) if err != nil { return err } @@ -85,8 +85,8 @@ func (s *composeService) ensureScale(ctx context.Context, project *types.Project return eg, actual, nil } -func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool) error { - eg, actual, err := s.ensureScale(ctx, project, service) +func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { + eg, actual, err := s.ensureScale(ctx, project, service, timeout) if err != nil { return err } @@ -106,7 +106,7 @@ func (s *composeService) ensureService(ctx context.Context, project *types.Proje diverged := container.Labels[configHashLabel] != expected if diverged || recreate == compose.RecreateForce || service.Extensions[extLifecycle] == forceRecreate { eg.Go(func() error { - return s.recreateContainer(ctx, project, service, container, inherit) + return s.recreateContainer(ctx, project, service, container, inherit, timeout) }) continue } @@ -209,10 +209,10 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro return nil } -func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container, inherit bool) error { +func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container, inherit bool, timeout *time.Duration) error { w := progress.ContextWriter(ctx) w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Recreate")) - err := s.apiClient.ContainerStop(ctx, container.ID, nil) + err := s.apiClient.ContainerStop(ctx, container.ID, timeout) if err != nil { return err } diff --git a/local/compose/create.go b/local/compose/create.go index 123502030..f35557d9f 100644 --- a/local/compose/create.go +++ b/local/compose/create.go @@ -102,9 +102,9 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { if contains(opts.Services, service.Name) { - return s.ensureService(c, project, service, opts.Recreate, opts.Inherit) + return s.ensureService(c, project, service, opts.Recreate, opts.Inherit, opts.Timeout) } - return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit) + return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit, opts.Timeout) }) }