From 02461ac2a3ec3a36829159c753cfa846444e002a Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Mon, 31 Mar 2025 09:10:24 +0200 Subject: [PATCH] main: allow seed setting Allow users to define the seed that's used for manifest generation. Regenerating an image with a given seed leads to the same manifest (provided depsolving does the same). The seed is normally mostly used to generate random filesystem UUIDs. This will need a bunch of documentation in a follow up since the use cases are meant to be advanced but it can really speed up multi-type and rebuilds of the same image. Signed-off-by: Simon de Vlieger --- cmd/image-builder/main.go | 10 ++++++++++ cmd/image-builder/manifest.go | 2 ++ test/test_container.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index bb9cee1..d846151 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -151,6 +151,14 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st if err != nil { return nil, err } + var customSeed *int64 + if cmd.Flags().Changed("seed") { + seedFlagVal, err := cmd.Flags().GetInt64("seed") + if err != nil { + return nil, err + } + customSeed = &seedFlagVal + } // no error check here as this is (deliberately) not defined on // "manifest" (if "images" learn to set the output filename in // manifests we would change this @@ -189,6 +197,7 @@ func cmdManifestWrapper(pbar progress.ProgressBar, cmd *cobra.Command, args []st Ostree: ostreeImgOpts, RpmDownloader: rpmDownloader, WithSBOM: withSBOM, + CustomSeed: customSeed, ForceRepos: forceRepos, } @@ -398,6 +407,7 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support. Hidden: true, } manifestCmd.Flags().String("blueprint", "", `filename of a blueprint to customize an image`) + manifestCmd.Flags().Int64("seed", 0, `rng seed, some values are derived randomly, pinning the seed allows more reproducibility if you need it. must be an integer. only used when changed.`) manifestCmd.Flags().String("arch", "", `build manifest for a different architecture`) manifestCmd.Flags().String("distro", "", `build manifest for a different distroname (e.g. centos-9)`) manifestCmd.Flags().String("ostree-ref", "", `OSTREE reference`) diff --git a/cmd/image-builder/manifest.go b/cmd/image-builder/manifest.go index d2f8b12..4d9e884 100644 --- a/cmd/image-builder/manifest.go +++ b/cmd/image-builder/manifest.go @@ -24,6 +24,7 @@ type manifestOptions struct { Ostree *ostree.ImageOptions RpmDownloader osbuild.RpmDownloader WithSBOM bool + CustomSeed *int64 ForceRepos []string UseBootstrapContainer bool @@ -55,6 +56,7 @@ func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Resu Output: output, RpmDownloader: opts.RpmDownloader, UseBootstrapContainer: opts.UseBootstrapContainer, + CustomSeed: opts.CustomSeed, } if opts.WithSBOM { outputDir := basenameFor(img, opts.OutputDir) diff --git a/test/test_container.py b/test/test_container.py index 8e80255..705be4f 100644 --- a/test/test_container.py +++ b/test/test_container.py @@ -122,3 +122,37 @@ def test_container_cross_build(tmp_path, build_container, arch): f"--arch={arch}", ], text=True) assert os.path.exists(output_dir / f"fedora-41-container-{arch}.tar") + + +@pytest.mark.parametrize("use_seed_arg", [False, True]) +@pytest.mark.skipif(os.getuid() != 0, reason="needs root") +def test_container_manifest_seeded_is_the_same(build_container, use_seed_arg): + manifests = set() + + cmd = [ + "podman", "run", + "--privileged", + build_container, + "manifest", + "--distro", "centos-9", + "minimal-raw", + ] + + if use_seed_arg: + cmd.extend(["--seed", "0"]) + + for _ in range(3): + p = subprocess.run( + cmd, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + manifests.add(p.stdout) + + # verify all calls with the same seed generated the same manifest + if use_seed_arg: + assert len(manifests) == 1 + else: + print(cmd) + assert len(manifests) == 3