introduce --resolve-image-digests for publish to seal service images by digest

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2023-10-26 15:34:52 +02:00 committed by Nicolas De loof
parent 5661fd1bfe
commit 6727908803
5 changed files with 100 additions and 33 deletions

View File

@ -25,11 +25,16 @@ 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 {
@ -37,14 +42,18 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
}),
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,
})
}

View File

@ -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-->

View File

@ -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

View File

@ -363,6 +363,7 @@ type PortOptions struct {
// PublishOptions group options of the Publish API
type PublishOptions struct {
ResolveImageDigests bool
}
func (e Event) String() string {

View File

@ -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{})
@ -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
}