Skip to content

Conversation

@richm
Copy link
Contributor

@richm richm commented Sep 17, 2025

Feature: Support running this role during container builds.

Reason: This is particularly useful for building bootc derivative OSes.

Result: User can configure the image at build time.

Signed-off-by: Rich Megginson rmeggins@redhat.com

Summary by Sourcery

Enable the role to run during container build contexts by detecting whether systemd is available and deferring runtime operations accordingly. Extract image pull and caching logic into dedicated tasks and a new custom module, and update existing tasks to only run when the system is booted. Extend tests to cover buildah-based bootc scenarios and validate caching and secret injection.

New Features:

  • Detect system boot state (__podman_is_booted) to conditionally run systemd-dependent tasks during container builds
  • Add manage_image_cache custom module and tasks to cache container images for bootc systems

Enhancements:

  • Refactor image pulling into handle_images.yml and handle_image_cache.yml to support rootless and non-booted environments
  • Gate systemd reloads, unit activation, and resource cleanup on the boot state to avoid failures in build-time contexts

Tests:

  • Extend tests for buildah-based bootc end-to-end scenarios, including image caching and JSON secret verification

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 17, 2025

Reviewer's Guide

This PR enables the role to distinguish between build-time (container) and runtime contexts by detecting if systemd is booted, and then branches tasks accordingly. It extracts image pull and cache logic into standalone tasks and a new Ansible module, refactors cleanup operations to defer during build-time, and updates the test suite to validate both scenarios.

Sequence diagram for image handling during build-time vs runtime

sequenceDiagram
    participant Role
    participant System
    participant "Podman Image Module"
    participant "Image Cache Module"
    participant "Systemd"
    Note over Role,System: Detect if systemd is booted
    Role->>System: Run systemctl is-system-running
    alt Systemd is booted (runtime)
        Role->>"Podman Image Module": Pull images with podman_image
        "Podman Image Module"->>System: Store images in containers-storage
    else Systemd is not booted (build-time)
        Role->>"Image Cache Module": Cache images with manage_image_cache
        "Image Cache Module"->>System: Store images in cache directory
        Role->>System: Create script and systemd unit for later import
    end
Loading

Class diagram for new manage_image_cache Ansible module

classDiagram
    class manage_image_cache {
        +images: list~str~
        +username: str
        +password: str
        +validate_certs: bool
        +cache_dir: str
        +preserve_digests: bool
        +state: str
        +run_skopeo_copy()
        +get_image_cache_path()
        +is_image_cached()
        +update_mapping_file()
        +remove_from_mapping_file()
        +remove_cached_image()
        +run_module()
    }
    manage_image_cache --> AnsibleModule
Loading

File-Level Changes

Change Details Files
Introduce boot state detection and conditional task execution
  • Add systemctl check and set_fact for __podman_is_booted
  • Wrap existing tasks with when: __podman_is_booted or not __podman_is_booted
  • Adjust task inclusion and looping based on booted flag
tasks/set_vars.yml
tasks/cleanup_quadlet_spec.yml
tasks/create_update_quadlet_spec.yml
tasks/create_update_kube_spec.yml
tasks/main.yml
tasks/manage_linger.yml
tasks/handle_secret.yml
Extract and unify image pull/caching logic
  • Remove inline podman_image tasks
  • Include handle_images.yml under runtime
  • Include handle_image_cache.yml for build-time
  • Add new manage_image_cache.py module
  • Add templates and systemd unit for image cache service
tasks/create_update_quadlet_spec.yml
tasks/handle_images.yml
tasks/handle_image_cache.yml
library/manage_image_cache.py
templates/lsr_podman_copy_images.sh.j2
templates/lsr_podman_copy_images.service.j2
Refactor resource cleanup to runtime-only block
  • Consolidate Refresh systemd, Remove managed resource, Remove volumes under a Perform resource cleanup block
  • Remove duplicate debug and prune steps
  • Guard cleanup block with __podman_file_removed and __podman_is_booted
tasks/cleanup_quadlet_spec.yml
Update tests for build-time and runtime scenarios
  • Add conditions on __podman_is_booted and ansible_connection buildah
  • Insert end-to-end QEMU build step
  • Set force/skip_existing flags for secrets
  • Restructure test blocks and cleanups
tests/tests_quadlet_basic.yml

Possibly linked issues

  • merge from ikke #1: The PR adds conditional systemd execution in create_update_quadlet_spec.yml, which addresses the systemctl daemon-reload failures in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `library/manage_image_cache.py:166-171` </location>
<code_context>
+from ansible.module_utils.basic import AnsibleModule
+
+
+def run_skopeo_copy(
+    module,
+    src_image,
+    dest_dir,
+    username=None,
+    password=None,
+    validate_certs=True,
+    preserve_digests=True,
+):
+    """Run skopeo copy command to cache an image"""
+
+    cmd = ["skopeo", "copy"]
+
+    if preserve_digests:
+        cmd.append("--preserve-digests")
+
+    # Add credentials if provided
+    if username:
+        if password:
+            cmd.extend(["--src-creds", f"{username}:{password}"])
+        else:
+            cmd.extend(["--src-creds", username])
+
+    # Add certificate validation option
+    if validate_certs is not None:
+        if validate_certs:
+            cmd.append("--src-tls-verify")
</code_context>

<issue_to_address>
**suggestion:** TLS verification flag handling may be inconsistent with skopeo expectations.

Always pass '--src-tls-verify' with an explicit boolean value (true/false) to match skopeo's expected usage and prevent ambiguous behavior.

```suggestion
    # Always pass --src-tls-verify with explicit boolean value
    cmd.append(f"--src-tls-verify={'true' if validate_certs else 'false'}")
```
</issue_to_address>

### Comment 2
<location> `library/manage_image_cache.py:200-202` </location>
<code_context>
+    )
+
+
+def has_mapping_entry(mapping_file, mapping_entry):
+    with open(mapping_file, "r") as ff:
+        return mapping_entry.strip() in ff.readlines()
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Mapping entry check may fail due to newline handling.

Since ff.readlines() includes newlines in each line, comparing with a stripped mapping_entry may not work as intended. Normalize both mapping_entry and file lines by stripping whitespace before comparison.

```suggestion
def has_mapping_entry(mapping_file, mapping_entry):
    with open(mapping_file, "r") as ff:
        file_lines = [line.strip() for line in ff.readlines()]
        return mapping_entry.strip() in file_lines
```
</issue_to_address>

### Comment 3
<location> `library/manage_image_cache.py:191-197` </location>
<code_context>
+    return os.path.join(cache_dir, image_hash)
+
+
+def is_image_cached(cache_path):
+    """Check if an image is already cached"""
+    return (
+        os.path.exists(cache_path)
+        and os.path.isdir(cache_path)
</code_context>

<issue_to_address>
**suggestion:** Image cache existence check may be too permissive.

Consider verifying the presence of expected files or a manifest in the cache directory to ensure image integrity and completeness.

```suggestion
def is_image_cached(cache_path):
    """Check if an image is already cached and complete (manifest present)"""
    manifest_path = os.path.join(cache_path, "manifest.json")
    return (
        os.path.exists(cache_path)
        and os.path.isdir(cache_path)
        and os.path.exists(manifest_path)
    )
```
</issue_to_address>

### Comment 4
<location> `tasks/handle_image_cache.yml:34-36` </location>
<code_context>
-      ternary(omit, __podman_validate_certs) }}"
-  register: __podman_image_updated
-  when: __podman_pull_image | bool
-  until: __podman_image_updated is success
-  retries: "{{ podman_pull_retry | ternary(3, 0) }}"
-  failed_when:
-    - __podman_image_updated is failed
-    - not __podman_continue_if_pull_fails
</code_context>

<issue_to_address>
**suggestion:** Retry logic may not handle partial failures in image list.

Currently, the logic treats the batch as a single success or failure, which may mask individual image errors. Please update the handling to check each image's result and retry or report failures accordingly.

Suggested implementation:

