Skip to content

Resolves bad rebase for artifact pulling#2473

Merged
olliewalsh merged 1 commit intocontainers:mainfrom
ramalama-labs:feat/artifact-pulling-v2
Mar 3, 2026
Merged

Resolves bad rebase for artifact pulling#2473
olliewalsh merged 1 commit intocontainers:mainfrom
ramalama-labs:feat/artifact-pulling-v2

Conversation

@ieaves
Copy link
Collaborator

@ieaves ieaves commented Feb 26, 2026

Summary by Sourcery

Refactor OCI transport and artifact handling to use pluggable strategies with proper OCI/CNCF model manifest support, improve engine/version detection and SemVer handling, and extend CLI and tests to better support artifacts, RLCR pulling, and container interoperability.

New Features:

  • Introduce OCI reference parsing and CNCF model artifact spec support, including manifest, descriptor, and file metadata helpers.
  • Add pluggable OCI strategies for podman/docker images and artifacts, including HTTP-based artifact download and local snapshot mounting.
  • Enable RLCR models to pull and mount artifacts via the new OCI strategies and support HTTP artifact fallback with snapshot creation.
  • Add an OCI type resolver to classify references as images or artifacts using engine, registry, and model store state.

Bug Fixes:

  • Fix artifact list output to handle empty or malformed podman output without crashing and ensure JSON parsing is robust.
  • Correct engine version handling by parsing semantic versions, avoiding string comparison errors and caching results.
  • Ensure inspect and rm behavior for OCI models consistently delegate to the selected strategy and respect ignore flags.
  • Fix API provider key resolution to fall back correctly and improve error messages for unsupported chat providers.
  • Adjust error expectations and matching in tests for non-existent images and invalid artifact operations.

Enhancements:

  • Refactor OCI transport to delegate pull, remove, exists, inspect, entrypoint, and mount behavior to strategy implementations instead of inlined logic.
  • Unify model listing across images, manifests, and artifacts with de-duplication and UTC-normalized timestamps.
  • Simplify artifact detection via a type guard and properties instead of ad-hoc inspection, and use strategy kind for mount decisions.
  • Tighten CLI behavior for push/inspect/rm, including rejecting OCI sources for conversion targets and requiring a model for inspect.
  • Improve compose and quadlet/kube generation to correctly handle artifact-backed models and pass additional runtime options like max_model_len.
  • Refine chat CLI argument serialization so arbitrary ARGS are passed safely after --, matching shell semantics.

Tests:

  • Expand system and e2e artifact tests to cover lifecycle, listing (including JSON), large files, concurrent operations, error handling, and config precedence without podman-version skips.
  • Update e2e serve, quadlet, artifact, and help tests to reflect new behavior (e.g., rm semantics, error messages, runtime options).
  • Add unit tests for OCI spec validation, OCI tools list/dedup behavior, SemVer-based engine capability checks, and OCI strategies (podman, docker, HTTP).
  • Extend RLCR and transport base unit tests to cover artifact fallback, snapshot creation, and engine-specific mount setup for images vs artifacts.
  • Adjust CLI argument tests to account for new ARGS handling and remove obsolete vLLM command expectations.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 26, 2026

Reviewer's Guide

Refactors OCI transport handling into a strategy-based architecture to reliably distinguish and operate on OCI images vs artifacts (including HTTP-based artifact snapshots), tightens engine/version handling via SemVer, and updates CLI behavior, mounts, and tests to fix bad artifact pulling/rebase issues and improve robustness of listing, removal, and quadlet/kube generation.

File-Level Changes

Change Details Files
Refactor OCI transport to use strategy pattern for images vs artifacts and centralize OCI reference handling.
  • Move OCI transport implementation into ramalama.transports.oci.oci and expose via new package module
  • Introduce OciRef/Oci reference parsing and resolution utilities (including split_oci_reference and list_models deduplication)
  • Add OCIStrategyFactory and BaseOCIStrategy hierarchy (podman/docker image strategies, podman/HTTP artifact strategies)
  • Change OCI.pull/remove/exists/inspect/mount_cmd/entrypoint_path to delegate to strategy and simplify artifact detection
ramalama/transports/oci.py
ramalama/transports/oci/oci.py
ramalama/oci_tools.py
ramalama/transports/oci/strategy.py
ramalama/transports/oci/strategies.py
ramalama/transports/oci/__init__.py
Implement HTTP/registry-based artifact download pipeline backed by the model store and OCI spec helpers.
  • Add OCIRegistryClient and download_oci_artifact to fetch manifests/blobs over HTTPS and materialize model snapshots
  • Define CNAI/CNCF model artifact spec helpers (media types, annotations, FileMetadata, Manifest/Descriptor validation)
  • Wire RLCR and OCI strategies to use HTTP artifact fallback and snapshot existence checks via model_store
  • Enhance SemVer and engine_version to support robust Podman feature gating and caching
ramalama/transports/oci/oci_artifact.py
ramalama/transports/oci/spec.py
ramalama/transports/rlcr.py
ramalama/common.py
ramalama/annotations.py
Adjust Transport base behavior for OCI models, entrypoint resolution, mounts, and quadlet/kube generation.
  • Introduce is_oci() type guard and remove cached artifact flag in favor of strategy-based is_artifact property
  • Update _get_entry_model_path to use entrypoint_path for all OCI transports and remove artifact_name-based path probing
  • Modify setup_mounts to use strategy.kind to choose image vs artifact mounts and to delegate to mount_cmd for docker volume cases
  • Ensure quadlet/kube/compose generation paths receive correct is_artifact flag and handle missing server_process safely in serve
ramalama/transports/base.py
ramalama/compose.py
ramalama/rag.py
Tighten CLI behavior for push/rm/inspect and chat/api provider configuration.
  • Disallow converting from an OCI-based SOURCE in push_cli when TARGET is specified
  • Simplify rm flow to rely on Transport.remove (including OCI strategies) instead of ad-hoc _rm_oci_model fallback
  • Make inspect MODEL argument mandatory at parser level and simplify inspect_cli
  • Fix chat provider error message and ensure provider API key resolution prefers scheme-specific keys
ramalama/cli.py
ramalama/chat_providers/api_providers.py
Broaden and harden artifact/image/system tests, including list output, artifacts lifecycle, strategies, and OCI tooling.
  • Rework artifact system tests to drop Podman version gating, use oci:// refs for rm, and add coverage for large files, size accuracy, concurrency, and JSON listing
  • Add unit tests for OCI spec parsing/validation, strategies implementations, artifact resolver behavior, RLCR artifact fallback, and oci_tools list_models/list_artifacts edge cases
  • Adjust transport_base tests to force specific OCI strategies and split Podman vs Docker mount behavior expectations
  • Update various e2e tests (serve, quadlet, artifact, help) for new behaviors, paths, and patterns (including reasoning-budget and error messages)
test/system/056-artifact.bats
test/e2e/test_artifact.py
test/e2e/test_serve.py
test/e2e/test_help.py
test/unit/test_transport_base.py
test/unit/test_rlcr.py
test/unit/test_oci_tools.py
test/unit/test_artifact_strategies_impl.py
test/unit/test_artifact_strategy.py
test/unit/test_oci_spec.py
test/unit/test_cli_args.py
test/unit/providers/test_openai_provider.py
test/conftest.py

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ieaves, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly overhauls the OCI artifact pulling and management system, addressing previous rebase conflicts and enhancing the overall reliability and standardization of artifact interactions. By introducing a flexible strategy pattern and adopting CNCF specifications, the system is now more adaptable to different container environments and provides a more consistent user experience for model lifecycle operations. The integration of semantic versioning further refines version-dependent logic, ensuring compatibility and correct behavior across various container engine versions.

Highlights

  • OCI Artifact Handling Refactor: The core logic for OCI artifact management has been significantly refactored, introducing a strategy pattern to handle different container engines (Podman, Docker) and direct HTTP downloads for artifacts. This enhances flexibility and robustness in how models are pulled, inspected, and removed.
  • CNCF Standardization: OCI artifact media types and annotations have been updated to align with Cloud Native Computing Foundation (CNCF) specifications, replacing previous cnai prefixes with cncf for better industry standard compliance.
  • Semantic Versioning (SemVer) Integration: A new SemVer utility has been introduced to accurately parse and compare engine versions, improving the reliability of version-dependent logic, particularly for Podman artifact support.
  • Improved Model Listing and Removal: The list_models functionality now includes deduplication logic to prevent redundant entries from different sources (images, manifests, artifacts) and handles various edge cases more gracefully. Model removal (rm) commands have also been streamlined to leverage the new OCI strategies.
  • Code Cleanup and Robustness: Numerous small improvements were made across the codebase, including better error handling for JSON parsing, clearer type hints, removal of deprecated code, and general test suite enhancements to ensure stability and maintainability.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • ramalama/annotations.py
    • Updated OCI artifact type and annotation keys from 'cnai' to 'cncf' for standardization.
  • ramalama/chat_providers/api_providers.py
    • Added a null check for API key resolvers to ensure a valid key is returned.
    • Corrected a typo in an error message from 'No support chat providers' to 'No supported chat provider'.
  • ramalama/cli.py
    • Removed an unnecessary blank line in the info_cli function.
    • Added a ValueError check in push_cli to prevent converting from OCI-based images.
    • Simplified the _rm_model function by removing explicit OCI removal logic, delegating it to the new OCI strategies.
    • Modified inspect_parser to make the MODEL argument required and removed redundant error handling.
  • ramalama/common.py
    • Imported dataclass and Path for new data structures.
    • Enhanced engine_version to use lru_cache for performance, accept Path | str for flexibility, and return a SemVer object.
    • Introduced SemVer dataclass and parse_semver function for robust semantic version parsing.
  • ramalama/compose.py
    • Updated type hints from Tuple[str, str] to tuple[str, str] for modern Python syntax.
  • ramalama/oci_tools.py
    • Imported dataclass, chain, and SemVer for new functionalities.
    • Improved list_artifacts with checks for empty output and json.JSONDecodeError for increased robustness.
    • Updated engine_supports_manifest_attributes to leverage the new SemVer comparison logic.
    • Refactored list_models to use itertools.chain and a seen set for efficient deduplication of model entries.
    • Added OciRef dataclass and split_oci_reference function for structured OCI reference parsing.
  • ramalama/rag.py
    • Updated the import path for OCI to reflect its new module location.
    • Removed an unnecessary blank line in the run method.
  • ramalama/transports/base.py
    • Removed cached_property import and the artifact property, replacing it with a standard property.
    • Added TypeGuard import and an is_oci type guard function for better type checking.
    • Modified _cleanup_server_process to safely handle potentially None server process arguments.
    • Updated quadlet, quadlet_kube, and kube methods to use the new is_artifact property.
    • Refactored _get_entry_model_path to utilize the is_oci type guard and delegate to entrypoint_path.
    • Adjusted setup_mounts to use the OCI strategy's kind and mount command generation.
  • ramalama/transports/oci/init.py
    • Added __init__.py to export the OCI class from its new sub-module.
  • ramalama/transports/oci/oci.py
    • Renamed from ramalama/transports/oci.py and moved to ramalama/transports/oci/oci.py.
    • Imported cached_property, OciRef, oci_spec, BaseOCIStrategy, and OCIStrategyFactory for the new OCI strategy pattern.
    • Removed direct engine_version import, now handled via common.py.
    • Introduced ref and strategy properties, and an is_artifact property that delegates to the selected strategy.
    • Added _target_decompose and _has_local_snapshot helper methods.
    • Modified _add_artifact to use oci_spec annotations for file path, metadata, and media type.
    • Simplified pull, remove, exists, inspect, and mount_cmd methods by delegating their logic to the resolved OCI strategy.
    • Removed internal _inspect and artifact_name methods, as their functionality is now handled by strategies.
  • ramalama/transports/oci/oci_artifact.py
    • Added new file to implement OCI artifact downloading and snapshot creation, including OCIRegistryClient for registry interaction.
  • ramalama/transports/oci/resolver.py
    • Added new file to provide logic for resolving OCI reference types (artifact, image, unknown) based on engine and model store state.
  • ramalama/transports/oci/spec.py
    • Added new file to define OCI artifact specification constants and data structures like FileMetadata, Descriptor, and Manifest.
  • ramalama/transports/oci/strategies.py
    • Added new file to define the abstract BaseOCIStrategy and concrete implementations for Podman Artifact, Podman Image, Docker Image, and HTTP Artifact strategies.
  • ramalama/transports/oci/strategy.py
    • Added new file to implement a factory for selecting the appropriate OCI strategy based on the container engine and its version.
  • ramalama/transports/rlcr.py
    • Updated the import path for OCI to its new module location.
    • Refactored _get_entry_model_path to integrate with the new strategy-based OCI logic.
  • ramalama/transports/transport_factory.py
    • Updated the import path for OCI to its new module location.
  • test/conftest.py
    • Improved get_podman_version to handle FileNotFoundError and TimeoutExpired more robustly, and added a shutil.which check.
    • Removed skip_if_podman_too_old from several tests, as version checks are now handled dynamically by the new OCI strategies.
  • test/e2e/test_artifact.py
    • Removed the path_to_uri helper function and replaced its usage with f-strings for direct path handling.
    • Eliminated skip_if_podman_too_old and skip_if_windows decorators from many tests, streamlining test execution.
    • Updated rm commands to consistently use the oci://localhost/ prefix for artifact removal.
  • test/e2e/test_help.py
    • Corrected a typo in config_runtime from 'lamma.cpp' to 'llama.cpp'.
  • test/e2e/test_serve.py
    • Removed skip_if_no_container from some tests, allowing broader execution.
    • Added max_model_len to quadlet generation tests.
    • Refined error message checks for non-existent images.
  • test/system/056-artifact.bats
    • Removed skip_if_podman_too_old from numerous tests, aligning with the new OCI strategy handling.
    • Updated rm commands for consistency.
    • Added a new test case for 'large file handling' in artifacts.
    • Removed a skip directive for concurrent operations, indicating a fix for previous issues.
    • Adjusted error code expectations for non-existent files.
  • test/unit/command/data/engines/vllm.yaml
    • Removed the vllm.yaml engine definition file, indicating a refactoring or removal of direct vLLM engine support.
  • test/unit/command/data/schema/schema.1-0-0.json
    • Removed serve --rag, run --rag, rag, convert, and quantize from the command enum, reflecting changes in supported commands.
  • test/unit/command/test_factory.py
    • Removed vLLM-related test cases, consistent with the removal of vllm.yaml.
  • test/unit/providers/test_openai_provider.py
    • Renamed test classes OpenAICompletionsProviderTests to TestOpenAICompletionsProvider and OpenAIResponsesProviderTests to TestOpenAIResponsesProvider for improved clarity and pytest compatibility.
  • test/unit/test_artifact_strategies_impl.py
    • Added new file to test the implementations of the various OCI artifact strategies.
  • test/unit/test_artifact_strategy.py
    • Added new file to test the logic for selecting the appropriate OCI strategy based on engine and version.
  • test/unit/test_cli_args.py
    • Removed string import as it was no longer needed.
    • Refactored args_to_cli_args to handle positional arguments more robustly, especially for ARGS.
    • Removed ARGS from st.builds in test_chat_endpoint.
  • test/unit/test_oci.py
    • Updated the import path for OCI to its new module location.
  • test/unit/test_oci_spec.py
    • Added new file to test the parsing and serialization of OCI artifact specification data structures.
  • test/unit/test_oci_tools.py
    • Added new file to test OCI tool functions, including model listing deduplication, timezone handling, manifest filtering, and SemVer usage.
  • test/unit/test_rlcr.py
    • Added patch for download_oci_artifact and OCIRegistryClient to facilitate testing of artifact download and fallback mechanisms.
    • Expanded the args fixture with additional attributes for comprehensive testing.
  • test/unit/test_transport_base.py
    • Updated the import path for OCI to its new module location.
    • Added force_oci_image and force_oci_artifact fixtures to control OCI strategy resolution in tests.
    • Updated test_extract_model_identifiers to use force_oci_image for consistent testing.
    • Refactored TestOCIModelSetupMounts into TestOCIModelSetupMountsPodman and TestOCIModelSetupMountsDocker to provide clearer, engine-specific testing of OCI model mounting.
Activity
  • The pull request introduces a comprehensive refactoring of OCI artifact handling, moving towards a more modular and standardized approach.
  • New files were added to define OCI artifact specifications, strategies for different container engines, and a resolver for determining the correct strategy.
  • Existing OCI-related code was updated to integrate with these new strategies, simplifying logic and improving maintainability.
  • Semantic versioning was introduced to accurately compare container engine versions, particularly for feature detection.
  • Numerous unit and end-to-end tests were updated or added to cover the new OCI artifact functionality and ensure its correctness across different environments.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ieaves ieaves force-pushed the feat/artifact-pulling-v2 branch from 3be730a to d3f9131 Compare February 26, 2026 20:08
@ieaves ieaves temporarily deployed to macos-installer February 26, 2026 20:08 — with GitHub Actions Inactive
Copy link
Contributor

@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 - I've found 4 issues, and left some high level feedback:

  • The new OCI.remove implementation delegates to the strategy and returns a boolean, but the CLI _rm_model caller no longer checks the return value; consider either raising a KeyError/CalledProcessError on failure or having _rm_model handle a False return so failed removals are surfaced to the user instead of silently ignored.
  • engine_version now returns a SemVer instance instead of a string; please ensure all existing callers (including any not touched in this PR) have been updated to avoid string comparisons or other assumptions about the previous return type.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new OCI.remove implementation delegates to the strategy and returns a boolean, but the CLI _rm_model caller no longer checks the return value; consider either raising a KeyError/CalledProcessError on failure or having _rm_model handle a False return so failed removals are surfaced to the user instead of silently ignored.
- engine_version now returns a SemVer instance instead of a string; please ensure all existing callers (including any not touched in this PR) have been updated to avoid string comparisons or other assumptions about the previous return type.

## Individual Comments

### Comment 1
<location path="ramalama/transports/oci/strategy.py" line_range="46-47" />
<code_context>


-def engine_version(engine: SUPPORTED_ENGINES) -> str:
+@lru_cache
+def engine_version(engine: SUPPORTED_ENGINES | Path | str) -> SemVer:
     # Create manifest list for target with imageid
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `lru_cache` with a `ModelStore` argument will fail because `ModelStore` is likely unhashable.

Because `lru_cache` uses function arguments as cache keys, this will raise `TypeError: unhashable type` on the first call if `ModelStore` doesn’t define `__hash__`. If you need caching, restrict the cache key to hashable inputs (e.g. `(engine, engine_name, kind)`) and pass `model_store` via another mechanism (closure, instance attribute), or use your own dict keyed by a stable identifier such as the model store root path instead of `lru_cache`.
</issue_to_address>

### Comment 2
<location path="test/e2e/test_artifact.py" line_range="18-17" />
<code_context>

 import pytest

-from test.conftest import skip_if_docker, skip_if_no_container, skip_if_podman_too_old, skip_if_windows
+from test.conftest import skip_if_docker, skip_if_no_container
 from test.e2e.utils import RamalamaExecWorkspace, check_output

</code_context>
<issue_to_address>
**issue (testing):** Artifact e2e tests no longer skip Windows but still assume POSIX-style `file://` URIs, which will likely break on Windows.

These tests used to be safe on Windows by combining `path_to_uri` with `@skip_if_windows`. Now we construct `file://` URIs inline and have removed `skip_if_windows` from some artifact e2e tests. On Windows, that URI format is invalid and will cause consistent test failures. Please either reintroduce a helper that produces valid `file://` URIs on Windows (e.g., via `path.as_uri()`) or retain `skip_if_windows` on tests that build these URIs manually.
</issue_to_address>

### Comment 3
<location path="test/system/056-artifact.bats" line_range="343" />
<code_context>
+    is "$output" ".*large-artifact.*latest" "large artifact was created"
+
+    # Verify size is reasonable
+    size=$(run_ramalama list --json | jq -r '.[0].size')
+    assert [ "$size" -gt 1000000 ] "artifact size is at least 1MB"
+
</code_context>
<issue_to_address>
**suggestion (testing):** JSON list tests assume the artifact is at index 0, which can make them order-dependent and flaky.

In the new "large file handling" (and "size reporting accuracy") test, size is read with `jq -r '.[0].size'`. If other artifacts are present, index 0 may not be the newly created one, making the test order-dependent and flaky.

Instead, filter by `name`, e.g.:

```bash
size=$(run_ramalama list --json | jq -r '.[] | select(.name=="large-artifact:latest") | .size')
```

and similarly for `size-test-artifact`, so the assertion is stable regardless of list ordering or additional entries.

Suggested implementation:

```shell
    # Verify size is reasonable
    size=$(run_ramalama list --json | jq -r '.[] | select(.name=="large-artifact:latest") | .size')
    assert [ "$size" -gt 1000000 ] "artifact size is at least 1MB"

```

There is another JSON size-based assertion for `size-test-artifact` (in the “size reporting accuracy” test or equivalent). To make that test order-independent as well, update its `jq` call from using `.[0].size` (or any other index-based access) to:

```bash
size=$(run_ramalama list --json | jq -r '.[] | select(.name=="size-test-artifact:latest") | .size')
```

