Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 91b3e96

Browse files
authored
Add --dry-run option to pull cmd
Signed-off-by: GitHub <[email protected]>
1 parent 796d08d commit 91b3e96

File tree

7 files changed

+272
-28
lines changed

7 files changed

+272
-28
lines changed

Diff for: cmd/compose/pull.go

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type pullOptions struct {
3636
noParallel bool
3737
includeDeps bool
3838
ignorePullFailures bool
39+
dryRun bool
40+
format string
3941
}
4042

4143
func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
@@ -63,6 +65,8 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
6365
cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.")
6466
flags.MarkHidden("no-parallel") //nolint:errcheck
6567
cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures")
68+
cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "Calc and print effects of execution of pull command without any actual effects")
69+
cmd.Flags().StringVar(&opts.format, "format", "", `Format the output. Only compatible with "--dry-run". Values: [pretty | json]. (Default: pretty)`)
6670
return cmd
6771
}
6872

@@ -88,5 +92,7 @@ func runPull(ctx context.Context, backend api.Service, opts pullOptions, service
8892
return backend.Pull(ctx, project, api.PullOptions{
8993
Quiet: opts.quiet,
9094
IgnoreFailures: opts.ignorePullFailures,
95+
DryRun: opts.dryRun,
96+
Format: opts.format,
9197
})
9298
}

Diff for: local/e2e/compose/compose_dry_run_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"strings"
21+
"testing"
22+
23+
"gotest.tools/v3/assert"
24+
25+
. "github.com/docker/compose-cli/utils/e2e"
26+
)
27+
28+
func TestComposePullDryRun(t *testing.T) {
29+
c := NewParallelE2eCLI(t, binDir)
30+
31+
t.Run("compose pull dry run", func(t *testing.T) {
32+
// ensure storing alpine image and deleting hello-world
33+
c.RunDockerCmd("pull", "alpine")
34+
c.RunDockerCmd("rmi", "hello-world", "-f")
35+
36+
res := c.RunDockerCmd("compose", "-f", "./fixtures/dry-run-test/pull/compose.yaml", "pull", "--dry-run")
37+
lines := Lines(res.Stdout())
38+
for _, line := range lines {
39+
if strings.Contains(line, "expected-skip") {
40+
assert.Assert(t, strings.Contains(line, " skip "))
41+
}
42+
if strings.Contains(line, "expected-fail") {
43+
assert.Assert(t, strings.Contains(line, " fail "))
44+
}
45+
if strings.Contains(line, "expected-fetch") {
46+
assert.Assert(t, strings.Contains(line, " fetch "))
47+
}
48+
}
49+
})
50+
51+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
expected-fetch:
3+
image: hello-world
4+
command: top
5+
expected-skip-already-exist:
6+
image: alpine
7+
command: top
8+
expected-fail:
9+
# this image is not expected to be registered on any registries
10+
image: expected-not-to-be-registered
11+
command: top
12+
expected-skip-build:
13+
build: .
14+
command: top

Diff for: pkg/api/api.go

+3
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ type PushOptions struct {
175175
type PullOptions struct {
176176
Quiet bool
177177
IgnoreFailures bool
178+
DryRun bool
179+
Format string
178180
}
179181

180182
// ImagesOptions group options of the Images API
@@ -308,6 +310,7 @@ type ImageSummary struct {
308310
Repository string
309311
Tag string
310312
Size int64
313+
Digests []string
311314
}
312315

313316
// ServiceStatus hold status about a service

Diff for: pkg/compose/build.go

+72-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"context"
2121
"fmt"
2222
"os"
23+
"strings"
24+
"sync"
2325

2426
"github.com/compose-spec/compose-go/types"
2527
"github.com/containerd/containerd/platforms"
@@ -29,10 +31,12 @@ import (
2931
"github.com/docker/buildx/util/buildflags"
3032
xprogress "github.com/docker/buildx/util/progress"
3133
moby "github.com/docker/docker/api/types"
34+
"github.com/docker/docker/registry"
3235
bclient "github.com/moby/buildkit/client"
3336
"github.com/moby/buildkit/session"
3437
"github.com/moby/buildkit/session/auth/authprovider"
3538
specs "github.com/opencontainers/image-spec/specs-go/v1"
39+
"golang.org/x/sync/errgroup"
3640

3741
"github.com/docker/compose-cli/pkg/api"
3842
"github.com/docker/compose-cli/pkg/progress"
@@ -97,7 +101,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
97101
}
98102
}
99103

100-
images, err := s.getLocalImagesDigests(ctx, project)
104+
images, err := s.getLocalImagesIDs(ctx, project)
101105
if err != nil {
102106
return err
103107
}
@@ -163,7 +167,19 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
163167

164168
}
165169

166-
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
170+
func (s *composeService) getLocalImagesIDs(ctx context.Context, project *types.Project) (map[string]string, error) {
171+
imgs, err := s.getLocalImageSummaries(ctx, project)
172+
if err != nil {
173+
return nil, err
174+
}
175+
images := map[string]string{}
176+
for name, info := range imgs {
177+
images[name] = info.ID
178+
}
179+
return images, nil
180+
}
181+
182+
func (s *composeService) getLocalImageSummaries(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
167183
imageNames := []string{}
168184
for _, s := range project.Services {
169185
imgName := getImageName(s, project.Name)
@@ -175,13 +191,64 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
175191
if err != nil {
176192
return nil, err
177193
}
178-
images := map[string]string{}
179-
for name, info := range imgs {
180-
images[name] = info.ID
194+
return imgs, nil
195+
}
196+
197+
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string][]string, error) {
198+
imgs, err := s.getLocalImageSummaries(ctx, project)
199+
if err != nil {
200+
return nil, err
201+
}
202+
images := map[string][]string{}
203+
for name, summary := range imgs {
204+
for _, d := range summary.Digests {
205+
s := strings.Split(d, "@")
206+
images[name] = append(images[name], s[len(s)-1])
207+
}
181208
}
182209
return images, nil
183210
}
184211

212+
func (s *composeService) getDistributionImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
213+
images := map[string]string{}
214+
215+
for _, service := range project.Services {
216+
if service.Image == "" {
217+
continue
218+
}
219+
images[service.Image] = ""
220+
}
221+
222+
info, err := s.apiClient.Info(ctx)
223+
if err != nil {
224+
return nil, err
225+
}
226+
227+
if info.IndexServerAddress == "" {
228+
info.IndexServerAddress = registry.IndexServer
229+
}
230+
231+
l := sync.Mutex{}
232+
eg, ctx := errgroup.WithContext(ctx)
233+
for img := range images {
234+
img := img
235+
eg.Go(func() error {
236+
registryAuth, err := getEncodedRegistryAuth(img, info, s.configFile)
237+
if err != nil {
238+
return err
239+
}
240+
inspect, _ := s.apiClient.DistributionInspect(ctx, img, registryAuth)
241+
// Ignore error here.
242+
// If you catch error here, all inspect requests will fail.
243+
l.Lock()
244+
images[img] = inspect.Descriptor.Digest.String()
245+
l.Unlock()
246+
return nil
247+
})
248+
}
249+
return images, eg.Wait()
250+
}
251+
185252
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, observedState Containers, mode string) (map[string]string, error) {
186253
info, err := s.apiClient.Info(ctx)
187254
if err != nil {

Diff for: pkg/compose/images.go

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
105105
Repository: repository,
106106
Tag: tag,
107107
Size: inspect.Size,
108+
Digests: inspect.RepoDigests,
108109
}
109110
l.Unlock()
110111
return nil

0 commit comments

Comments
 (0)