```
- name: Cache container images for bootc (physically bound images) - per image
  vars:
    podman_image_pull_results: []
    podman_image_pull_failures: []
  block:
    - name: Pull each image and retry on failure
      vars:
        image_pull_retries: "{{ podman_pull_retry | ternary(3, 0) }}"
      loop: "{{ __podman_images | unique | list }}"
      loop_control:
        loop_var: podman_image
      until: podman_image_pull_result is success or image_pull_retries == 0
      retries: "{{ podman_pull_retry | ternary(3, 0) }}"
      register: podman_image_pull_result
      no_log: true
      manage_image_cache:
        images: "{{ [podman_image] }}"
        username: "{{ __podman_registry_username
          if __podman_registry_username | length > 0 else omit }}"
        password: "{{ __podman_registry_password
          if __podman_registry_password | length > 0 else omit }}"
        validate_certs: "{{ __podman_validate_certs
          if __podman_validate_certs is not none else omit }}"
        cache_dir: "{{ __podman_image_cache_dir }}"
    - name: Collect failed image pulls
      set_fact:
        podman_image_pull_failures: >-
          {{
            podman_image_pull_failures +
            [item.item] if (item.failed or item is failed) else podman_image_pull_failures
          }}
      loop: "{{ podman_image_pull_result.results | default([]) }}"
      loop_control:
        label: "{{ item.item }}"
    - name: Fail if any image pulls failed and not allowed to continue
      fail:
        msg: "Failed to cache images: {{ podman_image_pull_failures }}"
      when:
        - podman_image_pull_failures | length > 0
        - not __podman_continue_if_pull_fails
=======

```

- If `manage_image_cache` is a custom module, ensure it supports single-image operation.
- You may need to adjust variable names and result parsing to match your module's output format.
- Remove or update any references to `__podman_image_updated` elsewhere in the playbook.
</issue_to_address>

### Comment 5
<location> `templates/lsr_podman_copy_images.sh.j2:16` </location>
<code_context>
+  skopeo copy --preserve-digests "dir:$cache_dir/$sha" "containers-storage:$image"
+done < "$mapping_file"
+podman images --all --digests --no-trunc || :
+rm -rf "$cache_dir"
</code_context>

<issue_to_address>
**issue (bug_risk):** Unconditional cache directory removal may cause race conditions or data loss.

Removing the cache directory without checks may disrupt other processes or cause data loss if the script fails. Please verify the cache is unused before deletion, or make removal optional.
</issue_to_address>

### Comment 6
<location> `tasks/handle_images.yml:10-11` </location>
<code_context>
-      if __podman_registry_username | length > 0 else omit }}"
-    password: "{{ __podman_registry_password
-      if __podman_registry_password | length > 0 else omit }}"
-    validate_certs: "{{ (__podman_validate_certs in ['', none]) |
-      ternary(omit, __podman_validate_certs) }}"
-  register: __podman_image_updated
</code_context>

<issue_to_address>
**suggestion:** validate_certs handling may not cover all falsy values.

The current check only accounts for '' and none; consider updating it to handle all possible falsy values, such as False and 0, to avoid unexpected behavior.

```suggestion
    validate_certs: "{{ (__podman_validate_certs | bool) | ternary(__podman_validate_certs, omit) }}"
```
</issue_to_address>

### Comment 7
<location> `library/manage_image_cache.py:213-215` </location>
<code_context>
        if os.path.exists(mapping_file):
            if has_mapping_entry(mapping_file, mapping_entry):
                return True, None

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/merge-nested-ifs))

```suggestion
        if os.path.exists(mapping_file) and has_mapping_entry(mapping_file, mapping_entry):
            return True, None

```

<br/><details><summary>Explanation</summary>Too much nesting can make code difficult to understand, and this is especially
true in Python, where there are no brackets to help out with the delineation of
different nesting levels.

Reading deeply nested code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

### Comment 8
<location> `library/manage_image_cache.py:178-181` </location>
<code_context>
def run_skopeo_copy(
    module,
    src_image,
    dest_dir,
    username=None,
    password=None,
    validate_certs=True,
    preserve_digests=True,
):
    """Run skopeo copy command to cache an image"""

    cmd = ["skopeo", "copy"]

    if preserve_digests:
        cmd.append("--preserve-digests")

    # Add credentials if provided
    if username:
        if password:
            cmd.extend(["--src-creds", f"{username}:{password}"])
        else:
            cmd.extend(["--src-creds", username])

    # Add certificate validation option
    if validate_certs is not None:
        if validate_certs:
            cmd.append("--src-tls-verify")
        else:
            cmd.append("--src-tls-verify=false")

    # Add source and destination
    cmd.extend([f"docker://{src_image}", f"dir:{dest_dir}"])

    rc, stdout, stderr = module.run_command(cmd)

    if rc == 0:
        return True, stdout, None
    else:
        return False, stdout, stderr

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))

```suggestion
    return (True, stdout, None) if rc == 0 else (False, stdout, stderr)
```
</issue_to_address>