Make sure the `select(.name==...)` string exactly matches how the artifact is named in the test (`size-test-artifact:latest` or the appropriate tag).
</issue_to_address>

### Comment 4
<location path="test/unit/test_transport_base.py" line_range="329-338" />
<code_context>
+class TestOCIModelSetupMountsDocker:
</code_context>
<issue_to_address>
**suggestion (testing):** Docker OCI mount tests only cover the image case; artifact mounting behavior is not exercised.

Given the new strategy-based OCI handling and Docker artifact support, please add a complementary test that exercises an OCI artifact under Docker (e.g., via `force_oci_artifact`). It should assert that `setup_mounts` invokes `mount_cmd` with the expected bind-mount arguments for snapshot-based artifacts, so both image and artifact flows are covered and future regressions in strategy selection or mount construction are caught.

Suggested implementation:

```python
class TestOCIModelSetupMountsDocker:
    """Test OCI model setup_mounts for Docker"""

    @pytest.fixture
    def mock_docker_engine(self):
        """Create a mock Docker engine for testing"""
        engine = Mock()
        engine.use_podman = False
        engine.use_docker = True
        engine.add = Mock()
        return engine

    def test_setup_mounts_oci_artifact_uses_bind_mount_for_snapshot(
        self,
        mock_docker_engine,
        oci_model_docker,
        monkeypatch,
    ):
        """
        When forcing OCI artifacts under Docker, setup_mounts should invoke
        mount_cmd with a bind mount targeting the snapshot-based artifact,
        not an image mount.
        """
        # Ensure the Docker engine is used for this model
        oci_model_docker.engine = mock_docker_engine

        # Spy on mount_cmd so we can assert how it is invoked
        mount_cmd_spy = monkeypatch.spy(oci_model_docker, "mount_cmd")

        # Force the OCI artifact path to be used instead of an image
        args = SimpleNamespace(force_oci_artifact=True)

        oci_model_docker.setup_mounts(args)

        # mount_cmd should have been called exactly once with the bind-mount args
        mount_cmd_spy.assert_called_once()
        (mount_args,) = mount_cmd_spy.call_args.args

        # Expect a bind mount (snapshot-based artifact), not an image mount
        # and ensure it targets the model mount directory.
        assert any(
            mount.startswith("--mount=type=bind")
            and f",destination={MNT_DIR}" in mount
            for mount in mount_args
        ), f"Expected a bind mount to {MNT_DIR} for OCI artifact, got: {mount_args}"

```

To make this test work with the rest of the file, you will also need to:

1. Ensure `SimpleNamespace` is imported at the top of `test_transport_base.py`:
   ```python
   from types import SimpleNamespace
   ```

2. Provide (or adapt) an `oci_model_docker` fixture used in the new test so that:
   - It has an `engine` attribute (settable to `mock_docker_engine`),
   - It implements `setup_mounts(args)`,
   - It implements `mount_cmd(mount_args)` which ultimately calls `engine.add(mount_args)`.

   If your existing Podman tests already use a fixture like `oci_model_podman` with this interface, you can mirror that fixture for Docker or generalize it and reuse it here.

3. If your actual mount command format differs (e.g., destination subpath is `f"{MNT_DIR}/models"` or similar), adjust the `destination` assertion in the test accordingly so it matches the real bind-mount arguments used for snapshot-based artifacts.
</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 +46 to +47
@lru_cache
def get_strategy(
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Using lru_cache with a ModelStore argument will fail because ModelStore is likely unhashable.

Because lru_cache uses function arguments as cache keys, this will raise TypeError: unhashable type on the first call if ModelStore doesn’t define __hash__. If you need caching, restrict the cache key to hashable inputs (e.g. (engine, engine_name, kind)) and pass model_store via another mechanism (closure, instance attribute), or use your own dict keyed by a stable identifier such as the model store root path instead of lru_cache.

Copy link
Collaborator

Choose a reason for hiding this comment

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

is this correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

No – that comment was overcautious here.

ModelStore doesn’t define __eq__ or __hash__, so it inherits object.__hash__ and is hashable by identity. That means:

  • @lru_cache will not raise TypeError for the model_store argument.
  • The cache key will distinguish different ModelStore instances, even if they point at the same underlying directory.

So as the code stands, using @lru_cache on get_strategy with model_store: ModelStore is technically fine.

If you want to be a bit more future-proof / explicit, you have a couple of options, but none are strictly required:

  • Keep as-is, but rely on the current identity-based hashing of ModelStore.

  • Or: change the cache key to something more stable, e.g. base path, and avoid using the ModelStore object itself:

    @lru_cache
    def _get_strategy_cached(
        engine: str,
        engine_name: SUPPORTED_ENGINES,
        model_store_base: str,
        kind: Literal["image", "artifact"],
    ) -> BaseArtifactStrategy | BaseImageStrategy:
        cls_generator = get_engine_image_strategy if kind == "image" else get_engine_artifact_strategy
        cls = cls_generator(engine, engine_name)
        # model_store is passed in by the caller, not cached by value
        raise RuntimeError("This helper should only be called through a wrapper that supplies model_store")

    and then wrap it in something that passes the real ModelStore instance. But that’s only worth doing if you care about decoupling the cache key from the concrete ModelStore object.

Bottom line: there’s no immediate bug from lru_cache + ModelStore in the current code; my original warning about TypeError: unhashable type doesn’t apply here.

from subprocess import STDOUT, CalledProcessError

import pytest

Copy link
Contributor

Choose a reason for hiding this comment

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

issue (testing): Artifact e2e tests no longer skip Windows but still assume POSIX-style file:// URIs, which will likely break on Windows.

These tests used to be safe on Windows by combining path_to_uri with @skip_if_windows. Now we construct file:// URIs inline and have removed skip_if_windows from some artifact e2e tests. On Windows, that URI format is invalid and will cause consistent test failures. Please either reintroduce a helper that produces valid file:// URIs on Windows (e.g., via path.as_uri()) or retain skip_if_windows on tests that build these URIs manually.

is "$output" ".*large-artifact.*latest" "large artifact was created"

# Verify size is reasonable
size=$(run_ramalama list --json | jq -r '.[0].size')
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): JSON list tests assume the artifact is at index 0, which can make them order-dependent and flaky.

In the new "large file handling" (and "size reporting accuracy") test, size is read with jq -r '.[0].size'. If other artifacts are present, index 0 may not be the newly created one, making the test order-dependent and flaky.

Instead, filter by name, e.g.:

size=$(run_ramalama list --json | jq -r '.[] | select(.name=="large-artifact:latest") | .size')

and similarly for size-test-artifact, so the assertion is stable regardless of list ordering or additional entries.

Suggested implementation:

    # Verify size is reasonable
    size=$(run_ramalama list --json | jq -r '.[] | select(.name=="large-artifact:latest") | .size')
    assert [ "$size" -gt 1000000 ] "artifact size is at least 1MB"

There is another JSON size-based assertion for size-test-artifact (in the “size reporting accuracy” test or equivalent). To make that test order-independent as well, update its jq call from using .[0].size (or any other index-based access) to:

size=$(run_ramalama list --json | jq -r '.[] | select(.name=="size-test-artifact:latest") | .size')

Make sure the select(.name==...) string exactly matches how the artifact is named in the test (size-test-artifact:latest or the appropriate tag).

Comment on lines +329 to +338
class TestOCIModelSetupMountsDocker:
"""Test OCI model setup_mounts for Docker"""

@pytest.fixture
def mock_docker_engine(self):
"""Create a mock Docker engine for testing"""
engine = Mock()
engine.use_podman = False
engine.use_docker = True
engine.add = Mock()
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Docker OCI mount tests only cover the image case; artifact mounting behavior is not exercised.

Given the new strategy-based OCI handling and Docker artifact support, please add a complementary test that exercises an OCI artifact under Docker (e.g., via force_oci_artifact). It should assert that setup_mounts invokes mount_cmd with the expected bind-mount arguments for snapshot-based artifacts, so both image and artifact flows are covered and future regressions in strategy selection or mount construction are caught.

Suggested implementation:

class TestOCIModelSetupMountsDocker:
    """Test OCI model setup_mounts for Docker"""

    @pytest.fixture
    def mock_docker_engine(self):
        """Create a mock Docker engine for testing"""
        engine = Mock()
        engine.use_podman = False
        engine.use_docker = True
        engine.add = Mock()
        return engine

    def test_setup_mounts_oci_artifact_uses_bind_mount_for_snapshot(
        self,
        mock_docker_engine,
        oci_model_docker,
        monkeypatch,
    ):
        """
        When forcing OCI artifacts under Docker, setup_mounts should invoke
        mount_cmd with a bind mount targeting the snapshot-based artifact,
        not an image mount.
        """
        # Ensure the Docker engine is used for this model
        oci_model_docker.engine = mock_docker_engine

        # Spy on mount_cmd so we can assert how it is invoked
        mount_cmd_spy = monkeypatch.spy(oci_model_docker, "mount_cmd")

        # Force the OCI artifact path to be used instead of an image
        args = SimpleNamespace(force_oci_artifact=True)

        oci_model_docker.setup_mounts(args)

        # mount_cmd should have been called exactly once with the bind-mount args
        mount_cmd_spy.assert_called_once()
        (mount_args,) = mount_cmd_spy.call_args.args

        # Expect a bind mount (snapshot-based artifact), not an image mount
        # and ensure it targets the model mount directory.
        assert any(
            mount.startswith("--mount=type=bind")
            and f",destination={MNT_DIR}" in mount
            for mount in mount_args
        ), f"Expected a bind mount to {MNT_DIR} for OCI artifact, got: {mount_args}"

To make this test work with the rest of the file, you will also need to:

  1. Ensure SimpleNamespace is imported at the top of test_transport_base.py:

    from types import SimpleNamespace
  2. Provide (or adapt) an oci_model_docker fixture used in the new test so that:

    • It has an engine attribute (settable to mock_docker_engine),
    • It implements setup_mounts(args),
    • It implements mount_cmd(mount_args) which ultimately calls engine.add(mount_args).

    If your existing Podman tests already use a fixture like oci_model_podman with this interface, you can mirror that fixture for Docker or generalize it and reuse it here.

  3. If your actual mount command format differs (e.g., destination subpath is f"{MNT_DIR}/models" or similar), adjust the destination assertion in the test accordingly so it matches the real bind-mount arguments used for snapshot-based artifacts.

Copy link
Contributor

@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

This is a substantial and well-executed refactoring of the OCI transport layer. The introduction of the strategy pattern to handle different OCI object types (images vs. artifacts) and container engines (Podman vs. Docker) is a significant architectural improvement. This makes the code more modular, maintainable, and extensible. The addition of a fallback mechanism for artifact pulling via HTTP also enhances robustness. The code is well-organized into new logical modules, and the test suite has been updated comprehensively to cover the new functionality. I've made a few minor suggestions to improve exception handling by using more specific exception types instead of broad except Exception: clauses.

Comment on lines +92 to +93
except Exception:
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. It's better to be more specific about the exceptions you expect to handle. In this case, run_cmd can raise subprocess.CalledProcessError and .decode() can raise UnicodeDecodeError. Catching these specific exceptions makes the code more robust and easier to debug.

Suggested change
except Exception:
continue
except (subprocess.CalledProcessError, UnicodeDecodeError):
continue

Comment on lines +74 to +78
except Exception:
raise KeyError(
"You must specify a registry for the model in the form "
f"'oci://registry.acme.org/ns/repo:tag', got instead: {self.model}"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. The unpack operation registry, reference = model.split("/", 1) will raise a ValueError if the split does not produce enough values to unpack. It's better to catch the more specific ValueError.

Suggested change
except Exception:
raise KeyError(
"You must specify a registry for the model in the form "
f"'oci://registry.acme.org/ns/repo:tag', got instead: {self.model}"
)
except ValueError:
raise KeyError(
"You must specify a registry for the model in the form "
f"'oci://registry.acme.org/ns/repo:tag', got instead: {self.model}"
)

Comment on lines +85 to +86
except Exception:
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. It's better to catch subprocess.CalledProcessError, which is what run_cmd is expected to raise on failure. This makes the code more robust. This pattern of catching Exception appears in other methods in this file as well (e.g., remove, HttpArtifactStrategy.exists, etc.) and should be addressed there too. You'll also need to add import subprocess at the top of the file.

Suggested change
except Exception:
return False
except subprocess.CalledProcessError:
return False

Comment on lines +26 to +27
return manifest
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. The client.get_manifest() method can raise exceptions like urllib.error.HTTPError or json.JSONDecodeError. It's better to catch these specific exceptions to make the code more robust. You'll need to import urllib.error and json.

Suggested change
return manifest
except Exception:
except (urllib.error.HTTPError, json.JSONDecodeError):
return None

@ieaves ieaves temporarily deployed to macos-installer February 26, 2026 20:38 — with GitHub Actions Inactive
@rhatdan
Copy link
Member

rhatdan commented Feb 27, 2026

Squash commits.
@olliewalsh PTAL

Copy link
Collaborator

@olliewalsh olliewalsh left a comment

Choose a reason for hiding this comment

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

doesn't resolve the rebase issues

message = (
f"Could not authenticate with {self.provider}."
"The provided API key was either missing or invalid.\n"
" The provided API key was either missing or invalid.\n"
Copy link
Collaborator

Choose a reason for hiding this comment

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

that's an issue but not related to this feature so I would remove and address in a follow-up

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Come on man.

Copy link
Collaborator

@olliewalsh olliewalsh Feb 27, 2026

Choose a reason for hiding this comment

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

Come on man.

Ditto. If this was a trivial PR then sure. However it is a huge PR. Every unrelated change just reduces the signal to noise ratio, requiring far more effort to identify and review the relevant changes.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also more likely to cause conflicts with other PRs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This PR wasn't as large when it was opened in... November? Dan opened his PR with podman in particular after I opened this but we agreed to push his through and rebase this into it which forced the scope to expand.

I just don't think you have any appreciation for how much time I've spent rebasing this thing to keep up with main over the past four months with zero eyes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also more likely to cause conflicts with other PRs

This isn't a credible concern applied to correcting an errant whitespace.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Every unrelated change...

Copy link
Collaborator

Choose a reason for hiding this comment

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

This PR wasn't as large when it was opened in... November? Dan opened his PR with podman in particular after I opened this but we agreed to push his through and rebase this into it which forced the scope to expand.

I just don't think you have any appreciation for how much time I've spent rebasing this thing to keep up with main over the past four months with zero eyes.

Not sure why that frustration is directed my way when the feature was mostly a collaboration between you and @rhatdan.

Resolving merge conflicts when rebasing a branch is never fun. It just can't be avoided sometimes, esp with large PRs or multiple PRs that depend on each other.

Cleaning up a bad rebase really is not fun. It is not how I wanted to spend my Friday afternoon. I've pretty much dropped everything to get this PR merged again ASAP before something else conflicts with it.

Copy link
Collaborator Author

@ieaves ieaves Feb 27, 2026

Choose a reason for hiding this comment

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

Not sure why that frustration is directed my way {...} I've pretty much dropped everything to get this PR merged again ASAP before something else conflicts with it.

Because this thread is almost entirely nits. This thread is about an entirely cosmetic whitespace change that, were it a problem, could have been flagged during review. This is not the appropriate context for long drawn out back and forths about these sorts of stylistic preferences.

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 yeah, this change is nothing - just was the first of many so the thread spawned from here. Most of the unrelated changes that made this PR quite difficult to review have been addressed now in any case

Comment on lines +46 to +47
@lru_cache
def get_strategy(
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this correct?

@ieaves
Copy link
Collaborator Author

ieaves commented Feb 27, 2026

@olliewalsh I don't know what is going on with GitHub's rendering of the diff but there are quite a few of your comments which are responsive to changes not actually in the current PR. Are you looking at an intentionally old commit or something?

For example, I see you commented on the info parser here but the actual diff doesn't contain any of those changes. You can verify those changes aren't in my source repo here as of the most recent push 19 hours ago.

@ieaves ieaves temporarily deployed to macos-installer February 27, 2026 15:34 — with GitHub Actions Inactive
Copy link
Collaborator

@olliewalsh olliewalsh left a comment

Choose a reason for hiding this comment

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

still seeing rebase issues though

from ramalama.transports.huggingface import Huggingface
from ramalama.transports.modelscope import ModelScope
from ramalama.transports.oci import OCI
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work


if TYPE_CHECKING:
from ramalama.chat import ChatOperationalArgs
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work


from ramalama.common import MNT_DIR, run_cmd
from ramalama.transports.oci import OCI
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work

from ramalama.config import get_config
from ramalama.transports.base import Transport, compute_ports, compute_serving_port
from ramalama.transports.oci import OCI
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work

def test_rlcr_inherits_from_oci(self, rlcr_model):
"""Test that RLCR properly inherits from OCI"""
from ramalama.transports.oci import OCI
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work

from ramalama.model_store.store import ModelStore
from ramalama.transports.huggingface import Huggingface
from ramalama.transports.oci import OCI
from ramalama.transports.oci.oci import OCI
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: from ramalama.transports.oci import OCI should still work

@olliewalsh
Copy link
Collaborator

@olliewalsh I don't know what is going on with GitHub's rendering of the diff but there are quite a few of your comments which are responsive to changes not actually in the current PR. Are you looking at an intentionally old commit or something?

For example, I see you commented on the info parser here but the actual diff doesn't contain any of those changes. You can verify those changes aren't in my source repo here as of the most recent push 19 hours ago.

Yeah, that's really bizarre - github has some serious bugs I guess. All of the comments were added to the same diff.



class OpenAICompletionsProviderTests:
class TestOpenAICompletionsProvider:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: not related to this PR

@olliewalsh
Copy link
Collaborator

@olliewalsh I don't know what is going on with GitHub's rendering of the diff but there are quite a few of your comments which are responsive to changes not actually in the current PR. Are you looking at an intentionally old commit or something?
For example, I see you commented on the info parser here but the actual diff doesn't contain any of those changes. You can verify those changes aren't in my source repo here as of the most recent push 19 hours ago.

Yeah, that's really bizarre - github has some serious bugs I guess. All of the comments were added to the same diff.

https://www.githubstatus.com/incidents/kv1lzpgzr9yp looks like it could be related to caching...

@ieaves ieaves temporarily deployed to macos-installer February 27, 2026 16:23 — with GitHub Actions Inactive
@ieaves
Copy link
Collaborator Author

ieaves commented Feb 27, 2026

I've restored the test changes you've identified to upstream/main.

@ieaves ieaves temporarily deployed to macos-installer February 27, 2026 16:41 — with GitHub Actions Inactive
ramalama/cli.py Outdated
)
parser.add_argument("--json", dest="json", action="store_true", help="display AI Model information in JSON format")
parser.add_argument("MODEL", nargs="?", completer=local_models) # positional argument
parser.add_argument("MODEL", completer=local_models) # positional argument
Copy link
Collaborator

Choose a reason for hiding this comment

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

rebase issue?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was part of the original PR. MODEL is actually a required field although in main enforcement is deferred to inspect_cli. It's a legitimate bug fix but can be moved to a future PR.

"""Return a configured API key for the given provider scheme, if any."""

if resolver := PROVIDER_API_KEY_RESOLVERS.get(scheme):
return resolver()
Copy link
Collaborator

@olliewalsh olliewalsh Feb 27, 2026

Choose a reason for hiding this comment

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

Every unrelated change...

e.g this doesn't look related to artifact pulling, but it's not obvious whether it is an intentional change or a rebase issue

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just better edge case handling, no grand conspiracy here.

assert exc_info.value.returncode == 22
assert "quay.io/ramalama/rag" in exc_info.value.output.decode("utf-8")
assert "does not exist" in exc_info.value.output.decode("utf-8")
assert re.search(r"Error: quay.io/ramalama/rag does not exist.*", exc_info.value.output.decode("utf-8"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

this line was relevant AFAICT

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right!

@ieaves
Copy link
Collaborator Author

ieaves commented Feb 27, 2026

@olliewalsh I know both of us are frustrated right now for a variety of reasons but I do appreciate your time reviewing and wanted to say thank you.

@olliewalsh
Copy link
Collaborator

@olliewalsh I know both of us are frustrated right now for a variety of reasons but I do appreciate your time reviewing and wanted to say thank you.

Much appreciated and thanks for being very responsive on getting this sorted!

Copy link
Collaborator

@olliewalsh olliewalsh left a comment

Choose a reason for hiding this comment

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

LGTM

@olliewalsh
Copy link
Collaborator

@rhatdan did you still want the commits squashed?

@olliewalsh
Copy link
Collaborator

@rhatdan did you still want the commits squashed?

@ieaves not sure when Dan will be online to respond, might be best to just squash the commits and get this merged

… podman

Signed-off-by: Ian Eaves <ian.k.eaves@gmail.com>
@ieaves ieaves force-pushed the feat/artifact-pulling-v2 branch from 743a9e2 to d1d19d5 Compare March 3, 2026 00:00
@ieaves ieaves temporarily deployed to macos-installer March 3, 2026 00:00 — with GitHub Actions Inactive
@ieaves
Copy link
Collaborator Author

ieaves commented Mar 3, 2026

No problem.

@olliewalsh olliewalsh merged commit a19da9a into containers:main Mar 3, 2026
35 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.

3 participants