Skip to content

Support for Immutable Releases #4821

@brandtkeller

Description

@brandtkeller

Describe what should be investigated or refactored

GitHub's Immutable Releases feature is incompatible with Zarf's current release pipeline. The pipeline splits release creation (release-please) from asset upload (GoReleaser running later in release.yml) and relies on appending assets to an already-published release. Under immutable releases, once a release is published, assets cannot be added, modified, or deleted, and the associated tag is locked to its commit and cannot be reused, so the existing mode: append flow will fail the moment the repository (or zarf-dev org) opts in.

We should investigate restructuring the release pipeline so that:

  1. A single atomic publish step owns release creation. All artifacts (CLI binaries, init packages, SBOMs, checksums, agent image references) must be attached before the release transitions from draft → published.
  2. End-to-end validation runs before any tag is created. The current validate-release job runs after release-please has already created the tag and GitHub Release. With immutable tags, a validation failure orphans the tag forever (cannot be deleted-and-reused), forcing every retry to consume a new version number.
  3. mode: append in .goreleaser.yaml is reworked. GoReleaser currently appends the pre-built zarf-init-* artifacts to a release it does not own. Either GoReleaser should own release creation end-to-end (with init-packages produced inside its pipeline or staged as extra_files at create-time), or we drop GoReleaser's release responsibilities and use an alternative atomic-publish mechanism.
  4. release-please's role is scoped down. Options to evaluate:
    • Configure release-please with draft: true so it creates a draft release; have release.yml attach assets and flip draft → published as the final step.
    • Have release-please only manage the changelog/version-bump PR and tag, with release.yml owning GitHub Release creation entirely.
  5. Side-effects that occur before publish are reviewed for idempotency. The pipeline pushes the agent OCI image, signs it, and publishes init-packages to ghcr.io/zarf-dev/packages before the GoReleaser step. Under the new flow, these still need to either complete successfully before the immutable publish or be safely re-runnable if the publish step fails.

Decisions to make:

  • Which tool owns "publish" — release-please (with assets appended to a draft) or GoReleaser (release-please reduced to changelog/PR only)?
  • Where does e2e validation move so it gates tag creation, not just asset upload?

Links to any relevant code

Additional context

GitHub Immutable Releases constraints (per docs and the GA changelog):

  • Once published, a release's assets cannot be added, modified, or deleted.
  • The associated git tag is locked to a specific commit, cannot be moved, and cannot be deleted while the release exists.
  • Tag names cannot be reused even after deleting an immutable release.
  • Existing releases remain mutable unless republished; only new releases become immutable when the feature is enabled at the repo or org level.
  • GitHub's recommended pattern is create-as-draft → attach all assets → publish.

Today's Zarf flow violates each of the first three constraints:

Step Current behavior Conflict
release-please merges PR Creates published GitHub Release + pushes tag Release is immutable from the moment it's published; nothing else can attach assets
build-release job Builds binaries, agent image, init-packages Fine in isolation, but agent image push is not gated on validation
validate-release job E2E tests run after tag/release exist Failure leaves a permanent, locked tag that cannot be reused
create-release job GoReleaser --clean with mode: append Append-after-publish is rejected under immutable releases

Supply-chain upside worth capturing as part of this work: immutable releases automatically generate a release attestation (cryptographic record of tag, commit SHA, and assets).

Sources:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions