Skip to content

Add pushes API for exporting images to remote registries - #353

Draft
chruffins wants to merge 4 commits into
hypeship/registry-push-managerfrom
hypeship/registry-push-api
Draft

Add pushes API for exporting images to remote registries#353
chruffins wants to merge 4 commits into
hypeship/registry-push-managerfrom
hypeship/registry-push-api

Conversation

@chruffins

@chruffins chruffins commented Aug 5, 2026

Copy link
Copy Markdown

Layer 3 of remote registry push support (stacked on #348, #350).

What

The HTTP surface for outbound pushes, docker-aligned credentials included.

API (openapi.yaml → generated)

  • POST /pushes → 202 + Push job. Body: image (hypeman image, must be ready), target (full remote ref), optional insecure, optional credentials
  • GET /pushes (newest first), GET /pushes/{id}
  • Push object: queued → pushing → pushed/failed with queue position, error, layers/bytes, timestamps
  • errors: 400 invalid_name/invalid_target, 404 image not found, 409 image_not_ready, 500 otherwise

Credentials — borrow, don't store

  • request credentials (username/password/registry_token, mirroring docker config.json fields) map to an authn.AuthConfig and are borrowed for that single push only — never persisted (asserted in layer 2) or logged
  • omitted → falls back to the server's default resolution (Docker keychain)
  • scopes reuse image:write (POST) / image:read (GET) — no new scope plumbing, existing tokens work

Wiring

  • ProvidePushManager (resolver = image manager, concurrency from limits.max_concurrent_pushes, default 2)
  • ApiService.PushManager + wire regen
  • GC roots composition: compositeOCICacheRoots in main.go feeds the OCI cache GC both the registry's BuildKit cache tags and the push manager's in-flight digests (imagepush.Manager gains LiveCacheManifestDigests to satisfy ocicachegc.RootsProvider)

Tests

Handler-level with a fake push manager (hermetic, follows the images_test pattern): request + credentials mapping, nil-credentials fallback, full error-status mapping table, get-not-found, list empty/all with layers/bytes mapping.

Notes

  • lib/oapi/oapi.go regenerated via make oapi-generate (pinned oapi-codegen v2.5.1); the embedded-spec blob diff includes the pre-existing compression drift any regen under the current Go toolchain produces on main too
  • wire_gen.go regenerated with wire v0.6.0
  • SSE/events for pushes not included — clients poll GET /pushes/{id} (builds-style events can be a follow-up)
  • the 409 for not-ready images is deliberate: the image exists but can't be pushed yet

Verification

  • go build -tags containers_image_openpgp ./... — full tree builds
  • new handler tests + lib/scopes, lib/providers, cmd/api/config, and all push/image packages pass

Note

Medium Risk
Touches image export, transient registry credentials in requests, and OCI cache GC roots—incorrect GC or credential handling could break pushes or delete blobs mid-upload, but scope is bounded to new push paths and existing image scopes.

Overview
Adds HTTP endpoints to queue and track outbound image pushes from the local OCI cache to remote registries: POST /pushes (202), GET /pushes, and GET /pushes/{id}. Handlers delegate to imagepush.Manager, map domain errors to 400/404/409 responses, and translate push jobs to OpenAPI Push objects (optional layers/bytes omitted when zero).

Credentials are borrowed per request only: optional credentials (username/password/registry_token) become authn.AuthConfig; absent or empty objects stay nil so the server’s default Docker keychain resolution is unchanged.

Wiring and limits: PushManager is injected via Wire/ProvidePushManager with limits.max_concurrent_pushes (default 2). Auth scopes reuse existing image:write / image:read for push routes.

OCI cache GC: In-flight push manifest digests are merged with registry BuildKit roots through compositeOCICacheRoots; imagepush.Manager implements LiveCacheManifestDigests for ocicachegc.RootsProvider.

OpenAPI/oapi client and server code is regenerated; handler tests cover credential mapping, error mapping, and list/get behavior.

Reviewed by Cursor Bugbot for commit 7448f03. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread openapi.yaml
Comment thread cmd/api/api/pushes.go
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch 2 times, most recently from 912ae22 to f2ee13c Compare August 5, 2026 19:43
Comment thread openapi.yaml
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch 2 times, most recently from 8eefe9c to d90000d Compare August 5, 2026 20:03
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from 90d7da1 to 9302c19 Compare August 6, 2026 14:17
@chruffins

chruffins commented Aug 6, 2026

Copy link
Copy Markdown
Author

reviewed — solid, well-tested layer 3: clean handler wiring, thorough error mapping, good happy-path tests. the bugs, nits, and the interface/timing structural items are fixed in 9302c19e and fa579f02 (nil-body guard, status-gated counters, Manager interface trim with the GC adapter moved into main.go, GC roots re-queried after the mark, and the missing 500-path handler tests); the items below remain open by decision.

Structural / Maintainability (open)

  • cmd/api/api/pushes.go + lib/imagepush/storage.goListPushes returns every push ever created with no retention; deferred (matches repo-wide precedent, and pagination/retention is a cross-endpoint decision).

Questions (open)

  • lib/registrypush/provider.go — nil keychain falls back to authn.DefaultKeychain (the server's stored registry credentials); this is documented intent and kept as-is for single-operator self-host. revisit if hypeman is ever deployed multi-tenant.

Notes

  • queue + storage — third parallel queue+disk-recovery implementation in the repo; consolidation ticketed, not done in this stack.

status: all review findings addressed except the listed deferrals

@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from 9302c19 to fa579f0 Compare August 6, 2026 15:19
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from fa579f0 to b244f44 Compare August 6, 2026 16:13
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from b244f44 to dbd4c99 Compare August 6, 2026 17:09
Comment thread lib/ocicachegc/gc.go Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 22a1dfe. Configure here.

Comment thread lib/ocicachegc/gc.go Outdated
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
-->

✱ stlc build

go code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

114 files generated at 6c63540 (pushed)

go get github.com/kernel/hypeman-go-staging@6c63540406b89209f0ac59a00ac786cc89ac2210
typescript code · compare

Your SDK build was successful.

generate ✅bootstrap ✅format ✅

136 files generated at d8391e3 (pushed)

Diagnostics: 💡 0 new / 5 total note
LevelCodeMessageTargets
Build metadata
Buildbd_765VCL5F-tectonic-coast
Timestamp2026-08-06T17:49:44.781Z
stlc8413509
Spec hash88d816cbcc30
Config hashee536b54c599

This comment is auto-generated by stlc and is kept up to date as you push.
If you push new commits, re-run this workflow to update this comment.
Last updated: 2026-08-06 17:50:04 UTC

@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from 22a1dfe to 846fef3 Compare August 6, 2026 21:07
POST /pushes creates a push job exporting a ready hypeman image from
the OCI cache to a remote registry; GET /pushes and GET /pushes/{id}
expose job state. Requests may lend registry credentials, which the
push manager borrows for that job only and never persists; without
them the server's own credentials resolve via the Docker keychain.
Routes use the existing image:read/image:write scopes, the push queue
concurrency is configurable, and in-flight push digests are composed
into the OCI cache GC roots.
- Pin x-enum-varnames on PushStatus so generated constants carry the
  PushStatus prefix like the other enums, avoiding bare Failed/Queued
  names in the oapi package.
- Treat an empty credentials object the same as absent credentials so
  the server's default credential resolution stays in effect instead of
  pushing with an empty auth config.
Without a pushes resource, generated SDK clients omit the push API even
though the server exposes it. Mirrors the builds resource mapping.
@chruffins
chruffins force-pushed the hypeship/registry-push-api branch from 846fef3 to 7448f03 Compare August 6, 2026 21:10
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.

1 participant