### Comment 9
<location> `library/manage_image_cache.py:264` </location>
<code_context>
def run_module():
    module_args = dict(
        images=dict(type="list", elements="str", required=True),
        username=dict(type="str", required=False),
        password=dict(type="str", required=False, no_log=True),
        validate_certs=dict(type="bool", required=False, default=True),
        cache_dir=dict(
            type="str", required=False, default="/usr/lib/containers-image-cache"
        ),
        preserve_digests=dict(type="bool", required=False, default=True),
        state=dict(
            type="str", required=False, default="present", choices=["present", "absent"]
        ),
    )

    result = dict(changed=False, results=[])

    module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)

    images = module.params["images"]
    username = module.params["username"]
    password = module.params["password"]
    validate_certs = module.params["validate_certs"]
    cache_dir = module.params["cache_dir"]
    preserve_digests = module.params["preserve_digests"]
    state = module.params["state"]

    overall_changed = False
    overall_failed = False

    for image in images:
        image_result = {
            "image": image,
            "changed": False,
            "skipped": False,
            "failed": False,
            "msg": "",
        }

        try:
            cache_path = get_image_cache_path(cache_dir, image)
            image_sha = os.path.basename(cache_path)  # Extract SHA from cache path

            if state == "present":
                # Check if image is already cached
                if is_image_cached(cache_path):
                    image_result["skipped"] = True
                    image_result["msg"] = (
                        f"Image {image} already cached at {cache_path}"
                    )
                else:
                    if module.check_mode:
                        image_result["changed"] = True
                        image_result["msg"] = (
                            f"Would cache image {image} to {cache_path}"
                        )
                    else:
                        # Copy image to cache
                        success, stdout, stderr = run_skopeo_copy(
                            module,
                            image,
                            cache_path,
                            username,
                            password,
                            validate_certs,
                            preserve_digests,
                        )
                        image_result["stdout"] = stdout
                        if success:
                            image_result["changed"] = True
                            image_result["cache_path"] = cache_path
                            image_result["msg"] = (
                                f"Successfully cached image {image} to {cache_path}"
                            )
                            overall_changed = True

                            # Update mapping file with image name and SHA
                            mapping_success, mapping_error = update_mapping_file(
                                cache_dir, image, image_sha
                            )
                            if not mapping_success:
                                module.warn(
                                    f"Failed to update mapping file: {mapping_error}"
                                )
                        else:
                            image_result["failed"] = True
                            error_msg = f"Failed to cache image {image}"
                            if stderr:
                                error_msg += f": {stderr}"
                            image_result["msg"] = error_msg
                            overall_failed = True
            elif state == "absent":
                # Check if image is cached and remove it
                if is_image_cached(cache_path):
                    if module.check_mode:
                        image_result["changed"] = True
                        image_result["msg"] = (
                            f"Would remove cached image {image} from {cache_path}"
                        )
                    else:
                        # Remove cached image
                        remove_success, remove_error = remove_cached_image(cache_path)

                        if remove_success:
                            image_result["changed"] = True
                            image_result["msg"] = (
                                f"Successfully removed cached image {image} from {cache_path}"
                            )
                            overall_changed = True

                            # Remove from mapping file using image name and SHA
                            mapping_success, mapping_error = remove_from_mapping_file(
                                cache_dir, image, image_sha
                            )
                            if not mapping_success:
                                module.warn(
                                    f"Failed to update mapping file: {mapping_error}"
                                )
                        else:
                            image_result["failed"] = True
                            image_result["msg"] = (
                                f"Failed to remove cached image {image}: {remove_error}"
                            )
                            overall_failed = True
                else:
                    image_result["skipped"] = True
                    image_result["msg"] = f"Image {image} not cached at {cache_path}"

        except Exception as e:
            image_result["failed"] = True
            image_result["msg"] = f"Error processing image {image}: {str(e)}"
            overall_failed = True
        result["results"].append(image_result)

    result["changed"] = overall_changed
    result["failed"] = overall_failed
    module.exit_json(**result)

</code_context>

<issue_to_address>
**issue (code-quality):** Low code quality found in run\_module - 7% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +34 to +36
until: __podman_image_updated is success
retries: "{{ podman_pull_retry | ternary(3, 0) }}"
failed_when:
Copy link

Choose a reason for hiding this comment

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

suggestion: Retry logic may not handle partial failures in image list.

Currently, the logic treats the batch as a single success or failure, which may mask individual image errors. Please update the handling to check each image's result and retry or report failures accordingly.

Suggested implementation:

