Skip to content

fix(ci): complete ext-workflow cutover (PR 2.5/6)#57

Merged
phramz merged 5 commits into
mainfrom
feat/local-ci-pr2.5-ext-workflow
Apr 23, 2026
Merged

fix(ci): complete ext-workflow cutover (PR 2.5/6)#57
phramz merged 5 commits into
mainfrom
feat/local-ci-pr2.5-ext-workflow

Conversation

@phramz

@phramz phramz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the four follow-up items explicitly deferred from PR 2 (#56). Ships the remaining plumbing so phpup build ext works against remote GHCR and .github/workflows/build-extension.yml uses the same rewire pattern Task 6 of PR 2 established for build-php-core.yml.

Changes

  1. registry.remoteStore.Push implemented (previously ErrUnsupported). Wraps go-containerregistry/pkg/v1/remote.Write with byte-identical media types and artifact-type annotations to what oras push emits today. Ref gains a Tag field used by remote push only (layout ignores it).
  2. loadExtBuildDeps refactor — drops the ad-hoc map[string]any parser in favor of the existing typed catalog.LoadExtensionSpec API.
  3. Zombie sidecar cleanupdefaultSidecarLifecycle.Start now sweeps stale containers + networks labeled buildrush.phpup.sidecar=1 before creating fresh ones. Gated integration test seeds a fake zombie and verifies it's swept.
  4. planner.MatrixCell.CoreDigest threading — populated during ext-matrix expansion from already-resolved php-core digests; emitted in matrix JSON as core_digest; plumbed through plan-and-build.yml as php_core_digest: ${{ matrix.core_digest }}.
  5. build-extension.yml rewired to call phpup build ext via the same oci-layout + cp staging shim pattern used by build-php-core.yml. Digest job-output contract preserved byte-for-byte; downstream Push to GHCR, Sign bundle, and Upload artifact steps unchanged.

Test plan

  • make check green end-to-end locally (full gate including docker-backed local-ci smoke).
  • go test ./internal/registry/... ./internal/build/... ./internal/planner/... — all pass with coverage unchanged or improved.
  • End-to-end sanity: ran cmd/planner against real ./catalog + ./bundles.lock → 172 ext cells with core_digest populated; 10 PHP cells without (as expected via omitempty).
  • Zombie-sidecar gated integration test: seeds a labeled fake zombie, calls Start, verifies the zombie is gone.
  • CI green on this PR.

Artifact parity

The remote Push's media types and artifact-type annotation were extracted verbatim from the existing oras push commands in build-php-core.yml:104-106 and build-extension.yml:85-87. Clients and signing tools should see byte-identical artifacts regardless of whether phpup or oras was the push origin.

Notes

  • Only new //nolint site in PR 2.5 is reusing the existing G204 suppression on execDocker for the new sweepStaleSidecars helper — already justified as a genuine false positive (execve without shell, argv from typed struct fields).
  • SeedCore in internal/build/sidecar.go still builds its image by hand via buildTwoLayerImage — now that remoteStore.Push works, it could delegate. Left as-is per Task 1's scope; clean follow-up for a future DRY pass.

phramz added 5 commits April 23, 2026 18:02
remote.Push was ErrUnsupported in PR 1+2; this completes the remote
backend so `phpup build ext --registry ghcr.io/<owner>` can publish
ext bundles directly (PR 2's deferred follow-up).

Design: Ref gains a Tag field used by remote push only. OCI remotes
are tag-addressed for writes (the registry computes the digest from
the manifest; callers cannot supply one), so remoteStore.Push needs
a tag and errors out with a clear message if Ref.Tag is empty.
layoutStore ignores Ref.Tag because its Push writes by index
annotation, not by tag.

Byte-identity with the existing `oras push` command in
build-php-core.yml / build-extension.yml is a hard requirement:
- layer 0: application/vnd.oci.image.layer.v1.tar+zstd (bundle)
- layer 1: application/vnd.buildrush.meta.v1+json (meta sidecar)
- manifest annotation org.opencontainers.artifact.type set to
  application/vnd.buildrush.php-core.v1 for php-core and
  application/vnd.buildrush.php-ext.v1 for php-ext-*

Keeping these strings byte-identical with the CI path means cosign
and downstream OCI clients see remoteStore-pushed bundles as
indistinguishable from oras-pushed ones.

Tests round-trip Push+ResolveDigest+Fetch through the in-process
pkg/registry and assert both the artifact-type annotation and the
layer media types match the CI contract.

Known follow-up: internal/build/sidecar.go's SeedCore still builds
its OCI image by hand via buildTwoLayerImage; it could now delegate
to remoteStore.Push. Left as-is to keep this task's scope tight.
Follow-up from PR 2 Task 5 code review. Drop the ad-hoc
map[string]any parser in favour of the existing typed catalog API
so the extension-schema shape lives in one place. Behavior is
byte-identical — .build_deps.linux is joined with spaces exactly
as before.
Flagged in PR 2 Task 5 code review as Important #1. If a sidecar
lifecycle aborts before its deferred stop (panic, outer timeout,
SIGKILL), the distribution:3 container and its docker network
linger and can block future runs.

Label all sidecar-created containers + networks with
buildrush.phpup.sidecar=1, and at the top of Start, sweep anything
matching that label. Opportunistic: errors from the sweep are
ignored — either the zombies didn't collide with the new run
(fine), or docker is broken enough that the subsequent Start will
report its own clear error.

Gated TestSidecar_SweepsZombiesOnStart_Real seeds a fake zombie
container + network, calls Start, and asserts the zombie is gone.
MatrixCell gains a CoreDigest string field populated only for
Kind=="ext". ExpandExtMatrix reads the already-resolved core digests
(by php_abi+os+arch key) and sets CoreDigest on each ext cell.

Emitted in the matrix JSON as `core_digest` (omitempty so php/tool
cells stay unchanged). plan-and-build.yml passes `matrix.core_digest`
as `php_core_digest` input to build-extension.yml; the workflow
declares the input but doesn't yet consume it (Task 5 does the
phpup build ext rewire).

No behavior change for the current CI pipeline — build-extension.yml
still uses oras push unchanged. This commit is purely preparatory
wiring for Task 5.
Mirrors PR 2 Task 6's pattern for build-php-core.yml. Builder script
stays unchanged; phpup build ext docker-wraps it and writes into
./build/ext/<slug>/. A "Stage bundle for publish" step cp's the
emitted bundle.tar.zst + .sha256 + meta.json to /tmp/ so the
existing Push to GHCR, Sign bundle, and Upload artifact steps read
from the same paths -- preserving the digest job-output contract
byte-for-byte.

inputs.php_core_digest now required (previously declared as optional
stub by PR 2.5 Task 4). plan-and-build.yml already passes
matrix.core_digest from the planner, so the CI wiring closes.

Completes the deferred ext-workflow work from PR 2.
@phramz
phramz merged commit 7f83cce into main Apr 23, 2026
340 of 344 checks passed
@phramz
phramz deleted the feat/local-ci-pr2.5-ext-workflow branch April 23, 2026 16:50
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