Skip to content

Commit

Permalink
introduce --resolve-image-digests for publish to seal service images …
Browse files Browse the repository at this point in the history
…by digest

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
  • Loading branch information
ndeloof committed Nov 2, 2023
1 parent 5661fd1 commit 6727908
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 33 deletions.
19 changes: 14 additions & 5 deletions cmd/compose/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,35 @@ import (
"github.com/docker/compose/v2/pkg/api"
)

type publishOptions struct {
*ProjectOptions
resolveImageDigests bool
}

func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := pushOptions{
opts := publishOptions{
ProjectOptions: p,
}
publishCmd := &cobra.Command{
cmd := &cobra.Command{
Use: "publish [OPTIONS] [REPOSITORY]",
Short: "Publish compose application",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPublish(ctx, dockerCli, backend, opts, args[0])
}),
Args: cobra.ExactArgs(1),
}
return publishCmd
flags := cmd.Flags()
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
return cmd
}

func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, repository string) error {
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
project, err := opts.ToProject(dockerCli, nil)
if err != nil {
return err
}

return backend.Publish(ctx, project, repository, api.PublishOptions{})
return backend.Publish(ctx, project, repository, api.PublishOptions{
ResolveImageDigests: opts.resolveImageDigests,
})
}
7 changes: 4 additions & 3 deletions docs/reference/compose_alpha_publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ Publish compose application

### Options

| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| Name | Type | Default | Description |
|:--------------------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--resolve-image-digests` | | | Pin image tags to digests. |


<!---MARKER_GEN_END-->
Expand Down
11 changes: 11 additions & 0 deletions docs/reference/docker_compose_alpha_publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ long: Publish compose application
usage: docker compose alpha publish [OPTIONS] [REPOSITORY]
pname: docker compose alpha
plink: docker_compose_alpha.yaml
options:
- option: resolve-image-digests
value_type: bool
default_value: "false"
description: Pin image tags to digests.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool
Expand Down
1 change: 1 addition & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ type PortOptions struct {

// PublishOptions group options of the Publish API
type PublishOptions struct {
ResolveImageDigests bool
}

func (e Event) String() string {
Expand Down
95 changes: 70 additions & 25 deletions pkg/compose/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,37 +63,24 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
return err
}

w.Event(progress.Event{
ID: file,
Text: "publishing",
Status: progress.Working,
})
layer := v1.Descriptor{
MediaType: "application/vnd.docker.compose.file+yaml",
Digest: digest.FromString(string(f)),
Size: int64(len(f)),
Annotations: map[string]string{
"com.docker.compose.version": api.ComposeVersion,
"com.docker.compose.file": filepath.Base(file),
},
layer, err := s.pushComposeFile(ctx, file, f, resolver, named)
if err != nil {
return err
}
layers = append(layers, layer)
err = resolver.Push(ctx, named, layer, f)
if err != nil {
w.Event(progress.Event{
ID: file,
Text: "publishing",
Status: progress.Error,
})
}

if options.ResolveImageDigests {
yaml, err := s.generateImageDigestsOverride(ctx, project)
if err != nil {
return err
}

w.Event(progress.Event{
ID: file,
Text: "published",
Status: progress.Done,
})
layer, err := s.pushComposeFile(ctx, "image-digests.yaml", yaml, resolver, named)
if err != nil {
return err
}
layers = append(layers, layer)
}

emptyConfig, err := json.Marshal(v1.ImageConfig{})
Expand Down Expand Up @@ -157,3 +144,61 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
})
return nil
}

func (s *composeService) generateImageDigestsOverride(ctx context.Context, project *types.Project) ([]byte, error) {
project.ApplyProfiles([]string{"*"})
err := project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
auth, err := encodedAuth(named, s.configFile())
if err != nil {
return "", err
}
inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
if err != nil {
return "", err
}
return inspect.Descriptor.Digest, nil
})
if err != nil {
return nil, err
}
override := types.Project{}
for _, service := range project.Services {
override.Services = append(override.Services, types.ServiceConfig{
Name: service.Name,
Image: service.Image,
})
}
return override.MarshalYAML()
}

func (s *composeService) pushComposeFile(ctx context.Context, file string, content []byte, resolver *imagetools.Resolver, named reference.Named) (v1.Descriptor, error) {
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: file,
Text: "publishing",
Status: progress.Working,
})
layer := v1.Descriptor{
MediaType: "application/vnd.docker.compose.file+yaml",
Digest: digest.FromString(string(content)),
Size: int64(len(content)),
Annotations: map[string]string{
"com.docker.compose.version": api.ComposeVersion,
"com.docker.compose.file": filepath.Base(file),
},
}
err := resolver.Push(ctx, named, layer, content)
w.Event(progress.Event{
ID: file,
Text: "published",
Status: statusFor(err),
})
return layer, err
}

func statusFor(err error) progress.EventStatus {
if err != nil {
return progress.Error
}
return progress.Done
}

0 comments on commit 6727908

Please sign in to comment.