introduce config --lock-image-digests

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2025-05-16 19:37:43 +02:00 committed by Guillaume Lours
parent 1f076a3781
commit 2352a4a016
4 changed files with 54 additions and 5 deletions

View File

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

View File

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

View File

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

View File

@ -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()
@ -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: