[PR #387] feat(image): manage image tags #396

Open
opened 2026-10-29 14:22:57 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/kvcache-ai/AgentENV/pull/387
Author: @Primary33
Created: 10/16/2026
Status: 🔄 Open

Base: main ← Head: feat/image-tags


📝 Commits (2)

  • 2c295eb feat: build and publish reusable images with the CLI
  • fe45aef feat(image): manage image tags

📊 Changes

38 files changed (+3994 additions, -275 deletions)

View changed files

📝 crates/aenv/src/client/templates.rs (+10 -0)
📝 crates/aenv/src/commands/build.rs (+116 -32)
📝 crates/aenv/src/commands/pull.rs (+1 -0)
📝 crates/aenv/src/main.rs (+1 -1)
📝 docs/src/concepts/sandboxes/starting.md (+61 -1)
📝 scripts/buildkit/test.py (+40 -0)
📝 services/gateway/internal/metrics.go (+5 -1)
📝 services/gateway/internal/metrics_test.go (+13 -0)
➕ src/api/generated/src/apis/images.rs (+99 -0)
📝 src/api/generated/src/apis/mod.rs (+1 -0)
📝 src/api/generated/src/models.rs (+746 -53)
📝 src/api/generated/src/server/mod.rs (+459 -0)
📝 src/api/impls/image_build.rs (+223 -85)
📝 src/api/impls/image_build/cleanup.rs (+9 -3)
➕ src/api/impls/image_build/record.rs (+206 -0)
📝 src/api/impls/image_build/tests.rs (+388 -5)
📝 src/api/impls/image_build/worker.rs (+5 -5)
➕ src/api/impls/images.rs (+271 -0)
📝 src/api/impls/mod.rs (+1 -0)
📝 src/api/impls/template.rs (+85 -13)

...and 18 more files

📄 Description

What

Manage image tags after a build: POST /images/tags points more tags of an image name at the image an existing tag of that name points to, DELETE /images/tags removes tags of a name, and GET /images/{name}/tags lists a name's tags with their digests and assignment times.

Why

#347 assigns tags only when an image is built, so promoting an image (for example pointing web:prod at the image of web:v1) or removing a tag requires a rebuild. Split out of #347 during review so the build flow and the tag API can be reviewed separately.

N/A. Depends on #347. Review this feature only. Design proposal: #378. The image catalog (#359) will be rebased onto this change.

Scope and non-goals

Tag assignment, listing, and deletion. The tag storage format, aenv/name:tag resolution, and build-time tagging are in #347. Listing image names and inspecting or deleting images by digest are in #359. Protected tags are out of scope.

Design and behavior changes

  • A tag is created only by a build of its name (#347) or by copying an existing tag of the same name, so a tag always points to an image built under its name. POST /images/tags {"target": "web:v1", "tags": ["prod"]} returns 201 {tags, imageDigest}, or 404 when the target tag does not point to a published image.
  • DELETE /images/tags {"name": "web", "tags": ["prod"]} is idempotent and never deletes the image.
  • GET /images/{name}/tags returns [{tag, imageDigest, createdAt}] in tag order. It reads images/tags/{name}/ through a new DocumentCollection::ImageTags(name); the collection now carries data, so it is no longer Copy, and the OSS and POSIX listing code takes it by reference.
  • Tag writes are last-writer-wins. A tag resolves to a digest once per launch, so moving it never changes existing sandboxes or snapshots.
  • Gateway: no routing changes. The paths have no sandbox binding and are scheduled to any node like /volumes; only the metric labels /images/tags and /images/{name}/tags are added.

Compatibility and operations

  • Public API or generated protocol: additive POST and DELETE /images/tags and GET /images/{name}/tags (OpenAPI tag images), with the regenerated server.
  • Configuration or defaults: none.
  • Snapshot manifest, artifact layout, or storage format: none beyond #347; this reads and deletes the tag documents #347 writes.
  • Upgrade and rollback: upgrade the server for the new endpoints; older servers return 404 for them. The gateway change only adds metric labels.
  • Host requirements, permissions, ports, or dependencies: N/A.

Validation

Based on #347 at 2c295eb (on b086553). The checks below ran on 06ec1d5, whose tree is identical to fe45aef. Run on a Linux test host as a non-root user with isolated AENV_TEST_STATE_DIR and AENV_TEST_DEPS_PATH:

  • make fmt
  • make clippy
  • make test-unit
  • Relevant Rust integration tests
  • make -C services test (required when services/ changes)
  • Generated clients/server regenerated with the documented make target
  • Documentation updated
  • Benchmarks or performance comparison completed

Commands and results:

make fmt                        passed
make clippy                     passed
make test-unit PROFILE=debug    passed: agentenv 1003 passed, 4 ignored; aenv 115 passed;
                                capability, envd, and ublk suites passed. Includes
                                images::tests::tags_are_assigned_listed_and_deleted_within_an_image_name
                                and proxy::tests::image_tag_endpoints_require_api_auth_and_validate_input
make -C services test           passed
make -C services fmt-check vet  passed
openapi-generator 7.22.0        regenerating src/api/generated (rust-axum, as cargo adev codegen
                                server does) reproduces the committed code, apart from one rustfmt
                                line break in the Node model that regenerating main produces too
scripts/buildkit/test.py        10 passed against a live server with a POSIX repository, and
                                10 passed against one with an S3-compatible (OSS) repository
cross-node check (OSS)          node A builds and assigns tags; node B, with its own home and cache
                                and the same bucket, lists latest, prod, and v1 with their digests,
                                reports the build ready, and cold starts by digest and by aenv/name:v1
cargo test -p agentenv --test integration --test orchestrator_integration
                                23 passed, 1 ignored
cargo test -p agentenv-e2e-tests --test snapshot_oss_e2e_test -- --ignored
                                9 passed

Skipped checks and reasons: the integration test config sets [pool.block] enabled = true, because with a config file the ublk daemon reads pool settings only from that file and a partial file disables the pool; this is already the case on main. The cross-node check ran as two isolated servers on one host. Benchmarks: not applicable.

Risks and reviewer notes

Review src/api/impls/images.rs (assignment, listing, and deletion semantics) and the per-name collection in src/repository/layout.rs and src/image/catalog.rs. Everything under src/api/generated is generated from the src/api/openapi.yml changes.

Checklist

  • The PR contains one coherent change and no unrelated formatting or refactoring.
  • New behavior is covered by tests, or I explained why testing is impractical.
  • Logs and examples contain no credentials, tokens, or private registry information.
  • I did not manually edit generated code without updating its source and regenerating it.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/kvcache-ai/AgentENV/pull/387 **Author:** [@Primary33](https://github.com/Primary33) **Created:** 10/16/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/image-tags` --- ### 📝 Commits (2) - [`2c295eb`](https://github.com/kvcache-ai/AgentENV/commit/2c295eba295f12399632eb713bdd10912936c269) feat: build and publish reusable images with the CLI - [`fe45aef`](https://github.com/kvcache-ai/AgentENV/commit/fe45aefdf04677e0e92863f0e3e285599fcf1916) feat(image): manage image tags ### 📊 Changes **38 files changed** (+3994 additions, -275 deletions) <details> <summary>View changed files</summary> 📝 `crates/aenv/src/client/templates.rs` (+10 -0) 📝 `crates/aenv/src/commands/build.rs` (+116 -32) 📝 `crates/aenv/src/commands/pull.rs` (+1 -0) 📝 `crates/aenv/src/main.rs` (+1 -1) 📝 `docs/src/concepts/sandboxes/starting.md` (+61 -1) 📝 `scripts/buildkit/test.py` (+40 -0) 📝 `services/gateway/internal/metrics.go` (+5 -1) 📝 `services/gateway/internal/metrics_test.go` (+13 -0) ➕ `src/api/generated/src/apis/images.rs` (+99 -0) 📝 `src/api/generated/src/apis/mod.rs` (+1 -0) 📝 `src/api/generated/src/models.rs` (+746 -53) 📝 `src/api/generated/src/server/mod.rs` (+459 -0) 📝 `src/api/impls/image_build.rs` (+223 -85) 📝 `src/api/impls/image_build/cleanup.rs` (+9 -3) ➕ `src/api/impls/image_build/record.rs` (+206 -0) 📝 `src/api/impls/image_build/tests.rs` (+388 -5) 📝 `src/api/impls/image_build/worker.rs` (+5 -5) ➕ `src/api/impls/images.rs` (+271 -0) 📝 `src/api/impls/mod.rs` (+1 -0) 📝 `src/api/impls/template.rs` (+85 -13) _...and 18 more files_ </details> ### 📄 Description ## What Manage image tags after a build: `POST /images/tags` points more tags of an image name at the image an existing tag of that name points to, `DELETE /images/tags` removes tags of a name, and `GET /images/{name}/tags` lists a name's tags with their digests and assignment times. ## Why #347 assigns tags only when an image is built, so promoting an image (for example pointing `web:prod` at the image of `web:v1`) or removing a tag requires a rebuild. Split out of #347 during review so the build flow and the tag API can be reviewed separately. ## Related issue N/A. Depends on #347. [Review this feature only](https://github.com/Primary33/AgentENV/compare/2c295eba295f12399632eb713bdd10912936c269...fe45aefdf04677e0e92863f0e3e285599fcf1916). Design proposal: #378. The image catalog (#359) will be rebased onto this change. ## Scope and non-goals Tag assignment, listing, and deletion. The tag storage format, `aenv/name:tag` resolution, and build-time tagging are in #347. Listing image names and inspecting or deleting images by digest are in #359. Protected tags are out of scope. ## Design and behavior changes - A tag is created only by a build of its name (#347) or by copying an existing tag of the same name, so a tag always points to an image built under its name. `POST /images/tags {"target": "web:v1", "tags": ["prod"]}` returns `201 {tags, imageDigest}`, or `404` when the target tag does not point to a published image. - `DELETE /images/tags {"name": "web", "tags": ["prod"]}` is idempotent and never deletes the image. - `GET /images/{name}/tags` returns `[{tag, imageDigest, createdAt}]` in tag order. It reads `images/tags/{name}/` through a new `DocumentCollection::ImageTags(name)`; the collection now carries data, so it is no longer `Copy`, and the OSS and POSIX listing code takes it by reference. - Tag writes are last-writer-wins. A tag resolves to a digest once per launch, so moving it never changes existing sandboxes or snapshots. - Gateway: no routing changes. The paths have no sandbox binding and are scheduled to any node like `/volumes`; only the metric labels `/images/tags` and `/images/{name}/tags` are added. ## Compatibility and operations - Public API or generated protocol: additive `POST` and `DELETE /images/tags` and `GET /images/{name}/tags` (OpenAPI tag `images`), with the regenerated server. - Configuration or defaults: none. - Snapshot manifest, artifact layout, or storage format: none beyond #347; this reads and deletes the tag documents #347 writes. - Upgrade and rollback: upgrade the server for the new endpoints; older servers return 404 for them. The gateway change only adds metric labels. - Host requirements, permissions, ports, or dependencies: N/A. ## Validation Based on #347 at `2c295eb` (on `b086553`). The checks below ran on `06ec1d5`, whose tree is identical to `fe45aef`. Run on a Linux test host as a non-root user with isolated `AENV_TEST_STATE_DIR` and `AENV_TEST_DEPS_PATH`: - [x] `make fmt` - [x] `make clippy` - [x] `make test-unit` - [x] Relevant Rust integration tests - [x] `make -C services test` (required when `services/` changes) - [x] Generated clients/server regenerated with the documented `make` target - [x] Documentation updated - [ ] Benchmarks or performance comparison completed Commands and results: ```text make fmt passed make clippy passed make test-unit PROFILE=debug passed: agentenv 1003 passed, 4 ignored; aenv 115 passed; capability, envd, and ublk suites passed. Includes images::tests::tags_are_assigned_listed_and_deleted_within_an_image_name and proxy::tests::image_tag_endpoints_require_api_auth_and_validate_input make -C services test passed make -C services fmt-check vet passed openapi-generator 7.22.0 regenerating src/api/generated (rust-axum, as cargo adev codegen server does) reproduces the committed code, apart from one rustfmt line break in the Node model that regenerating main produces too scripts/buildkit/test.py 10 passed against a live server with a POSIX repository, and 10 passed against one with an S3-compatible (OSS) repository cross-node check (OSS) node A builds and assigns tags; node B, with its own home and cache and the same bucket, lists latest, prod, and v1 with their digests, reports the build ready, and cold starts by digest and by aenv/name:v1 cargo test -p agentenv --test integration --test orchestrator_integration 23 passed, 1 ignored cargo test -p agentenv-e2e-tests --test snapshot_oss_e2e_test -- --ignored 9 passed ``` Skipped checks and reasons: the integration test config sets `[pool.block] enabled = true`, because with a config file the ublk daemon reads pool settings only from that file and a partial file disables the pool; this is already the case on main. The cross-node check ran as two isolated servers on one host. Benchmarks: not applicable. ## Risks and reviewer notes Review `src/api/impls/images.rs` (assignment, listing, and deletion semantics) and the per-name collection in `src/repository/layout.rs` and `src/image/catalog.rs`. Everything under `src/api/generated` is generated from the `src/api/openapi.yml` changes. ## Checklist - [x] The PR contains one coherent change and no unrelated formatting or refactoring. - [x] New behavior is covered by tests, or I explained why testing is impractical. - [x] Logs and examples contain no credentials, tokens, or private registry information. - [x] I did not manually edit generated code without updating its source and regenerating it. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#396
No description provided.