diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index 482e4832a..5f7446e08 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -24,15 +24,15 @@ import ( "io" "strings" - "github.com/docker/compose/v2/pkg/api" - "github.com/pkg/errors" - "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/streams" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" + "github.com/pkg/errors" "github.com/sanathkr/go-yaml" ) @@ -194,3 +194,39 @@ func (s *composeService) actualState(ctx context.Context, projectName string, se } 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 { + return nil, err + } + + actual := types.Volumes{} + for _, vol := range volumes.Volumes { + actual[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{ + Name: vol.Name, + Driver: vol.Driver, + Labels: vol.Labels, + } + } + return actual, nil +} + +func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) { + networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{ + Filters: filters.NewArgs(projectFilter(projectName)), + }) + if err != nil { + return nil, err + } + + actual := types.Networks{} + for _, net := range networks { + actual[net.Labels[api.NetworkLabel]] = types.NetworkConfig{ + Name: net.Name, + Driver: net.Driver, + Labels: net.Labels, + } + } + return actual, nil +} diff --git a/pkg/compose/create.go b/pkg/compose/create.go index ac370f5b4..637d4c5ea 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -1078,14 +1078,13 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi return nil } -func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error { - w := progress.ContextWriter(ctx) - eventName := fmt.Sprintf("Network %s", networkName) +func (s *composeService) removeNetwork(ctx context.Context, network string, w progress.Writer) error { + eventName := fmt.Sprintf("Network %s", network) w.Event(progress.RemovingEvent(eventName)) - if err := s.apiClient().NetworkRemove(ctx, networkID); err != nil { + if err := s.apiClient().NetworkRemove(ctx, network); err != nil { w.Event(progress.ErrorEvent(eventName)) - return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID)) + return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", network)) } w.Event(progress.RemovedEvent(eventName)) diff --git a/pkg/compose/down.go b/pkg/compose/down.go index 113034682..d86aefc8a 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -24,7 +24,6 @@ import ( "github.com/compose-spec/compose-go/types" moby "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" "github.com/docker/docker/errdefs" "golang.org/x/sync/errgroup" @@ -41,7 +40,6 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a } func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error { - builtFromResources := options.Project == nil w := progress.ContextWriter(ctx) resourceToRemove := false @@ -51,8 +49,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a return err } - if builtFromResources { - options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName) + project := options.Project + if project == nil { + project, err = s.getProjectWithResources(ctx, containers, projectName) if err != nil { return err } @@ -62,7 +61,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a resourceToRemove = true } - err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service string) error { + err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error { serviceContainers := containers.filter(isService(service)) err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes) return err @@ -71,7 +70,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a return err } - orphans := containers.filter(isNotService(options.Project.ServiceNames()...)) + orphans := containers.filter(isNotService(project.ServiceNames()...)) if options.RemoveOrphans && len(orphans) > 0 { err := s.removeContainers(ctx, w, orphans, options.Timeout, false) if err != nil { @@ -79,17 +78,14 @@ func (s *composeService) down(ctx context.Context, projectName string, options a } } - ops, err := s.ensureNetworksDown(ctx, projectName) - if err != nil { - return err - } + ops := s.ensureNetworksDown(ctx, project, w) if options.Images != "" { ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...) } if options.Volumes { - ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...) + ops = append(ops, s.ensureVolumesDown(ctx, project, w)...) } if !resourceToRemove && len(ops) == 0 { @@ -106,6 +102,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp { var ops []downOp for _, vol := range project.Volumes { + if vol.External.External { + continue + } volumeName := vol.Name ops = append(ops, func() error { return s.removeVolume(ctx, volumeName, w) @@ -125,20 +124,18 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin return ops } -func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) { +func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp { 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 + for _, n := range project.Networks { + if n.External.External { + continue + } networkName := n.Name ops = append(ops, func() error { - return s.removeNetwork(ctx, networkID, networkName) + return s.removeNetwork(ctx, networkName, w) }) } - return ops, nil + return ops } func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} { @@ -233,21 +230,20 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer return eg.Wait() } -func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) { +func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) { containers = containers.filter(isNotOneOff) project, _ := s.projectFromName(containers, projectName) - volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) + + volumes, err := s.actualVolumes(ctx, projectName) if err != nil { return nil, err } + project.Volumes = volumes - project.Volumes = types.Volumes{} - for _, vol := range volumes.Volumes { - project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{ - Name: vol.Name, - Driver: vol.Driver, - Labels: vol.Labels, - } + networks, err := s.actualNetworks(ctx, projectName) + if err != nil { + return nil, err } + project.Networks = networks return project, nil } diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go index 5f820e30d..e7860fd37 100644 --- a/pkg/compose/down_test.go +++ b/pkg/compose/down_test.go @@ -49,6 +49,8 @@ func TestDown(t *testing.T) { }, 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{{Name: "myProject_default"}}, nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil) @@ -58,9 +60,6 @@ func TestDown(t *testing.T) { api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil) api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil) - api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}}, - nil) - api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil) err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{}) @@ -84,6 +83,8 @@ func TestDownRemoveOrphans(t *testing.T) { }, 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{{Name: "myProject_default"}}, nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil) @@ -93,9 +94,6 @@ func TestDownRemoveOrphans(t *testing.T) { api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil) api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil) - api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}}, - nil) - api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil) err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true}) @@ -117,12 +115,12 @@ func TestDownRemoveVolumes(t *testing.T) { Return(volume.VolumeListOKBody{ Volumes: []*moby.Volume{{Name: "myProject_volume"}}, }, nil) + api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}). + Return(nil, nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil) - api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return(nil, nil) - api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil) err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})