Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cmd/compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type configOptions struct {
noConsistency bool
variables bool
environment bool
lockImageDigests bool
}

func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
Expand Down Expand Up @@ -98,6 +99,9 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
if p.Compatibility {
opts.noNormalize = true
}
if opts.lockImageDigests {
opts.resolveImageDigests = true
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
Expand Down Expand Up @@ -133,6 +137,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "", "Format the output. Values: [yaml | json]")
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
flags.BoolVar(&opts.lockImageDigests, "lock-image-digests", false, "Produces an override file with image digests")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything")
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables")
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model")
Expand Down Expand Up @@ -208,6 +213,10 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
}
}

if opts.lockImageDigests {
project = imagesOnly(project)
}

var content []byte
switch opts.Format {
case "json":
Expand All @@ -223,6 +232,18 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
return content, nil
}

// imagesOnly return project with all attributes removed but service.images
func imagesOnly(project *types.Project) *types.Project {
digests := types.Services{}
for name, config := range project.Services {
digests[name] = types.ServiceConfig{
Image: config.Image,
}
}
project = &types.Project{Services: digests}
return project
}

func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
// we can't use ToProject, so the model we render here is only partially resolved
model, err := opts.ToModel(ctx, dockerCli, services)
Expand All @@ -237,6 +258,23 @@ func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts con
}
}

if opts.lockImageDigests {
for key, e := range model {
if key != "services" {
delete(model, key)
} else {
for _, s := range e.(map[string]any) {
service := s.(map[string]any)
for key := range service {
if key != "image" {
delete(service, key)
}
}
}
}
}
}

return formatModel(model, opts.Format)
}

Expand Down
1 change: 1 addition & 0 deletions docs/reference/compose_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ the canonical format.
| `--format` | `string` | | Format the output. Values: [yaml \| json] |
| `--hash` | `string` | | Print the service config hash, one per line. |
| `--images` | `bool` | | Print the image names, one per line. |
| `--lock-image-digests` | `bool` | | Produces an override file with image digests |
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
| `--no-env-resolution` | `bool` | | Don't resolve service env files |
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
Expand Down
10 changes: 10 additions & 0 deletions docs/reference/docker_compose_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: lock-image-digests
value_type: bool
default_value: "false"
description: Produces an override file with image digests
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: no-consistency
value_type: bool
default_value: "false"
Expand Down
10 changes: 5 additions & 5 deletions pkg/e2e/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func TestCompatibility(t *testing.T) {
}

func TestConfig(t *testing.T) {
const projectName = "compose-e2e-convert"
const projectName = "compose-e2e-config"
c := NewParallelCLI(t)

wd, err := os.Getwd()
Expand All @@ -253,24 +253,24 @@ services:
default: null
networks:
default:
name: compose-e2e-convert_default
name: compose-e2e-config_default
`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
})
}

func TestConfigInterpolate(t *testing.T) {
const projectName = "compose-e2e-convert-interpolate"
const projectName = "compose-e2e-config-interpolate"
c := NewParallelCLI(t)

wd, err := os.Getwd()
assert.NilError(t, err)

t.Run("convert", func(t *testing.T) {
t.Run("config", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose-interpolate.yaml", "-p", projectName, "config", "--no-interpolate")
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
networks:
default:
name: compose-e2e-convert-interpolate_default
name: compose-e2e-config-interpolate_default
services:
nginx:
build:
Expand Down