From 98187b2f624696a9677b3357760148ac856d48ba Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 29 Apr 2021 21:54:54 +0200 Subject: [PATCH 1/2] down --volume to remove volume Signed-off-by: Nicolas De Loof --- local/compose/down.go | 29 +++++++++++++++++++++++++++++ local/compose/down_test.go | 25 +++++++++++++++++++++++-- local/e2e/compose/volumes_test.go | 6 ++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/local/compose/down.go b/local/compose/down.go index 98184733..b3929b03 100644 --- a/local/compose/down.go +++ b/local/compose/down.go @@ -98,6 +98,20 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c } } + if options.Volumes { + networks, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) + if err != nil { + return err + } + for _, vol := range networks.Volumes { + id := vol.Name + eg.Go(func() error { + resourceToRemove = true + return s.removeVolume(ctx, id, w) + }) + } + } + if !resourceToRemove { w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove")) } @@ -134,6 +148,21 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre return err } +func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error { + resource := fmt.Sprintf("Volume %s", id) + w.Event(progress.NewEvent(resource, progress.Working, "Removing")) + err := s.apiClient.VolumeRemove(ctx, id, true) + if err == nil { + w.Event(progress.NewEvent(resource, progress.Done, "Removed")) + return nil + } + if errdefs.IsNotFound(err) { + w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove")) + return nil + } + return err +} + func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error { for _, container := range containers { toStop := container diff --git a/local/compose/down_test.go b/local/compose/down_test.go index 00282b02..b4bc37b0 100644 --- a/local/compose/down_test.go +++ b/local/compose/down_test.go @@ -20,12 +20,12 @@ import ( "context" "testing" - "github.com/docker/docker/api/types/filters" - "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/local/mocks" apitypes "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" ) @@ -79,3 +79,24 @@ func TestDownRemoveOrphans(t *testing.T) { err := tested.Down(context.Background(), testProject, compose.DownOptions{RemoveOrphans: true}) assert.NilError(t, err) } + +func TestDownRemoveVolumes(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + api := mocks.NewMockAPIClient(mockCtrl) + tested.apiClient = api + + api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( + []apitypes.Container{testContainer("service1", "123")}, nil) + + api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) + api.EXPECT().ContainerRemove(gomock.Any(), "123", apitypes.ContainerRemoveOptions{Force: true}).Return(nil) + + api.EXPECT().NetworkList(gomock.Any(), apitypes.NetworkListOptions{Filters: filters.NewArgs(projectFilter(testProject))}).Return(nil, nil) + + api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(testProject))).Return(volume.VolumeListOKBody{Volumes: []*apitypes.Volume{{Name: "myProject_volume"}}}, nil) + api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil) + + err := tested.Down(context.Background(), testProject, compose.DownOptions{Volumes: true}) + assert.NilError(t, err) +} diff --git a/local/e2e/compose/volumes_test.go b/local/e2e/compose/volumes_test.go index ecf0bfd8..93557736 100644 --- a/local/e2e/compose/volumes_test.go +++ b/local/e2e/compose/volumes_test.go @@ -72,7 +72,9 @@ func TestLocalComposeVolume(t *testing.T) { }) t.Run("cleanup volume project", func(t *testing.T) { - c.RunDockerCmd("compose", "--project-name", projectName, "down") - c.RunDockerCmd("volume", "rm", projectName+"_staticVol") + c.RunDockerCmd("compose", "--project-name", projectName, "down", "--volumes") + res := c.RunDockerCmd("volume", "ls") + assert.Assert(t, !strings.Contains(res.Stdout(), projectName+"_staticVol")) + assert.Assert(t, !strings.Contains(res.Stdout(), "myvolume")) }) } From deecf764212f82d165e7a290cc38a79ae87e4880 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 30 Apr 2021 09:35:09 +0200 Subject: [PATCH 2/2] reduce code complexity with sub-functions Signed-off-by: Nicolas De Loof --- local/compose/create.go | 2 +- local/compose/down.go | 81 +++++++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/local/compose/create.go b/local/compose/create.go index 56b8adad..6deefa78 100644 --- a/local/compose/create.go +++ b/local/compose/create.go @@ -938,7 +938,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi return nil } -func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error { +func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error { w := progress.ContextWriter(ctx) eventName := fmt.Sprintf("Network %s", networkName) w.Event(progress.RemovingEvent(eventName)) diff --git a/local/compose/down.go b/local/compose/down.go index b3929b03..73582c36 100644 --- a/local/compose/down.go +++ b/local/compose/down.go @@ -33,6 +33,8 @@ import ( "github.com/docker/compose-cli/api/progress" ) +type downOp func() error + func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { w := progress.ContextWriter(ctx) resourceToRemove := false @@ -73,51 +75,76 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c } } - networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))}) + ops, err := s.ensureNetwoksDown(ctx, projectName) if err != nil { return err } - eg, _ := errgroup.WithContext(ctx) - for _, n := range networks { - resourceToRemove = true - networkID := n.ID - networkName := n.Name - eg.Go(func() error { - return s.ensureNetworkDown(ctx, networkID, networkName) - }) - } - if options.Images != "" { - for image := range s.getServiceImages(options, projectName) { - image := image - eg.Go(func() error { - resourceToRemove = true - return s.removeImage(ctx, image, w) - }) - } + ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...) } if options.Volumes { - networks, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) + rm, err := s.ensureVolumesDown(ctx, projectName, w) if err != nil { return err } - for _, vol := range networks.Volumes { - id := vol.Name - eg.Go(func() error { - resourceToRemove = true - return s.removeVolume(ctx, id, w) - }) - } + ops = append(ops, rm...) } - if !resourceToRemove { + if !resourceToRemove && len(ops) == 0 { w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove")) } + + eg, _ := errgroup.WithContext(ctx) + for _, op := range ops { + eg.Go(op) + } return eg.Wait() } +func (s *composeService) ensureVolumesDown(ctx context.Context, projectName string, w progress.Writer) ([]downOp, error) { + var ops []downOp + volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) + if err != nil { + return ops, err + } + for _, vol := range volumes.Volumes { + id := vol.Name + ops = append(ops, func() error { + return s.removeVolume(ctx, id, w) + }) + } + return ops, nil +} + +func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options compose.DownOptions, w progress.Writer) []downOp { + var ops []downOp + for image := range s.getServiceImages(options, projectName) { + image := image + ops = append(ops, func() error { + return s.removeImage(ctx, image, w) + }) + } + return ops +} + +func (s *composeService) ensureNetwoksDown(ctx context.Context, projectName string) ([]downOp, error) { + var ops []downOp + networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))}) + if err != nil { + return ops, err + } + for _, n := range networks { + networkID := n.ID + networkName := n.Name + ops = append(ops, func() error { + return s.removeNetwork(ctx, networkID, networkName) + }) + } + return ops, nil +} + func (s *composeService) getServiceImages(options compose.DownOptions, projectName string) map[string]struct{} { images := map[string]struct{}{} for _, service := range options.Project.Services {