Skip to content

feat(template): versioned OCI template registry — push/add/update/diff/verify (spec 19) - #108

Merged
gustavobertoi merged 2 commits into
mainfrom
feat/template-registry
Jul 1, 2026
Merged

feat(template): versioned OCI template registry — push/add/update/diff/verify (spec 19)#108
gustavobertoi merged 2 commits into
mainfrom
feat/template-registry

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

What

Implements spec 19 — a versioned OCI template registry — turning the embedded template pile into digest-pinned, shareable team infrastructure.

New: internal/registry

Pure-Go (oras.land/oras-go/v2, CGO-free) OCI seam behind a small TargetResolver interface so the CLI never touches oras directly and tests round-trip through an in-memory oras store (no network):

  • reference.go — parse oci://host/repo:tag / @sha256:… (+ ParseRepository for the bare lockfile source:), reject floating :latest.
  • bundle.godeterministic tar packaging of a template dir (sorted paths, zeroed mtime/uid/gid, fixed modes) so the artifact digest is a pure function of content; atomic, traversal-safe, size-capped unpack.
  • registry.go / pull.go / target.goPush / ResolveDigest / Pull, custom artifactType + layer media type, docker/ORAS credential store + GITHUB_TOKEN for ghcr.io (no devstack token store), auth-error remediation. Mandatory digest verification on pull (a re-pushed/tampered manifest is refused; nothing is unpacked).
  • verify.go — cosign Verifier/Signer seam (keyless + keyed VerifyPolicy), mock-able; a zero policy = digest-pinned-but-unsigned notice.

Graduated template verbs (were stubs)

push, add (tag→digest pin + optional signature verify + digest-keyed cache + lockfile entry, all under lock.WithLock), update (re-resolve, --dry-run, --to), diff (render-diff pinned vs remote tag), verify (re-pull + re-check digest/signature), ls.

Wiring

  • internal/store gains a digest-pinned templates: lockfile block + a digest-keyed cache under XDG_CACHE_HOME/devstack/templates/<algo>/<hex> (never keyed by tag → no cache poisoning).
  • Remote templates are chained into the generate/lint/test source: embedded < store < remote, digest-pinned and offline-first. With no remote templates registered the source chain is byte-identical to before, so generation stays deterministic.

Why

Multiple repos can now render byte-identical compose/Dockerfiles from a committed, digest-pinned lockfile; a moving tag never reaches the hot path.

How tested

  • internal/registry: push+pull round-trip through an oras memory store; PackBundle determinism (metadata-independent); digest-mismatch refusal; reference-parse table; verify good vs tampered (fake verifier); unavailable-cosign abort.
  • internal/cli: full add/update/diff/verify/ls flow through a memory-backed client seam — digest pinned into the store lockfile, bundle cached under the digest key and resolvable by name; drift detected + --dry-run writes nothing + real update moves the pin; newer-schemaVersion bundle rejected with an upgrade message; floating-tag refusal; signature policy accept/reject.
  • Gates green: CGO_ENABLED=0 go build ./..., CGO_ENABLED=1 go test ./internal/..., gofmt -l clean, go vet ./..., make determinism.

Scoped out (flagged in code)

  • git+https/ssh template scheme (spec lists it; this milestone is OCI-first).
  • workspace.yaml templates:/verify.cosign lockfile + generate wiring — this slice keeps the registry lockfile in the store config (~/.devstack/config.yaml); the RemoteTemplate shape is identical to the spec's workspace block so migration is a move, not a redesign.
  • Pure-Go sigstore-go verification — the real Verifier/Signer shell cosign, consistent with internal/selfupdate's documented CGO-free decision; the seam makes a sigstore-go swap drop-in.
  • Cache GC/TTL integration and multi-template sub-path bundles (one template per tag).

🤖 Generated with Claude Code

gustavobertoi and others added 2 commits July 1, 2026 13:48
…mote Docker host (spec 21)

Generalize the shared stack to run on a remote Docker host (an SSH/TCP
`docker context` or a DOCKER_HOST endpoint) instead of only the local
daemon. Ref-counting, provisioning, and DNS/alias are backend-agnostic;
this swaps WHERE containers run. Default is local, byte-for-byte unchanged.

- internal/docker: a Backend seam (ComposeEnv/Reachability/NewClient/
  RemoteReachable). Local = zero value (unchanged). Remote binds moby to the
  resolved endpoint and keys the ledger by the context name/host so remote
  rows never bleed into local counts, like the WSL2 Desktop-vs-dockerd split.
- internal/docker: Compose gains ContextEnv, appended after the secret Env,
  pinning every compose verb to the endpoint (DOCKER_HOST/DOCKER_CONTEXT).
- internal/config: a workspace `backend:` selector (+ store global default);
  validated (dockerhost scheme; context XOR host).
- orchestrate/up + cli/up: thread the backend into the client, ledger key,
  and every compose driver. Remote host-side Postgres provisioning is guarded
  with a clear error (scoped follow-up — needs an SSH tunnel).
- doctor: a backend.remote reachability probe with remediation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f/verify (spec 19)

Add internal/registry: a pure-Go (oras-go/v2, CGO-free) OCI template-registry
seam behind a TargetResolver interface (real remote.Repository w/ docker creds;
tests round-trip through an in-memory oras store, no network). Deterministic
bundle tar packaging (content-addressed digest), mandatory digest verification
on pull, and a cosign Verifier/Signer seam (shells cosign, matching
internal/selfupdate's CGO-free precedent; mock-able for tests).

Graduate the template.go stubs to real commands: push, add (tag→digest pin +
verify + digest-keyed cache + lockfile entry, lock-guarded), update (re-resolve
w/ --dry-run), diff (render-diff pinned vs remote), verify (re-pull + signature),
and ls. Store config gains a digest-pinned `templates:` lockfile; remote
templates are chained into the generate/lint/test source (embedded < store <
remote), digest-pinned and offline-first so generation stays byte-deterministic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gustavobertoi
gustavobertoi force-pushed the feat/template-registry branch from fcd14aa to ddaa465 Compare July 1, 2026 16:51
@gustavobertoi
gustavobertoi merged commit 2d9cec1 into main Jul 1, 2026
4 checks passed
@gustavobertoi
gustavobertoi deleted the feat/template-registry branch July 1, 2026 17:04
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