- name: Cache container images for bootc (physically bound images) - per image
  vars:
    podman_image_pull_results: []
    podman_image_pull_failures: []
  block:
    - name: Pull each image and retry on failure
      vars:
        image_pull_retries: "{{ podman_pull_retry | ternary(3, 0) }}"
      loop: "{{ __podman_images | unique | list }}"
      loop_control:
        loop_var: podman_image
      until: podman_image_pull_result is success or image_pull_retries == 0
      retries: "{{ podman_pull_retry | ternary(3, 0) }}"
      register: podman_image_pull_result
      no_log: true
      manage_image_cache:
        images: "{{ [podman_image] }}"
        username: "{{ __podman_registry_username
          if __podman_registry_username | length > 0 else omit }}"
        password: "{{ __podman_registry_password
          if __podman_registry_password | length > 0 else omit }}"
        validate_certs: "{{ __podman_validate_certs
          if __podman_validate_certs is not none else omit }}"
        cache_dir: "{{ __podman_image_cache_dir }}"
    - name: Collect failed image pulls
      set_fact:
        podman_image_pull_failures: >-
          {{
            podman_image_pull_failures +
            [item.item] if (item.failed or item is failed) else podman_image_pull_failures
          }}
      loop: "{{ podman_image_pull_result.results | default([]) }}"
      loop_control:
        label: "{{ item.item }}"
    - name: Fail if any image pulls failed and not allowed to continue
      fail:
        msg: "Failed to cache images: {{ podman_image_pull_failures }}"
      when:
        - podman_image_pull_failures | length > 0
        - not __podman_continue_if_pull_fails
=======

  • If manage_image_cache is a custom module, ensure it supports single-image operation.
  • You may need to adjust variable names and result parsing to match your module's output format.
  • Remove or update any references to __podman_image_updated elsewhere in the playbook.

Comment on lines +178 to +180
if rc == 0:
return True, stdout, None
else:
return False, stdout, stderr
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Replace if statement with if expression (assign-if-exp)

Suggested change
if rc == 0:
return True, stdout, None
else:
return False, stdout, stderr
return (True, stdout, None) if rc == 0 else (False, stdout, stderr)

return False, str(e)


def run_module():
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Low code quality found in run_module - 7% (low-code-quality)


ExplanationThe quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

  • Reduce the function length by extracting pieces of functionality out into
    their own functions. This is the most important thing you can do - ideally a
    function should be less than 10 lines.
  • Reduce nesting, perhaps by introducing guard clauses to return early.
  • Ensure that variables are tightly scoped, so that code using related concepts
    sits together within the function rather than being scattered.

@richm richm force-pushed the bootc branch 9 times, most recently from fdea749 to 69e1fbe Compare September 19, 2025 13:40
richm added a commit to richm/lsr_.github that referenced this pull request Sep 19, 2025
and fix ansible-lint for podman
needed for podman bootc support PR
linux-system-roles/podman#245
richm added a commit to linux-system-roles/.github that referenced this pull request Sep 19, 2025
and fix ansible-lint for podman
needed for podman bootc support PR
linux-system-roles/podman#245
@vrothberg
Copy link
Member

I don't have the cycles (or competences) to review this PR but I want to share my excitement about this feature. Thanks a ton for enabling this use case 🙏

@richm
Copy link
Contributor Author

richm commented Sep 20, 2025

[citest]

Feature: Support running this role during container builds.

Reason: This is particularly useful for building bootc derivative OSes.

Result: User can configure the image at build time.

Signed-off-by: Rich Megginson <rmeggins@redhat.com>
@richm
Copy link
Contributor Author

richm commented Sep 20, 2025

[citest]

@richm richm requested a review from spetrosi September 22, 2025 16:28
@richm
Copy link
Contributor Author

richm commented Sep 22, 2025

The issue with centos-10-bootc looks like some sort of issue with bootc on centos-10 - all other bootc platforms work fine

@@ -0,0 +1,412 @@
#!/usr/bin/python
Copy link
Contributor

Choose a reason for hiding this comment

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

You add this module only here or it will be used somewhere else? Do we need to cover it with unit tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only here. This is really just a small helper to do the skopeo stuff. I could have done it with command or shell - but doing it in python makes formatting the output much easier. I don't think we need unit tests for this.

@richm richm merged commit a7588ac into linux-system-roles:main Oct 6, 2025
36 of 38 checks passed
@richm richm deleted the bootc branch October 6, 2025 17:49
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.

3 participants