Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions .github/RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,15 @@ Push to main
↓ (version bump needed)
[Create/Update Release PR]
↓ (human reviews & merges)
[Create GitHub Release + Tag]
[Create GitHub Release + Tag (draft)]
[Retag CI-built images to semver + latest, sign, attest]
[Retag CI-built images to semver + latest, sign, attest → publish the release]
```

> The next Release PR is opened/refreshed by a **second release-please pass** that runs
> *after* a release is published, so it never sees the still-draft release. See
> [What Happens When You Push](#what-happens-when-you-push).

### What Happens When You Push

**On every push to `main`:**
Expand All @@ -65,18 +69,26 @@ Push to main
- Unit tests: `task test` (with the coverage ratchet)
- E2E tests: the Ginkgo suite in a k3d cluster, plus the project image scan

2. **Release Analysis** (if tests pass):
- release-please analyzes commits since last release
- Determines version bump based on commit types
- Creates/updates Release PR if needed
2. **Release handling** (if tests pass) — release-please runs as **two separate passes**:
- **Cut pass** (`release-please` job): if the previous Release PR was merged, cut its
GitHub Release (created as a *draft* — see step 4). This pass uses
`skip-github-pull-request`, so it does **not** open the next PR.
- **PR pass** (`release-please-pr` job): analyze commits since the last release, determine
the version bump, and create/update the next Release PR (`skip-github-release`). It runs
**after** the release is published, so it sees the just-published release instead of the
draft. Without this split, the run that cut a draft would open a PR computed against the
whole history and propose a bogus "release everything" version.

3. **Release PR Contents**:
- Auto-generated CHANGELOG.md updates
- Updated `charts/gitops-reverser/Chart.yaml` versions
- Summary of all changes

4. **When Release PR is Merged**:
- GitHub Release created with tag (e.g., `v0.2.0`)
- GitHub Release created with tag (e.g., `v0.2.0`) — first as a **draft**, so every signed
asset (`install.yaml`, SBOM, `.sigstore.json` signatures, `.intoto.jsonl` attestations)
can be attached before it goes public; immutable releases reject post-publish uploads, so
`publish-release` flips the draft to published only after every asset is in place.
- The linux/amd64 + linux/arm64 image digests already built and scanned by that
commit's CI run are **retagged** (not rebuilt) as `0.2.0`, `0.2`, `0`, `latest` on
`ghcr.io`, then cosign-signed with SLSA provenance + SPDX SBOM attestations
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
# Cut releases here, but do NOT open the next release PR yet.
# release-please creates the release as a draft (immutable-releases
# requires every asset attached before publish — see publish-manifest/
# publish-helm), and a draft is invisible to release-please's own
# "latest release" lookup. Opening the next PR in this same invocation
# would compute it against the whole history and propose a bogus
# "release everything" version. The release-please-pr job opens/refreshes
# the PR *after* publish-release makes the release visible.
skip-github-pull-request: true

# Merge the per-arch release-grade digests — already built and pushed by the
# `ci` run of *this* workflow (see build-release-* in ci.yml) — into one
Expand Down Expand Up @@ -356,3 +365,37 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ needs.release-please.outputs.tag_name }}
run: gh release edit "${TAG_NAME}" --draft=false --repo "${{ github.repository }}"

# The PR half of release-please, run as a separate invocation so it sees an
# already-published release instead of the draft the release-please job just
# created. skip-github-release: true means it only opens/refreshes the next
# release PR — it never tags or releases.
#
# Gating: this must run AFTER publish-release when a release was cut (so the
# release is published and visible), but also on ordinary pushes that cut no
# release (so the pending release PR keeps tracking new commits). The `if`
# therefore runs whenever CI + release-please succeeded and EITHER no release
# was created (publish-release was skipped) OR publish-release succeeded — so a
# failed publish (release still a draft) will NOT open a PR against the draft.
release-please-pr:
name: Release Please (PR)
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [ci, release-please, publish-release]
if: >-
${{ !cancelled()
&& needs.ci.result == 'success'
&& needs.release-please.result == 'success'
&& (needs.publish-release.result == 'success'
|| needs.release-please.outputs.release_created != 'true') }}
permissions:
contents: write
pull-requests: write
steps:
- name: Open or refresh the next release PR
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: true
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ If you only want the supported product docs, start with the files below.

- [`../README.md`](../README.md): product overview and end-to-end quick start
- [`configuration.md`](configuration.md): core configuration objects and how they fit together
- [`installing-apps-as-krm.md`](installing-apps-as-krm.md): installing an app is adding a KRM
document — Flux `HelmRelease`, Argo CD `Application`, KRO, and core resources all mirror and edit alike
- [`commit-signing.md`](commit-signing.md): how valid Git signatures map to platform verification
- [`github-setup-guide.md`](github-setup-guide.md): GitHub repository and credential setup
- [`sops-age-guide.md`](sops-age-guide.md): Secret encryption with SOPS + age
Expand Down
12 changes: 11 additions & 1 deletion docs/ci-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ so local and CI results cannot drift apart.
| Release & publish | [release.yml](../.github/workflows/release.yml) tail jobs | after trusted validation is green | `GITHUB_TOKEN` + OIDC | packages, releases, attestations | Version (release-please), **retag** the CI-built multi-arch digests to semver + `latest` (zero rebuilds), publish chart, sign, attest |
| Hygiene | [scorecard.yml](../.github/workflows/scorecard.yml) | weekly + `main` | none | security-events | OpenSSF Scorecard supply-chain checks |

Two properties are worth calling out:
Three properties are worth calling out:

- **One copy of the validation pipeline.** `ci.yml` runs directly for PRs and is invoked
by `release.yml` as a [reusable workflow](https://docs.github.com/en/actions/using-workflows/reusing-workflows)
Expand All @@ -33,6 +33,16 @@ Two properties are worth calling out:
published from a commit that did not pass the full pipeline first. The release tail
builds nothing — the multi-arch image digests it publishes were built and scanned by the
`ci` run of the same commit; the release only retags, signs, and attests them.
- **release-please runs in two passes, on purpose.** A GitHub release is created as a
*draft* so every signed asset can be attached before it goes public (immutable releases
reject post-publish uploads — see [Release artifacts](#release-artifacts-and-how-to-verify-them)).
But a draft is invisible to release-please's own "latest release" lookup, so if the
invocation that cut the draft also opened the *next* release PR, that PR would be computed
against the whole history and propose a bogus "release everything" version. `release.yml`
therefore splits the action: the `release-please` job cuts the release with
`skip-github-pull-request`, and a separate `release-please-pr` job opens/refreshes the next
PR with `skip-github-release` **after** `publish-release` has made the release public and
visible. Same guarantee, correct changelog.

## How fork PRs work

Expand Down
6 changes: 5 additions & 1 deletion docs/design/gitops-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ base"), and the mirror-mode vs. intent-cluster topology — live in
| # | Feature | Design doc | Status |
|---|---------|-----------|--------|
| F1 | Kustomize `images:` / `replicas:` edit-through — a live change produced by an override entry is written back to that entry, never through into the source manifest | [f1-images-replicas-edit-through.md](f1-images-replicas-edit-through.md) | implemented ([#198](https://github.com/ConfigButler/gitops-reverser/pull/198)) |
| F7 | Higher-level KRM objects as first-class documents — corpus + e2e pinning that Flux `HelmRelease`/`Kustomization`, Argo CD `Application`, KRO resources, and core resources mirror and edit like any KRM document (they should already; F7 *proves* it), plus "install an app = add KRM" user docs | | not designed (expected small) — **launch-critical** |
| F7 | Higher-level KRM objects as first-class documents — corpus + e2e pinning that Flux `HelmRelease`/`Kustomization`, Argo CD `Application`, KRO resources, and core resources mirror and edit like any KRM document (they should already; F7 *proves* it), plus "install an app = add KRM" user docs | [f7-higher-level-krm-documents.md](f7-higher-level-krm-documents.md) | implemented (manifestedit corpus for HelmRelease/Application/KRO; HelmRelease mirror+edit e2e; [installing-apps-as-krm.md](../../installing-apps-as-krm.md) user docs) |
| F2 | Render-root scoping — a GitTarget declares its render root (e.g. `overlays/env1`); base files reached through `../../base` become read-only context, dissolving overlay fan-out ambiguity. Launch scope: overlay `images:`/`replicas:` entries + overlay-local documents, shipped **with** the per-edit `FullyReflected` accounting | — | not designed — **launch-critical** |
| F4 | New-file placement rules — sibling inference + `spec.placement` template so new resources land in the folder's convention, not the canonical REST path; includes creating the `resources:` entry when the target folder carries a kustomization | designed in [version2/gittarget-new-file-placement-rules.md](../manifest/version2/gittarget-new-file-placement-rules.md) | implemented (v1: declared policy + sibling inference steps 1/2/4 + kustomize `resources:` entry; step 3 and ordered-rule Option A deferred) |
| F5 | Branch/session ergonomics — base-branch selection, opt-in remote branch cleanup, a GitTarget-level quiescence condition | — | not designed — **launch-critical** |
Expand All @@ -143,6 +143,10 @@ base"), and the mirror-mode vs. intent-cluster topology — live in
surface with terminal `Pushed=True` + SHA.
- Refusals: unsupported kustomize features, duplicate identities, impure or
foreign content — refuse-first, never mis-edit.
- Higher-level KRM documents (Flux `HelmRelease`, Argo CD `Application`, KRO
resources) mirror and edit exactly like core resources — the pipeline is
kind-agnostic, now pinned by F7's corpus + HelmRelease e2e
([f7-higher-level-krm-documents.md](f7-higher-level-krm-documents.md)).

## Known boundary (what stays refused)

Expand Down
Loading