From f6e96dd783dd57a58f9f51c05d9659a3209ab995 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 11 Apr 2022 10:58:20 +0200 Subject: [PATCH] if command is ran with a compose file, apply the compose model, not just project name Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 14 ++++++++++++++ cmd/compose/down.go | 13 +++---------- cmd/compose/events.go | 4 ++-- cmd/compose/kill.go | 4 ++-- cmd/compose/pause.go | 10 ++++++---- cmd/compose/remove.go | 8 +++++--- cmd/compose/restart.go | 5 +++-- cmd/compose/start.go | 5 +++-- cmd/compose/stop.go | 5 +++-- pkg/api/api.go | 12 +++++++++++- pkg/compose/compose.go | 20 -------------------- pkg/compose/down.go | 7 +++++-- pkg/compose/down_test.go | 6 +++--- pkg/compose/kill_test.go | 11 +++++++++-- pkg/compose/pause.go | 12 ++++++++++-- pkg/compose/remove.go | 6 +++++- pkg/compose/restart.go | 20 +++++++++----------- pkg/compose/stop.go | 24 +++++++++++++++++++----- pkg/compose/stop_test.go | 8 +++++++- pkg/e2e/compose_run_test.go | 3 ++- 20 files changed, 121 insertions(+), 76 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 2db045017..c15241415 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -136,6 +136,20 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) { _ = f.MarkHidden("workdir") } +func (o *projectOptions) projectOrName() (*types.Project, string, error) { + name := o.ProjectName + var project *types.Project + if o.ProjectName == "" { + p, err := o.toProject(nil) + if err != nil { + return nil, "", err + } + project = p + name = p.Name + } + return project, name, nil +} + func (o *projectOptions) toProjectName() (string, error) { if o.ProjectName != "" { return o.ProjectName, nil diff --git a/cmd/compose/down.go b/cmd/compose/down.go index 9907f4cc6..cc14e25df 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -22,7 +22,6 @@ import ( "os" "time" - "github.com/compose-spec/compose-go/types" "github.com/docker/compose/v2/pkg/utils" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -79,15 +78,9 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runDown(ctx context.Context, backend api.Service, opts downOptions) error { - name := opts.ProjectName - var project *types.Project - if opts.ProjectName == "" { - p, err := opts.toProject(nil) - if err != nil { - return err - } - project = p - name = p.Name + project, name, err := opts.projectOrName() + if err != nil { + return err } var timeout *time.Duration diff --git a/cmd/compose/events.go b/cmd/compose/events.go index 1731f6bac..f50605b4f 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -51,12 +51,12 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error { - project, err := opts.toProjectName() + name, err := opts.toProjectName() if err != nil { return err } - return backend.Events(ctx, project, api.EventsOptions{ + return backend.Events(ctx, name, api.EventsOptions{ Services: services, Consumer: func(event api.Event) error { if opts.json { diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index 7ea5b6cb9..6a96dee09 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -49,12 +49,12 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error { - projectName, err := opts.toProjectName() + name, err := opts.toProjectName() if err != nil { return err } - return backend.Kill(ctx, projectName, api.KillOptions{ + return backend.Kill(ctx, name, api.KillOptions{ Services: services, Signal: opts.signal, }) diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go index f51f47c7a..9afbcaec4 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -44,13 +44,14 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error { - project, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } - return backend.Pause(ctx, project, api.PauseOptions{ + return backend.Pause(ctx, name, api.PauseOptions{ Services: services, + Project: project, }) } @@ -74,12 +75,13 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error { - project, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } - return backend.UnPause(ctx, project, api.PauseOptions{ + return backend.UnPause(ctx, name, api.PauseOptions{ Services: services, + Project: project, }) } diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go index 2fa933057..2c1152494 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -59,23 +59,25 @@ Any data which is not in a volume will be lost.`, } func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error { - project, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } if opts.stop { - err := backend.Stop(ctx, project, api.StopOptions{ + err := backend.Stop(ctx, name, api.StopOptions{ Services: services, + Project: project, }) if err != nil { return err } } - return backend.Remove(ctx, project, api.RemoveOptions{ + return backend.Remove(ctx, name, api.RemoveOptions{ Services: services, Force: opts.force, Volumes: opts.volumes, + Project: project, }) } diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index e8b786d22..2bb8c8f3e 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -49,14 +49,15 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error { - projectName, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } timeout := time.Duration(opts.timeout) * time.Second - return backend.Restart(ctx, projectName, api.RestartOptions{ + return backend.Restart(ctx, name, api.RestartOptions{ Timeout: &timeout, Services: services, + Project: project, }) } diff --git a/cmd/compose/start.go b/cmd/compose/start.go index e9b67ae44..45da76122 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -43,12 +43,13 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error { - projectName, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } - return backend.Start(ctx, projectName, api.StartOptions{ + return backend.Start(ctx, name, api.StartOptions{ AttachTo: services, + Project: project, }) } diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index b13163514..484bed824 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command { } func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error { - projectName, err := opts.toProjectName() + project, name, err := opts.projectOrName() if err != nil { return err } @@ -63,8 +63,9 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service timeoutValue := time.Duration(opts.timeout) * time.Second timeout = &timeoutValue } - return backend.Stop(ctx, projectName, api.StopOptions{ + return backend.Stop(ctx, name, api.StopOptions{ Timeout: timeout, Services: services, + Project: project, }) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 30b1ac06e..947522854 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -117,7 +117,7 @@ type CreateOptions struct { // StartOptions group options of the Start API type StartOptions struct { - // Project is the compose project used to define this app. Might be nil if user ran `start` just with project name + // Project is the compose project used to define this app. Might be nil if user ran command just with project name Project *types.Project // Attach to container and forward logs if not nil Attach LogConsumer @@ -133,6 +133,8 @@ type StartOptions struct { // RestartOptions group options of the Restart API type RestartOptions struct { + // Project is the compose project used to define this app. Might be nil if user ran command just with project name + Project *types.Project // Timeout override container restart timeout Timeout *time.Duration // Services passed in the command line to be restarted @@ -141,6 +143,8 @@ type RestartOptions struct { // StopOptions group options of the Stop API type StopOptions struct { + // Project is the compose project used to define this app. Might be nil if user ran command just with project name + Project *types.Project // Timeout override container stop timeout Timeout *time.Duration // Services passed in the command line to be stopped @@ -201,6 +205,8 @@ type KillOptions struct { // RemoveOptions group options of the Remove API type RemoveOptions struct { + // Project is the compose project used to define this app. Might be nil if user ran command just with project name + Project *types.Project // DryRun just list removable resources DryRun bool // Volumes remove anonymous volumes @@ -213,6 +219,8 @@ type RemoveOptions struct { // RunOptions group options of the Run API type RunOptions struct { + // Project is the compose project used to define this app. Might be nil if user ran command just with project name + Project *types.Project Name string Service string Command []string @@ -377,6 +385,8 @@ type LogOptions struct { type PauseOptions struct { // Services passed in the command line to be started Services []string + // Project is the compose project used to define this app. Might be nil if user ran command just with project name + Project *types.Project } const ( diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index bfd4bdbf1..6d2c4021c 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -172,26 +172,6 @@ SERVICES: return project, nil } -// actualState list resources labelled by projectName to rebuild compose project model -func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) { - var containers Containers - // don't filter containers by options.Services so projectFromName can rebuild project with all existing resources - containers, err := s.getContainers(ctx, projectName, oneOffInclude, true) - if err != nil { - return nil, nil, err - } - - project, err := s.projectFromName(containers, projectName, services...) - if err != nil && !api.IsNotFoundError(err) { - return nil, nil, err - } - - if len(services) > 0 { - containers = containers.filter(isService(services...)) - } - return containers, project, nil -} - func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) { volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) if err != nil { diff --git a/pkg/compose/down.go b/pkg/compose/down.go index 4251a8dc1..cd5af07bb 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -45,8 +45,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a w := progress.ContextWriter(ctx) resourceToRemove := false - var containers Containers - containers, err := s.getContainers(ctx, projectName, oneOffInclude, true) + include := oneOffExclude + if options.RemoveOrphans { + include = oneOffInclude + } + containers, err := s.getContainers(ctx, projectName, include, true) if err != nil { return err } diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go index ea9527939..0111fdea8 100644 --- a/pkg/compose/down_test.go +++ b/pkg/compose/down_test.go @@ -40,7 +40,7 @@ func TestDown(t *testing.T) { tested.dockerCli = cli cli.EXPECT().Client().Return(api).AnyTimes() - api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( + api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return( []moby.Container{ testContainer("service1", "123", false), testContainer("service2", "456", false), @@ -88,7 +88,7 @@ func TestDownRemoveOrphans(t *testing.T) { tested.dockerCli = cli cli.EXPECT().Client().Return(api).AnyTimes() - api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( + api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(true)).Return( []moby.Container{ testContainer("service1", "123", false), testContainer("service2", "789", false), @@ -125,7 +125,7 @@ func TestDownRemoveVolumes(t *testing.T) { tested.dockerCli = cli cli.EXPECT().Client().Return(api).AnyTimes() - api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( + api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return( []moby.Container{testContainer("service1", "123", false)}, nil) api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))). Return(volume.VolumeListOKBody{ diff --git a/pkg/compose/kill_test.go b/pkg/compose/kill_test.go index 9680afe30..1455b6c2d 100644 --- a/pkg/compose/kill_test.go +++ b/pkg/compose/kill_test.go @@ -18,6 +18,7 @@ package compose import ( "context" + "fmt" "path/filepath" "strings" "testing" @@ -110,9 +111,15 @@ func anyCancellableContext() gomock.Matcher { return gomock.AssignableToTypeOf(ctxWithCancel) } -func projectFilterListOpt() moby.ContainerListOptions { +func projectFilterListOpt(withOneOff bool) moby.ContainerListOptions { + filter := filters.NewArgs( + projectFilter(strings.ToLower(testProject)), + ) + if !withOneOff { + filter.Add("label", fmt.Sprintf("%s=False", compose.OneoffLabel)) + } return moby.ContainerListOptions{ - Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))), + Filters: filter, All: true, } } diff --git a/pkg/compose/pause.go b/pkg/compose/pause.go index 3ea593eef..02434b4f9 100644 --- a/pkg/compose/pause.go +++ b/pkg/compose/pause.go @@ -33,12 +33,16 @@ func (s *composeService) Pause(ctx context.Context, projectName string, options }) } -func (s *composeService) pause(ctx context.Context, project string, options api.PauseOptions) error { - containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...) +func (s *composeService) pause(ctx context.Context, projectName string, options api.PauseOptions) error { + containers, err := s.getContainers(ctx, projectName, oneOffExclude, false, options.Services...) if err != nil { return err } + if options.Project != nil { + containers = containers.filter(isService(options.Project.ServiceNames()...)) + } + w := progress.ContextWriter(ctx) eg, ctx := errgroup.WithContext(ctx) containers.forEach(func(container moby.Container) { @@ -67,6 +71,10 @@ func (s *composeService) unPause(ctx context.Context, projectName string, option return err } + if options.Project != nil { + containers = containers.filter(isService(options.Project.ServiceNames()...)) + } + w := progress.ContextWriter(ctx) eg, ctx := errgroup.WithContext(ctx) containers.forEach(func(container moby.Container) { diff --git a/pkg/compose/remove.go b/pkg/compose/remove.go index cc10b71a9..557572b64 100644 --- a/pkg/compose/remove.go +++ b/pkg/compose/remove.go @@ -31,7 +31,7 @@ import ( func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error { projectName = strings.ToLower(projectName) - containers, _, err := s.actualState(ctx, projectName, options.Services) + containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...) if err != nil { if api.IsNotFoundError(err) { fmt.Fprintln(s.stderr(), "No stopped containers") @@ -40,6 +40,10 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options return err } + if options.Project != nil { + containers = containers.filter(isService(options.Project.ServiceNames()...)) + } + stoppedContainers := containers.filter(func(c moby.Container) bool { return c.State != ContainerRunning }) diff --git a/pkg/compose/restart.go b/pkg/compose/restart.go index 87d236cc8..9b3178e3c 100644 --- a/pkg/compose/restart.go +++ b/pkg/compose/restart.go @@ -34,15 +34,17 @@ func (s *composeService) Restart(ctx context.Context, projectName string, option } func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error { - - observedState, err := s.getContainers(ctx, projectName, oneOffExclude, true) + containers, err := s.getContainers(ctx, projectName, oneOffExclude, true) if err != nil { return err } - project, err := s.projectFromName(observedState, projectName, options.Services...) - if err != nil { - return err + project := options.Project + if project == nil { + project, err = s.getProjectWithResources(ctx, containers, projectName) + if err != nil { + return err + } } if len(options.Services) == 0 { @@ -50,12 +52,12 @@ func (s *composeService) restart(ctx context.Context, projectName string, option } w := progress.ContextWriter(ctx) - err = InDependencyOrder(ctx, project, func(c context.Context, service string) error { + return InDependencyOrder(ctx, project, func(c context.Context, service string) error { if !utils.StringContains(options.Services, service) { return nil } eg, ctx := errgroup.WithContext(ctx) - for _, container := range observedState.filter(isService(service)) { + for _, container := range containers.filter(isService(service)) { container := container eg.Go(func() error { eventName := getContainerProgressName(container) @@ -69,8 +71,4 @@ func (s *composeService) restart(ctx context.Context, projectName string, option } return eg.Wait() }) - if err != nil { - return err - } - return nil } diff --git a/pkg/compose/stop.go b/pkg/compose/stop.go index d17e01a94..971b7e998 100644 --- a/pkg/compose/stop.go +++ b/pkg/compose/stop.go @@ -22,6 +22,7 @@ import ( "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" + "github.com/docker/compose/v2/pkg/utils" ) func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error { @@ -31,15 +32,28 @@ func (s *composeService) Stop(ctx context.Context, projectName string, options a } func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error { - w := progress.ContextWriter(ctx) - - containers, project, err := s.actualState(ctx, projectName, options.Services) + containers, err := s.getContainers(ctx, projectName, oneOffExclude, true) if err != nil { return err } + project := options.Project + if project == nil { + project, err = s.getProjectWithResources(ctx, containers, projectName) + if err != nil { + return err + } + } + + if len(options.Services) == 0 { + options.Services = project.ServiceNames() + } + + w := progress.ContextWriter(ctx) return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error { - containersToStop := containers.filter(isService(service)).filter(isNotOneOff) - return s.stopContainers(ctx, w, containersToStop, options.Timeout) + if !utils.StringContains(options.Services, service) { + return nil + } + return s.stopContainers(ctx, w, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout) }) } diff --git a/pkg/compose/stop_test.go b/pkg/compose/stop_test.go index e5848780e..97a83356e 100644 --- a/pkg/compose/stop_test.go +++ b/pkg/compose/stop_test.go @@ -26,6 +26,8 @@ import ( "github.com/docker/compose/v2/pkg/mocks" moby "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" "github.com/golang/mock/gomock" "gotest.tools/v3/assert" ) @@ -40,12 +42,16 @@ func TestStopTimeout(t *testing.T) { cli.EXPECT().Client().Return(api).AnyTimes() ctx := context.Background() - api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( + api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return( []moby.Container{ testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false), }, nil) + api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))). + Return(volume.VolumeListOKBody{}, nil) + api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}). + Return([]moby.NetworkResource{}, nil) timeout := time.Duration(2) * time.Second api.EXPECT().ContainerStop(gomock.Any(), "123", &timeout).Return(nil) diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go index e1abb581e..4df783251 100644 --- a/pkg/e2e/compose_run_test.go +++ b/pkg/e2e/compose_run_test.go @@ -79,7 +79,7 @@ func TestLocalComposeRun(t *testing.T) { }) t.Run("down", func(t *testing.T) { - c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down") + c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down", "--remove-orphans") res := c.RunDockerCmd(t, "ps", "--all") assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) }) @@ -121,6 +121,7 @@ func TestLocalComposeRun(t *testing.T) { c.Env = append(c.Env, "COMPOSE_REMOVE_ORPHANS=True") }) res := c.RunDockerCmd(t, "ps", "--all") + assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) })