diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go index 13df16f03..addf2ad27 100644 --- a/pkg/compose/pull.go +++ b/pkg/compose/pull.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "encoding/json" "errors" + "fmt" "io" "strings" @@ -64,13 +65,16 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts eg, ctx := errgroup.WithContext(ctx) var mustBuild []string + + imagesBeingPulled := map[string]string{} + for _, service := range project.Services { service := service if service.Image == "" { w.Event(progress.Event{ ID: service.Name, Status: progress.Done, - Text: "Skipped", + Text: "Skipped - No image to be pulled", }) continue } @@ -88,12 +92,32 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts w.Event(progress.Event{ ID: service.Name, Status: progress.Done, - Text: "Exists", + Text: "Skipped - Image is already present locally", + }) + continue + } + default: + if _, ok := images[service.Image]; ok { + w.Event(progress.Event{ + ID: service.Name, + Status: progress.Done, + Text: "Skipped - Image is already present locally", }) continue } } + if s, ok := imagesBeingPulled[service.Image]; ok { + w.Event(progress.Event{ + ID: service.Name, + Status: progress.Done, + Text: fmt.Sprintf("Skipped - Image is already being pulled by %v", s), + }) + continue + } + + imagesBeingPulled[service.Image] = service.Name + eg.Go(func() error { _, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false) if err != nil { diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index d1e613c4d..7f8c46ecf 100644 --- a/pkg/e2e/compose_test.go +++ b/pkg/e2e/compose_test.go @@ -122,11 +122,49 @@ func TestLocalComposeUp(t *testing.T) { func TestComposePull(t *testing.T) { c := NewParallelCLI(t) - res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/simple-composefile", "pull") - output := res.Combined() + t.Run("Verify image pulled", func(t *testing.T) { + // cleanup existing images + c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/simple", "down", "--rmi", "all") - assert.Assert(t, strings.Contains(output, "simple Pulled")) - assert.Assert(t, strings.Contains(output, "another Pulled")) + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/simple", "pull") + output := res.Combined() + + assert.Assert(t, strings.Contains(output, "simple Pulled")) + assert.Assert(t, strings.Contains(output, "another Pulled")) + }) + + t.Run("Verify a image is pulled once", func(t *testing.T) { + // cleanup existing images + c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/duplicate-images", "down", "--rmi", "all") + + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/duplicate-images", "pull") + output := res.Combined() + + if strings.Contains(output, "another Pulled") { + assert.Assert(t, strings.Contains(output, "another Pulled")) + assert.Assert(t, strings.Contains(output, "Skipped - Image is already being pulled by another")) + } else { + assert.Assert(t, strings.Contains(output, "simple Pulled")) + assert.Assert(t, strings.Contains(output, "Skipped - Image is already being pulled by simple")) + } + }) + + t.Run("Verify skipped pull if image is already present locally", func(t *testing.T) { + // make sure the required image is present + c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/image-present-locally", "pull") + + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/image-present-locally", "pull") + output := res.Combined() + + assert.Assert(t, strings.Contains(output, "Skipped - Image is already present locally")) + }) + + t.Run("Verify skipped no image to be pulled", func(t *testing.T) { + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/compose-pull/no-image-name-given", "pull") + output := res.Combined() + + assert.Assert(t, strings.Contains(output, "Skipped - No image to be pulled")) + }) } func TestDownComposefileInParentFolder(t *testing.T) { diff --git a/pkg/e2e/fixtures/compose-pull/duplicate-images/docker-compose.yaml b/pkg/e2e/fixtures/compose-pull/duplicate-images/docker-compose.yaml new file mode 100644 index 000000000..4a0d4c7b9 --- /dev/null +++ b/pkg/e2e/fixtures/compose-pull/duplicate-images/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + simple: + image: alpine:3.13 + command: top + another: + image: alpine:3.13 + command: top diff --git a/pkg/e2e/fixtures/compose-pull/image-present-locally/docker-compose.yaml b/pkg/e2e/fixtures/compose-pull/image-present-locally/docker-compose.yaml new file mode 100644 index 000000000..ebc0df079 --- /dev/null +++ b/pkg/e2e/fixtures/compose-pull/image-present-locally/docker-compose.yaml @@ -0,0 +1,4 @@ +services: + simple: + image: alpine:3.13.12 + command: top diff --git a/pkg/e2e/fixtures/compose-pull/no-image-name-given/docker-compose.yaml b/pkg/e2e/fixtures/compose-pull/no-image-name-given/docker-compose.yaml new file mode 100644 index 000000000..69494e7a1 --- /dev/null +++ b/pkg/e2e/fixtures/compose-pull/no-image-name-given/docker-compose.yaml @@ -0,0 +1,3 @@ +services: + no-image-service: + build: . diff --git a/pkg/e2e/fixtures/compose-pull/simple/docker-compose.yaml b/pkg/e2e/fixtures/compose-pull/simple/docker-compose.yaml new file mode 100644 index 000000000..2a5fd32a7 --- /dev/null +++ b/pkg/e2e/fixtures/compose-pull/simple/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + simple: + image: alpine:3.14 + command: top + another: + image: alpine:3.15 + command: top