Browse Source

introduce config --lock-image-digests

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 6 months ago
parent
commit
2352a4a016

+ 38 - 0
cmd/compose/config.go

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

+ 1 - 0
docs/reference/compose_config.md

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

+ 10 - 0
docs/reference/docker_compose_config.yaml

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

+ 5 - 5
pkg/e2e/compose_test.go

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