Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/agent_env/artifact/artifacts/docker_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from agent_env.store.ids import fs_safe, image_repository, is_local_id, local_image_repository
from agent_env.store.image_store.local_registry_image_store import LocalRegistryImageStore
from agent_env.store.image_store.oci_registry_credentials import image_reference_problem, is_loopback_host, names_registry, registry_host_from_ref
from agent_env.store.image_store.registry_api import NoSuchImageError, pin_digest, pinned_from
from agent_env.store.image_store.registry_api import NoSuchImageError, RegistryRefusedError, pin_digest, pinned_from
from agent_env.utils.docker.context import BuildContext
from agent_env.utils.docker.build import DEFAULT_BUILD_PLATFORM, build_image, normalize_platform

Expand Down Expand Up @@ -222,7 +222,9 @@ def put_ref(cls, id: str, *, description: str, image_name: str, earlier_pin: str
sandbox that can't pull the image to build it.

``image_name`` must name its registry, read with the configured image store's credentials where it holds them;
one on this machine is refused unless ``id``'s image store is the local registry."""
one on this machine is refused unless ``id``'s image store is the local registry. One already pinned to a
digest, in a registry that refuses to serve it to a store holding no credentials for it, is recorded as given,
for a sandbox whose Docker is logged in to that registry to pull."""
from agent_env.artifact.store import get_artifact_store
from agent_env.config import get_config

Expand All @@ -240,7 +242,13 @@ def put_ref(cls, id: str, *, description: str, image_name: str, earlier_pin: str
if earlier_pin is not None and pinned_from(earlier_pin, image_name):
pinned = earlier_pin
else:
pinned = pin_digest(image_name, config.get_image_store().auth(image_name))
auth = config.get_image_store().auth(image_name)
try:
pinned = pin_digest(image_name, auth)
except RegistryRefusedError:
if auth is not None or "@" not in image_name:
raise
pinned = image_name
if context_path is None:
return get_artifact_store().put_document(cls(id=id, description=description, image_name=pinned))
image_name, _, image_digest = pinned.rpartition("@")
Expand Down
6 changes: 4 additions & 2 deletions src/agent_env/bundle/_import_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,10 @@ def _needs_lines(imported: BundleImport) -> list[str]:
refs = [pulled.image for pulled in imported.pulled] + [image.ref for image in imported.images if image.ref]
registries = sorted({host for ref in refs if (host := registry_host_from_ref(ref))})
if registries:
lines.append(f"- Access to the registries its pulled images are in: `{'`, `'.join(registries)}`. agent-env reads "
"a private one only through its configured image store, such as an ECR store for an ECR registry.")
lines.append(f"- Access to the registries its pulled images are in: `{'`, `'.join(registries)}`. An image pinned "
"to a digest needs only this machine's Docker logged in to its registry (`docker login`); agent-env "
"reads any other private one only through its configured image store, such as an ECR store for an "
"ECR registry.")
return lines


Expand Down
29 changes: 29 additions & 0 deletions tst/unit/artifact/test_docker_image_put_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,35 @@ def unresolvable(ref, auth):
assert get_artifact_store().next_version("tool-image") == 1


@pytest.mark.parametrize("ref, credentials, kept", [
(f"registry.example/team/app:v1@{DIGEST}", None, True),
("registry.example/team/app:v1", None, False),
(f"registry.example/team/app:v1@{DIGEST}", RegistryAuth("registry.example", "user", "pw"), False),
], ids=["digest", "tag", "the store's own registry"])
def test_a_digest_ref_its_registry_refuses_a_store_without_its_credentials_is_kept_as_given(
local_stores, monkeypatch, ref, credentials, kept):
"""For a sandbox whose Docker is logged in to that registry to pull; a tag, or the store's own refusal, still fails."""
class _Registry(ImageStore):
def image_ref(self, repository, tag):
return f"registry.example/{repository}:{tag}"

def auth(self, ref):
return credentials

def refuse(ref, auth):
raise docker_image.RegistryRefusedError(f"{ref}: registry.example refused to serve it (HTTP 401)")

local_stores.set_image_store(_Registry())
monkeypatch.setattr(docker_image, "pin_digest", refuse)

if kept:
assert DockerImageArtifact.put_ref("app-image", description="d", image_name=ref).image_name == ref
else:
with pytest.raises(ValueError, match="refused to serve it"):
DockerImageArtifact.put_ref("app-image", description="d", image_name=ref)
assert get_artifact_store().next_version("app-image") == 1


def test_a_ref_given_its_build_context_runs_under_its_tag_pulled_by_the_digest_given(local_stores, resolved, tmp_path):
"""So a sandbox that can't pull it builds it from the context, under the name its containers start from."""
(tmp_path / "Dockerfile").write_text("FROM scratch\n")
Expand Down
5 changes: 3 additions & 2 deletions tst/unit/bundle/importer_entities_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ def test_an_image_its_registry_names_is_written_as_its_ref_and_reported_as_pulle
assert _toml(imported.root, "envs/crm/env.toml")["image"] == {"ref": "registry.example.com/crm:v1"}
assert [(pull.folder, pull.image) for pull in imported.pulled] == [("envs/crm", "registry.example.com/crm:v1")]
assert imported.pinned == () and imported.images == ()
assert ("- Access to the registries its pulled images are in: `registry.example.com`. agent-env reads a private one "
"only through its configured image store, such as an ECR store for an ECR registry.") in (
assert ("- Access to the registries its pulled images are in: `registry.example.com`. An image pinned to a digest "
"needs only this machine's Docker logged in to its registry (`docker login`); agent-env reads any other "
"private one only through its configured image store, such as an ECR store for an ECR registry.") in (
imported.root / "README.md").read_text().splitlines()


Expand Down
Loading