Skip to content

Use skopeo for hermetic bootstrap images#1717

Merged
praiskup merged 1 commit intorpm-software-management:mainfrom
tkopecek:hermetic-skopeo
Feb 23, 2026
Merged

Use skopeo for hermetic bootstrap images#1717
praiskup merged 1 commit intorpm-software-management:mainfrom
tkopecek:hermetic-skopeo

Conversation

@tkopecek
Copy link
Contributor

@tkopecek tkopecek commented Feb 20, 2026

Better replacement for #1716

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request successfully transitions from podman pull|save to skopeo for handling hermetic bootstrap images, which is a more robust approach for managing image digests and offline transports. However, there is a critical mismatch between how skopeo sync --scoped prepares the directory structure and how the configuration logic attempts to locate it. Switching to skopeo copy with a predictable destination directory (e.g., bootstrap/) would resolve this and maintain consistency with the previous implementation.

Comment on lines 130 to 137
pull_cmd = ["skopeo", "sync", "--src", "docker", "--dest", "dir",
"--scoped", "--preserve-digests"]
if "architecture" in bootstrap_data:
pull_cmd += ["--override-arch", bootstrap_data["architecture"]]
if "pull_digest" in bootstrap_data:
image_specification += "@" + bootstrap_data["pull_digest"]
if "architecture" in bootstrap_data:
pull_cmd += ["--arch", bootstrap_data["architecture"]]
pull_cmd += [image_specification]
pull_cmd += [image_specification, outputdir]
log.info("Pulling like: %s", ' '.join(pull_cmd))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of skopeo sync --scoped creates a nested directory hierarchy based on the registry and repository (e.g., docker.io/library/fedora), which does not match the flat path construction in mockbuild/config.py. Since this script is intended to fetch a specific bootstrap image for a build, skopeo copy is more appropriate as it allows specifying a predictable destination directory. Additionally, subprocess.check_call is preferred over check_output when the output is not being captured.

Suggested change
pull_cmd = ["skopeo", "sync", "--src", "docker", "--dest", "dir",
"--scoped", "--preserve-digests"]
if "architecture" in bootstrap_data:
pull_cmd += ["--override-arch", bootstrap_data["architecture"]]
if "pull_digest" in bootstrap_data:
image_specification += "@" + bootstrap_data["pull_digest"]
if "architecture" in bootstrap_data:
pull_cmd += ["--arch", bootstrap_data["architecture"]]
pull_cmd += [image_specification]
pull_cmd += [image_specification, outputdir]
log.info("Pulling like: %s", ' '.join(pull_cmd))
bootstrap_dir = os.path.join(outputdir, "bootstrap")
pull_cmd = ["skopeo", "copy"]
if "architecture" in bootstrap_data:
pull_cmd += ["--override-arch", bootstrap_data["architecture"]]
if "pull_digest" in bootstrap_data:
image_specification += "@" + bootstrap_data["pull_digest"]
pull_cmd += ["docker://" + image_specification, "dir:" + bootstrap_dir]
log.info("Pulling like: %s", ' '.join(pull_cmd))
subprocess.check_call(pull_cmd)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should work in every case. docker.io/library/fedora is still the part of boostrap_image string, so nested directories will be part of final path?

Comment on lines 809 to 811
image_pullspec = f"{data['config']['bootstrap_image']}@{data['bootstrap']['pull_digest']}"
bootstrap_dir = os.path.join(final_offline_repo, image_pullspec)
config_opts["bootstrap_image"] = f"dir:{bootstrap_dir}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current path construction for bootstrap_dir assumes a flat directory named after the pullspec, which conflicts with the hierarchical structure created by skopeo sync --scoped. To ensure the build can correctly locate the offline image, it is better to use a fixed directory name like bootstrap, matching the suggested change in mock-hermetic-repo.py.

Suggested change
image_pullspec = f"{data['config']['bootstrap_image']}@{data['bootstrap']['pull_digest']}"
bootstrap_dir = os.path.join(final_offline_repo, image_pullspec)
config_opts["bootstrap_image"] = f"dir:{bootstrap_dir}"
# Use the offline image for bootstrapping.
bootstrap_dir = os.path.join(final_offline_repo, "bootstrap")
config_opts["bootstrap_image"] = f"dir:{bootstrap_dir}"

@praiskup
Copy link
Member

