Repository: docker/compose-cli
Pull request #1455 (2021-03-23 13:28:12)

Reuse container list on command down
**What I did**
Use cached container list on command down

**Related issue**
Resolves https://github.com/docker/compose-cli/issues/1304


Patch:
diff --git a/local/compose/down.go b/local/compose/down.go
index 0c29f4da..0b33f10e 100644
--- a/local/compose/down.go
+++ b/local/compose/down.go
@@ -38,22 +38,23 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
 	w := progress.ContextWriter(ctx)
 	resourceToRemove := false
 
-	if options.Project == nil {
-		project, err := s.projectFromContainerLabels(ctx, projectName)
-		if err != nil {
-			return err
-		}
-		options.Project = project
-	}
-
 	var containers Containers
 	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-		Filters: filters.NewArgs(projectFilter(options.Project.Name)),
+		Filters: filters.NewArgs(projectFilter(projectName)),
 		All:     true,
 	})
 	if err != nil {
 		return err
 	}
+
+	if options.Project == nil {
+		project, err := s.projectFromContainerLabels(containers, projectName)
+		if err != nil {
+			return err
+		}
+		options.Project = project
+	}
+
 	if len(containers) > 0 {
 		resourceToRemove = true
 	}
@@ -176,18 +177,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
 	return eg.Wait()
 }
 
-func projectFilterListOpt(projectName string) moby.ContainerListOptions {
-	return moby.ContainerListOptions{
-		Filters: filters.NewArgs(projectFilter(projectName)),
-		All:     true,
-	}
-}
-
-func (s *composeService) projectFromContainerLabels(ctx context.Context, projectName string) (*types.Project, error) {
-	containers, err := s.apiClient.ContainerList(ctx, projectFilterListOpt(projectName))
-	if err != nil {
-		return nil, err
-	}
+func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) (*types.Project, error) {
 	fakeProject := &types.Project{
 		Name: projectName,
 	}


Test patch:
diff --git a/local/compose/down_test.go b/local/compose/down_test.go
index 64ed23aa..7182a9ff 100644
--- a/local/compose/down_test.go
+++ b/local/compose/down_test.go
@@ -36,8 +36,8 @@ func TestDown(t *testing.T) {
 	tested.apiClient = api
 
 	ctx := context.Background()
-	api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
-		[]apitypes.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil).Times(2)
+	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
+		[]apitypes.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil)
 
 	api.EXPECT().ContainerStop(ctx, "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(ctx, "456", nil).Return(nil)
@@ -62,8 +62,8 @@ func TestDownRemoveOrphans(t *testing.T) {
 	tested.apiClient = api
 
 	ctx := context.Background()
-	api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
-		[]apitypes.Container{testContainer("service1", "123"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil).Times(2)
+	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
+		[]apitypes.Container{testContainer("service1", "123"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil)
 
 	api.EXPECT().ContainerStop(ctx, "123", nil).Return(nil)
 	api.EXPECT().ContainerStop(ctx, "789", nil).Return(nil)
diff --git a/local/compose/kill_test.go b/local/compose/kill_test.go
index 5b6d78aa..0149b579 100644
--- a/local/compose/kill_test.go
+++ b/local/compose/kill_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"testing"
 
+	"github.com/docker/docker/api/types/filters"
 	"github.com/golang/mock/gomock"
 	"gotest.tools/v3/assert"
 
@@ -43,7 +44,7 @@ func TestKillAll(t *testing.T) {
 	project := types.Project{Name: testProject, Services: []types.ServiceConfig{testService("service1"), testService("service2")}}
 
 	ctx := context.Background()
-	api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
+	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
 		[]apitypes.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789")}, nil)
 	api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
 	api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
@@ -62,7 +63,7 @@ func TestKillSignal(t *testing.T) {
 	project := types.Project{Name: testProject, Services: []types.ServiceConfig{testService("service1")}}
 
 	ctx := context.Background()
-	api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return([]apitypes.Container{testContainer("service1", "123")}, nil)
+	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]apitypes.Container{testContainer("service1", "123")}, nil)
 	api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
 
 	err := tested.Kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
@@ -90,3 +91,10 @@ func anyCancellableContext() gomock.Matcher {
 	cancel()
 	return gomock.AssignableToTypeOf(ctxWithCancel)
 }
+
+func projectFilterListOpt() apitypes.ContainerListOptions {
+	return apitypes.ContainerListOptions{
+		Filters: filters.NewArgs(projectFilter(testProject)),
+		All:     true,
+	}
+}
diff --git a/local/compose/stop_test.go b/local/compose/stop_test.go
index 1afce2d4..ea15b8a9 100644
--- a/local/compose/stop_test.go
+++ b/local/compose/stop_test.go
@@ -21,9 +21,10 @@ import (
 	"testing"
 	"time"
 
+	moby "github.com/docker/docker/api/types"
+
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/local/mocks"
-	moby "github.com/docker/docker/api/types"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/golang/mock/gomock"
@@ -37,7 +38,7 @@ func TestStopTimeout(t *testing.T) {
 	tested.apiClient = api
 
 	ctx := context.Background()
-	api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
+	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
 		[]moby.Container{
 			testContainer("service1", "123"),
 			testContainer("service1", "456"),