Skip to content

Commit 6727908

Browse files
committed
introduce --resolve-image-digests for publish to seal service images by digest
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 5661fd1 commit 6727908

File tree

5 files changed

+100
-33
lines changed

5 files changed

+100
-33
lines changed

cmd/compose/publish.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,35 @@ import (
2525
"github.com/docker/compose/v2/pkg/api"
2626
)
2727

28+
type publishOptions struct {
29+
*ProjectOptions
30+
resolveImageDigests bool
31+
}
32+
2833
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
29-
opts := pushOptions{
34+
opts := publishOptions{
3035
ProjectOptions: p,
3136
}
32-
publishCmd := &cobra.Command{
37+
cmd := &cobra.Command{
3338
Use: "publish [OPTIONS] [REPOSITORY]",
3439
Short: "Publish compose application",
3540
RunE: Adapt(func(ctx context.Context, args []string) error {
3641
return runPublish(ctx, dockerCli, backend, opts, args[0])
3742
}),
3843
Args: cobra.ExactArgs(1),
3944
}
40-
return publishCmd
45+
flags := cmd.Flags()
46+
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
47+
return cmd
4148
}
4249

43-
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, repository string) error {
50+
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
4451
project, err := opts.ToProject(dockerCli, nil)
4552
if err != nil {
4653
return err
4754
}
4855

49-
return backend.Publish(ctx, project, repository, api.PublishOptions{})
56+
return backend.Publish(ctx, project, repository, api.PublishOptions{
57+
ResolveImageDigests: opts.resolveImageDigests,
58+
})
5059
}

docs/reference/compose_alpha_publish.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ Publish compose application
55

66
### Options
77

8-
| Name | Type | Default | Description |
9-
|:------------|:-----|:--------|:--------------------------------|
10-
| `--dry-run` | | | Execute command in dry run mode |
8+
| Name | Type | Default | Description |
9+
|:--------------------------|:-----|:--------|:--------------------------------|
10+
| `--dry-run` | | | Execute command in dry run mode |
11+
| `--resolve-image-digests` | | | Pin image tags to digests. |
1112

1213

1314
<!---MARKER_GEN_END-->

docs/reference/docker_compose_alpha_publish.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ long: Publish compose application
44
usage: docker compose alpha publish [OPTIONS] [REPOSITORY]
55
pname: docker compose alpha
66
plink: docker_compose_alpha.yaml
7+
options:
8+
- option: resolve-image-digests
9+
value_type: bool
10+
default_value: "false"
11+
description: Pin image tags to digests.
12+
deprecated: false
13+
hidden: false
14+
experimental: false
15+
experimentalcli: false
16+
kubernetes: false
17+
swarm: false
718
inherited_options:
819
- option: dry-run
920
value_type: bool

pkg/api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ type PortOptions struct {
363363

364364
// PublishOptions group options of the Publish API
365365
type PublishOptions struct {
366+
ResolveImageDigests bool
366367
}
367368

368369
func (e Event) String() string {

pkg/compose/publish.go

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -63,37 +63,24 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
6363
return err
6464
}
6565

66-
w.Event(progress.Event{
67-
ID: file,
68-
Text: "publishing",
69-
Status: progress.Working,
70-
})
71-
layer := v1.Descriptor{
72-
MediaType: "application/vnd.docker.compose.file+yaml",
73-
Digest: digest.FromString(string(f)),
74-
Size: int64(len(f)),
75-
Annotations: map[string]string{
76-
"com.docker.compose.version": api.ComposeVersion,
77-
"com.docker.compose.file": filepath.Base(file),
78-
},
66+
layer, err := s.pushComposeFile(ctx, file, f, resolver, named)
67+
if err != nil {
68+
return err
7969
}
8070
layers = append(layers, layer)
81-
err = resolver.Push(ctx, named, layer, f)
82-
if err != nil {
83-
w.Event(progress.Event{
84-
ID: file,
85-
Text: "publishing",
86-
Status: progress.Error,
87-
})
71+
}
8872

73+
if options.ResolveImageDigests {
74+
yaml, err := s.generateImageDigestsOverride(ctx, project)
75+
if err != nil {
8976
return err
9077
}
9178

92-
w.Event(progress.Event{
93-
ID: file,
94-
Text: "published",
95-
Status: progress.Done,
96-
})
79+
layer, err := s.pushComposeFile(ctx, "image-digests.yaml", yaml, resolver, named)
80+
if err != nil {
81+
return err
82+
}
83+
layers = append(layers, layer)
9784
}
9885

9986
emptyConfig, err := json.Marshal(v1.ImageConfig{})
@@ -157,3 +144,61 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
157144
})
158145
return nil
159146
}
147+
148+
func (s *composeService) generateImageDigestsOverride(ctx context.Context, project *types.Project) ([]byte, error) {
149+
project.ApplyProfiles([]string{"*"})
150+
err := project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
151+
auth, err := encodedAuth(named, s.configFile())
152+
if err != nil {
153+
return "", err
154+
}
155+
inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
156+
if err != nil {
157+
return "", err
158+
}
159+
return inspect.Descriptor.Digest, nil
160+
})
161+
if err != nil {
162+
return nil, err
163+
}
164+
override := types.Project{}
165+
for _, service := range project.Services {
166+
override.Services = append(override.Services, types.ServiceConfig{
167+
Name: service.Name,
168+
Image: service.Image,
169+
})
170+
}
171+
return override.MarshalYAML()
172+
}
173+
174+
func (s *composeService) pushComposeFile(ctx context.Context, file string, content []byte, resolver *imagetools.Resolver, named reference.Named) (v1.Descriptor, error) {
175+
w := progress.ContextWriter(ctx)
176+
w.Event(progress.Event{
177+
ID: file,
178+
Text: "publishing",
179+
Status: progress.Working,
180+
})
181+
layer := v1.Descriptor{
182+
MediaType: "application/vnd.docker.compose.file+yaml",
183+
Digest: digest.FromString(string(content)),
184+
Size: int64(len(content)),
185+
Annotations: map[string]string{
186+
"com.docker.compose.version": api.ComposeVersion,
187+
"com.docker.compose.file": filepath.Base(file),
188+
},
189+
}
190+
err := resolver.Push(ctx, named, layer, content)
191+
w.Event(progress.Event{
192+
ID: file,
193+
Text: "published",
194+
Status: statusFor(err),
195+
})
196+
return layer, err
197+
}
198+
199+
func statusFor(err error) progress.EventStatus {
200+
if err != nil {
201+
return progress.Error
202+
}
203+
return progress.Done
204+
}

0 commit comments

Comments
 (0)