CI failure:

      INFO:__main__:Pulling like: skopeo sync --src docker --dest dir --scoped --preserve-digests --override-arch amd64 registry.fedoraproject.org/fedora:rawhide@sha256:926c55dd6993b0332abe54abbc993929b578fa0212bb05d23c187c424f203bbf /tmp/mock-tests-local-repo-3gghtsxg
      time="2026-02-20T16:17:55Z" level=info msg="Tag presence check" imagename="registry.fedoraproject.org/fedora:rawhide@sha256:926c55dd6993b0332abe54abbc993929b578fa0212bb05d23c187c424f203bbf" tagged=true
      time="2026-02-20T16:17:55Z" level=fatal msg="Cannot obtain a valid image reference for transport \"docker\" and reference \"registry.fedoraproject.org/fedora:rawhide@sha256:926c55dd6993b0332abe54abbc993929b578fa0212bb05d23c187c424f203bbf\": Docker references with both a tag and digest are currently not supported"
      Traceback (most recent call last):
        File "/usr/bin/mock-hermetic-repo", line 199, in <module>
          _main()
          ~~~~~^^
        File "/usr/bin/mock-hermetic-repo", line 194, in _main
          prepare_image(data["config"]["bootstrap_image"], data["bootstrap"],
          ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        options.output_repo)
                        ^^^^^^^^^^^^^^^^^^^^
        File "/usr/bin/mock-hermetic-repo", line 138, in prepare_image
          subprocess.check_output(pull_cmd)
          ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
        File "/usr/lib64/python3.14/subprocess.py", line 472, in check_output
          return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
                 ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                     **kwargs).stdout
                     ^^^^^^^^^
        File "/usr/lib64/python3.14/subprocess.py", line 577, in run
          raise CalledProcessError(retcode, process.args,
                                   output=stdout, stderr=stderr)
      subprocess.CalledProcessError: Command '['skopeo', 'sync', '--src', 'docker', '--dest', 'dir', '--scoped', '--preserve-digests', '--override-arch', 'amd64', 'registry.fedoraproject.org/fedora:rawhide@sha256:926c55dd6993b0332abe54abbc993929b578fa0212bb05d23c187c424f203bbf', '/tmp/mock-tests-local-repo-3gghtsxg']' returned non-zero exit status 1.

    And a hermetic build is retriggered with the lockfile and repository                 # None
    Then the build succeeds                                                              # None

"""
pull_cmd = ["podman", "pull"]
pull_cmd = ["skopeo", "sync", "--src", "docker", "--dest", "dir",
"--scoped", "--preserve-digests"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed before, this worked for me correctly - but I didn't even use --scoped and --preserve-digests 🤷 please document why we need those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

Store the tarball into the same directory where the RPMs are
"""
pull_cmd = ["podman", "pull"]
pull_cmd = ["skopeo", "sync", "--src", "docker", "--dest", "dir",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need skopeo in Recommends.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is there.

@praiskup
Copy link
Member

      INFO:__main__:Pulling like: skopeo sync --src docker --dest dir --scoped --preserve-digests --override-arch amd64 quay.io/centos/centos:stream9@sha256:8e47e1ac4bfb7ae13d18403389d0ae0d6b44aa3c5a66e4561bc6cb881c140cd4 /tmp/mock-tests-local-repo-29xlxzf_
      time="2026-02-23T13:09:45Z" level=info msg="Tag presence check" imagename="quay.io/centos/centos:stream9@sha256:8e47e1ac4bfb7ae13d18403389d0ae0d6b44aa3c5a66e4561bc6cb881c140cd4" tagged=true
      time="2026-02-23T13:09:45Z" level=fatal msg="Cannot obtain a valid image reference for transport \"docker\" and reference \"quay.io/centos/centos:stream9@sha256:8e47e1ac4bfb7ae13d18403389d0ae0d6b44aa3c5a66e4561bc6cb881c140cd4\": Docker references with both a tag and digest are currently not supported"
      Traceback (most recent call last):
        File "/usr/bin/mock-hermetic-repo", line 204, in <module>
          _main()
          ~~~~~^^
        File "/usr/bin/mock-hermetic-repo", line 199, in _main
          prepare_image(data["config"]["bootstrap_image"], data["bootstrap"],
          ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        options.output_repo)
                        ^^^^^^^^^^^^^^^^^^^^
        File "/usr/bin/mock-hermetic-repo", line 143, in prepare_image
          subprocess.check_output(pull_cmd)
          ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
        File "/usr/lib64/python3.14/subprocess.py", line 472, in check_output
          return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
                 ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                     **kwargs).stdout
                     ^^^^^^^^^
        File "/usr/lib64/python3.14/subprocess.py", line 577, in run
          raise CalledProcessError(retcode, process.args,
                                   output=stdout, stderr=stderr)
      subprocess.CalledProcessError: Command '['skopeo', 'sync', '--src', 'docker', '--dest', 'dir', '--scoped', '--preserve-digests', '--override-arch', 'amd64', 'quay.io/centos/centos:stream9@sha256:8e47e1ac4bfb7ae13d18403389d0ae0d6b44aa3c5a66e4561bc6cb881c140cd4', '/tmp/mock-tests-local-repo-29xlxzf_']' returned non-zero exit status 1.

    And a hermetic build is retriggered with the lockfile and repository                                # None
    Then the build succeeds                                                                             # None
    And the produced lockfile is validated properly                                                     # None

Feature: Test the "library" methods # features/library.feature:1

  @library @simple_load_config
  Scenario: The --resultdir option is incompatible with --chain                                    # features/library.feature:4

@tkopecek
Copy link
Contributor Author

#1717 (comment) - interesting - works for me locally, I'll try to debug it more.

@tkopecek
Copy link
Contributor Author

#1717 (comment) - interesting - works for me locally, I'll try to debug it more.

ouch, missed a file to commit

Copy link
Member

@praiskup praiskup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@praiskup
Copy link
Member

Would you mind squashing the PR?

@praiskup praiskup mentioned this pull request Feb 23, 2026
# also it matches with full pullspec from config's bootstrap_image
# --preserve-digests - it is not needed for most images, but some could end with
# modified digests (semantically identical copy, but modified digest,
# e.g. embedded docker reference for v1 images), so better safe than sorry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are in EL8, too 👍

@praiskup praiskup merged commit 649b22c into rpm-software-management:main Feb 23, 2026
31 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants