From 69ccf13233e46908660912668bb6df0f1268781f Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Fri, 26 Jun 2026 15:10:38 +0200 Subject: [PATCH 1/4] feat: add /release skill and fix operator release docs Add an interactive release skill that guides through the full Jumpstarter release process: creating release branches, tagging releases (RC and final), and contributing operator bundles to community-operators. The skill enforces correct ordering, RC-first policy, and tag naming conventions. Also removes the incorrect e2e_test.go update step from the operator release docs since defaultControllerImage uses :latest. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/release/SKILL.md | 232 +++++++++++++++++++++++++++ .cursor/rules/releasing-operator.mdc | 10 +- 2 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 .claude/skills/release/SKILL.md diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 000000000..16774f12f --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,232 @@ +--- +name: release +description: Guide through the Jumpstarter release process (branch, tag, operator bundle) +argument-hint: "Optional: version (e.g. '0.9.0-rc.1') or phase (e.g. 'operator-bundle')" +--- + +# Jumpstarter Release + +You are guiding the user through the Jumpstarter project release process. + +Before proceeding, read `.claude/rules/releasing-operator.md` for operator-specific details. + +Release input: $ARGUMENTS + +## Conventions + +- **Git tags** use a `v` prefix: `v0.8.1`, `v0.9.0-rc.1` +- **Container image tags** do NOT use a `v` prefix: `:0.8.1`, `:0.9.0-rc.1` +- **RC tag format**: `vX.Y.Z-rc.N` (with dot before N). Reject old formats like `rc1` or `-rc1`. +- **RC-first rule**: When a release branch has no final release tags yet, the first tag MUST be an RC. Never tag a direct `vX.Y.0` final on a new branch without at least one RC first. +- Python packages are versioned automatically from git tags via `hatch-vcs` — no manual version files. +- The `bundle/` directory is NOT committed to the repo. +- `GITHUB_USER` env var controls the fork for community-operators (defaults to `mangelajo`). + +## Ordering constraint + +``` +types.go + Makefile update → commit → tag push → [CI builds images] → GitHub Release → make bundle → make contribute +``` + +Images must exist before bundle generation. The skill enforces this by splitting the process into phases. + +## Steps + +### 1. Gather context and determine release type + +Inspect the current git state: + +```bash +git branch --show-current +git branch -r | grep 'origin/release-' +git tag --sort=-version:refname | head -20 +gh release list --limit 5 +grep -E '^(VERSION|REPLACES) ' controller/deploy/operator/Makefile +``` + +Using the git state and `$ARGUMENTS` (if provided), ask the user: + +1. **What type of release is this?** + - **(A) Create a new release branch** — starting a new `X.Y` cycle from `main` + - **(B) Tag a release** — cut an RC or final from an existing release branch + - **(C) Operator bundle contribution** — generate and contribute the OLM bundle (after images are built) + +2. **What version?** Suggest the next logical version based on existing tags. Examples: + - Latest tag is `v0.8.1` → suggest `v0.9.0-rc.1` for a new branch, or `v0.8.2-rc.1` for a patch + - Latest RC is `v0.9.0-rc.2` → suggest `v0.9.0-rc.3` or `v0.9.0` (final) + +3. **Validate the version:** + - Format must be `vX.Y.Z` or `vX.Y.Z-rc.N` + - If this is a final release (`vX.Y.Z` without `-rc`), verify that at least one `vX.Y.Z-rc.*` tag exists. If not, warn the user and suggest creating an RC first. + - If creating a new release branch, the first tag must be an RC (e.g., `vX.Y.0-rc.1`) + +### 2A. Create a new release branch + +Only if the user selected type (A). + +```bash +git fetch origin +git checkout main +git pull origin main +git checkout -b release-X.Y +git push origin release-X.Y +``` + +After pushing, inform the user: +- The branch push triggers CI to build images tagged with the branch name (e.g., `:release-0.9`) +- The next step is to tag the first RC from this branch +- Offer to continue immediately with step 2B to tag `vX.Y.0-rc.1` + +### 2B. Tag a release + +Only if the user selected type (B), or continuing from step 2A. + +#### Phase 1: Pre-tag code changes + +Ensure you are on the correct `release-X.Y` branch: + +```bash +git fetch origin +git checkout release-X.Y +git pull origin release-X.Y +``` + +**Update version references:** + +1. **`controller/deploy/operator/api/v1alpha1/jumpstarter_types.go`** — update both kubebuilder default image tags (there are two: one in `RoutersConfig`, one in `ControllerConfig`): + ```go + // +kubebuilder:default="quay.io/jumpstarter-dev/jumpstarter-controller:X.Y.Z" + ``` + For RCs, use the full RC version (e.g., `:0.9.0-rc.1`). Note: no `v` prefix on image tags. + +2. **`controller/deploy/operator/Makefile`** — update: + - `VERSION ?= X.Y.Z` (or `X.Y.Z-rc.N` for RCs, no `v` prefix) + - `REPLACES ?= jumpstarter-operator.vPREVIOUS` — must point to the most recently published version in the OLM channel (including RCs). Check existing tags to determine the correct value. For the first release on a new branch, check what the last published version was across all branches. + +3. **Regenerate manifests:** + ```bash + cd controller/deploy/operator + make manifests generate + ``` + +4. **Commit and push** the changes to the release branch. Files to include: + - `controller/deploy/operator/Makefile` + - `controller/deploy/operator/api/v1alpha1/jumpstarter_types.go` + - `controller/deploy/operator/config/crd/bases/operator.jumpstarter.dev_jumpstarters.yaml` + - Any other files changed by `make manifests generate` + +#### Phase 2: Tag and GitHub Release + +5. **Create and push the git tag:** + ```bash + git tag vX.Y.Z # or vX.Y.Z-rc.N + git push origin vX.Y.Z + ``` + +6. **Create the GitHub Release:** + ```bash + # For a release candidate: + gh release create vX.Y.Z-rc.N \ + --title "vX.Y.Z-rc.N" \ + --prerelease \ + --generate-notes \ + --notes-start-tag vPREVIOUS_TAG + + # For a final release: + gh release create vX.Y.Z \ + --title "vX.Y.Z" \ + --generate-notes \ + --notes-start-tag vPREVIOUS_TAG + ``` + Use the previous tag as `--notes-start-tag` to scope the generated release notes. + +#### Phase 3: Wait for CI + +The tag push triggers: +- `build-images.yaml` — builds and pushes all container images +- `trigger-packages.yaml` — regenerates the Python package index + +The GitHub Release triggers: +- `release-operator-installer.yaml` — uploads `operator-installer.yaml` to the release + +Check CI status: +```bash +gh run list --workflow=build-images.yaml --limit 5 +``` + +Tell the user: **Wait for CI to complete before proceeding to the operator bundle step.** When images are available, continue with step 2C (or run `/release operator-bundle` later). + +### 2C. Operator bundle contribution + +Only if the user selected type (C), or continuing after step 2B. + +#### Verify images exist + +Before generating the bundle, confirm the container images for this version are available: + +```bash +gh run list --workflow=build-images.yaml --limit 5 +``` + +The user can also check `quay.io/jumpstarter-dev/jumpstarter-controller:X.Y.Z` directly. + +#### Generate the OLM bundle + +```bash +cd controller/deploy/operator +make bundle +``` + +#### Verify the bundle output + +```bash +# Image references should show :X.Y.Z (no :latest, no :vX.Y.Z) +grep -E "containerImage|image: quay" controller/deploy/operator/bundle/manifests/jumpstarter-operator.clusterserviceversion.yaml + +# CRD defaults should match +grep "default: quay" controller/deploy/operator/bundle/manifests/operator.jumpstarter.dev_jumpstarters.yaml + +# Release config +cat controller/deploy/operator/bundle/release-config.yaml +``` + +Show the output to the user and ask them to confirm it looks correct before continuing. + +#### Contribute to community-operators + +```bash +cd controller/deploy/operator + +# Set GITHUB_USER if different from default (mangelajo): +# export GITHUB_USER=yourusername + +make contribute +``` + +This will show a confirmation prompt. After the script completes, push to the fork and open PRs: + +```bash +cd controller/deploy/operator/contribute/community-operators +git push -f user jumpstarter-operator-release-X.Y.Z + +cd ../community-operators-prod +git push -f user jumpstarter-operator-release-X.Y.Z +``` + +Remind the user to open PRs on: +- `k8s-operatorhub/community-operators` +- `redhat-openshift-ecosystem/community-operators-prod` + +### 3. Post-release checklist + +Present a checklist of what was done (mark completed items) and what remains: + +- [ ] Release branch created (if new `X.Y` cycle) +- [ ] Version references updated (types.go, Makefile, CRDs) +- [ ] Git tag created and pushed +- [ ] GitHub Release created (with `--prerelease` for RCs) +- [ ] CI image build completed +- [ ] `operator-installer.yaml` asset uploaded (automated by CI) +- [ ] OLM bundle generated and verified (`make bundle`) +- [ ] Community-operators PRs opened (`make contribute`) +- [ ] Infrastructure-only Makefile fixes cherry-picked to `main` (if applicable) diff --git a/.cursor/rules/releasing-operator.mdc b/.cursor/rules/releasing-operator.mdc index cddbf5c5b..6931cfdff 100644 --- a/.cursor/rules/releasing-operator.mdc +++ b/.cursor/rules/releasing-operator.mdc @@ -50,13 +50,7 @@ Image string `json:"image,omitempty"` ``` These defaults flow into the CRD YAML via `make manifests` and into the OLM bundle via `make bundle`. -### 3. `controller/deploy/operator/test/e2e/e2e_test.go` -Update the default controller image constant: -```go -const defaultControllerImage = "quay.io/jumpstarter-dev/jumpstarter-controller:X.Y.Z" -``` - -### 4. `controller/deploy/operator/config/manager/kustomization.yaml` +### 3. `controller/deploy/operator/config/manager/kustomization.yaml` The `newTag` field is updated automatically by `make bundle` (via `kustomize edit set image`), but verify it reflects the correct tag after running the bundle target. ## Release Steps @@ -91,7 +85,7 @@ cat bundle/release-config.yaml ``` ### Step 4: Commit -Create a commit with the Makefile, types, CRD, kustomization, and e2e changes. The `bundle/` directory is NOT committed to the repo (it was removed in commit `12b680e3`). +Create a commit with the Makefile, types, CRD, and kustomization changes. The `bundle/` directory is NOT committed to the repo (it was removed in commit `12b680e3`). ### Step 5: Contribute to community-operators (when ready) ```bash From d4c60448a64cf2afd74b1ef94b6c1ba781868e91 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Fri, 26 Jun 2026 15:14:45 +0200 Subject: [PATCH 2/4] fix: fetch remote state before inspecting git context in release skill Co-Authored-By: Claude Opus 4.6 --- .claude/skills/release/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 16774f12f..9377dab9e 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -34,9 +34,10 @@ Images must exist before bundle generation. The skill enforces this by splitting ### 1. Gather context and determine release type -Inspect the current git state: +Fetch the latest remote state first, then inspect: ```bash +git fetch origin --tags git branch --show-current git branch -r | grep 'origin/release-' git tag --sort=-version:refname | head -20 From ca7595b1ffa2f9c28dd19c0399e845d1976177ae Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Fri, 26 Jun 2026 15:16:48 +0200 Subject: [PATCH 3/4] fix: monitor CI build status instead of asking user to wait The release skill now finds the CI run triggered by the tag push and monitors it periodically, then automatically proceeds to the operator bundle step when images are ready. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/release/SKILL.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 9377dab9e..668fb0e84 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -150,12 +150,20 @@ The tag push triggers: The GitHub Release triggers: - `release-operator-installer.yaml` — uploads `operator-installer.yaml` to the release -Check CI status: +Tell the user that CI is now building the container images and you will monitor progress. + +Find the CI run triggered by the tag and monitor it: ```bash -gh run list --workflow=build-images.yaml --limit 5 +# Find the run triggered by the tag push +gh run list --workflow=build-images.yaml --limit 3 --json databaseId,status,conclusion,headBranch,event,createdAt + +# Watch the run until it completes (use the databaseId from above) +gh run watch ``` -Tell the user: **Wait for CI to complete before proceeding to the operator bundle step.** When images are available, continue with step 2C (or run `/release operator-bundle` later). +Monitor the run periodically using `gh run view --json status,conclusion` until it completes. If it fails, show the user the failure details with `gh run view --log-failed` and stop. + +When the build-images workflow succeeds, inform the user and proceed automatically to step 2C. ### 2C. Operator bundle contribution From ab184fe86dc11ce45220552aa41210b4f050c04b Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Pelayo Date: Fri, 26 Jun 2026 17:20:50 +0200 Subject: [PATCH 4/4] feat: update release skill with lessons from v0.9.0-rc.1 release - Remove jumpstarter_types.go update step (operator resolves image tags at runtime now) - Add AUTO_CONFIRM=1 for non-interactive make contribute - Add gh pr create commands for community-operators PRs - Add branch protection ruleset reminder for new release branches - Add gh run rerun suggestion on CI failure - Document API fallback for creating protected release branches - Add cherry-pick infrastructure fixes reminder Co-Authored-By: Claude Opus 4.6 --- .claude/skills/release/SKILL.md | 93 +++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 668fb0e84..56a6d36ec 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -21,11 +21,12 @@ Release input: $ARGUMENTS - Python packages are versioned automatically from git tags via `hatch-vcs` — no manual version files. - The `bundle/` directory is NOT committed to the repo. - `GITHUB_USER` env var controls the fork for community-operators (defaults to `mangelajo`). +- Do NOT modify `controller/deploy/operator/api/v1alpha1/jumpstarter_types.go` — the operator resolves `:latest` image defaults to its own version at runtime. ## Ordering constraint ``` -types.go + Makefile update → commit → tag push → [CI builds images] → GitHub Release → make bundle → make contribute +Makefile update → commit → tag push → [CI builds images] → GitHub Release → make bundle → make contribute ``` Images must exist before bundle generation. The skill enforces this by splitting the process into phases. @@ -65,14 +66,21 @@ Using the git state and `$ARGUMENTS` (if provided), ask the user: Only if the user selected type (A). +**Important:** The `release-*` branch pattern may be protected by repository rulesets. If a direct push is rejected, try creating the branch via the GitHub API: + ```bash +# Try direct push first git fetch origin -git checkout main -git pull origin main -git checkout -b release-X.Y +git checkout -b release-X.Y origin/main git push origin release-X.Y + +# If rejected by branch protection, use the API: +SHA=$(git rev-parse origin/main) +gh api repos/{owner}/{repo}/git/refs -f ref=refs/heads/release-X.Y -f sha="$SHA" ``` +If both fail, the user needs to temporarily remove `release-*` from the branch protection ruleset, push the branch, then re-add it. + After pushing, inform the user: - The branch push triggers CI to build images tagged with the branch name (e.g., `:release-0.9`) - The next step is to tag the first RC from this branch @@ -94,37 +102,29 @@ git pull origin release-X.Y **Update version references:** -1. **`controller/deploy/operator/api/v1alpha1/jumpstarter_types.go`** — update both kubebuilder default image tags (there are two: one in `RoutersConfig`, one in `ControllerConfig`): - ```go - // +kubebuilder:default="quay.io/jumpstarter-dev/jumpstarter-controller:X.Y.Z" - ``` - For RCs, use the full RC version (e.g., `:0.9.0-rc.1`). Note: no `v` prefix on image tags. - -2. **`controller/deploy/operator/Makefile`** — update: +1. **`controller/deploy/operator/Makefile`** — update: - `VERSION ?= X.Y.Z` (or `X.Y.Z-rc.N` for RCs, no `v` prefix) - `REPLACES ?= jumpstarter-operator.vPREVIOUS` — must point to the most recently published version in the OLM channel (including RCs). Check existing tags to determine the correct value. For the first release on a new branch, check what the last published version was across all branches. -3. **Regenerate manifests:** +2. **Regenerate manifests:** ```bash cd controller/deploy/operator make manifests generate ``` -4. **Commit and push** the changes to the release branch. Files to include: +3. **Commit and push** the changes to the release branch. Files to include: - `controller/deploy/operator/Makefile` - - `controller/deploy/operator/api/v1alpha1/jumpstarter_types.go` - - `controller/deploy/operator/config/crd/bases/operator.jumpstarter.dev_jumpstarters.yaml` - - Any other files changed by `make manifests generate` + - Any files changed by `make manifests generate` #### Phase 2: Tag and GitHub Release -5. **Create and push the git tag:** +4. **Create and push the git tag:** ```bash git tag vX.Y.Z # or vX.Y.Z-rc.N git push origin vX.Y.Z ``` -6. **Create the GitHub Release:** +5. **Create the GitHub Release:** ```bash # For a release candidate: gh release create vX.Y.Z-rc.N \ @@ -161,7 +161,7 @@ gh run list --workflow=build-images.yaml --limit 3 --json databaseId,status,conc gh run watch ``` -Monitor the run periodically using `gh run view --json status,conclusion` until it completes. If it fails, show the user the failure details with `gh run view --log-failed` and stop. +Monitor the run periodically using `gh run view --json status,conclusion` until it completes. If it fails, offer to re-trigger with `gh run rerun `. If it fails again, show the user the failure details with `gh run view --log-failed` and stop. When the build-images workflow succeeds, inform the user and proceed automatically to step 2C. @@ -192,9 +192,6 @@ make bundle # Image references should show :X.Y.Z (no :latest, no :vX.Y.Z) grep -E "containerImage|image: quay" controller/deploy/operator/bundle/manifests/jumpstarter-operator.clusterserviceversion.yaml -# CRD defaults should match -grep "default: quay" controller/deploy/operator/bundle/manifests/operator.jumpstarter.dev_jumpstarters.yaml - # Release config cat controller/deploy/operator/bundle/release-config.yaml ``` @@ -209,33 +206,63 @@ cd controller/deploy/operator # Set GITHUB_USER if different from default (mangelajo): # export GITHUB_USER=yourusername -make contribute +# AUTO_CONFIRM=1 skips the interactive y/N prompt +AUTO_CONFIRM=1 make contribute ``` -This will show a confirmation prompt. After the script completes, push to the fork and open PRs: +After the script completes, push to the fork and create PRs using `gh`: ```bash +BRANCH="jumpstarter-operator-release-X.Y.Z" + cd controller/deploy/operator/contribute/community-operators -git push -f user jumpstarter-operator-release-X.Y.Z +git push -f user "$BRANCH" cd ../community-operators-prod -git push -f user jumpstarter-operator-release-X.Y.Z +git push -f user "$BRANCH" ``` -Remind the user to open PRs on: -- `k8s-operatorhub/community-operators` -- `redhat-openshift-ecosystem/community-operators-prod` +Then create PRs on both repos: + +```bash +# PR for community-operators +cd controller/deploy/operator/contribute/community-operators +gh pr create --repo k8s-operatorhub/community-operators \ + --title "operator jumpstarter-operator (X.Y.Z)" \ + --body "Release X.Y.Z of the jumpstarter-operator for the alpha channel." \ + --head GITHUB_USER:$BRANCH --base main + +# PR for community-operators-prod +cd ../community-operators-prod +gh pr create --repo redhat-openshift-ecosystem/community-operators-prod \ + --title "operator jumpstarter-operator (X.Y.Z)" \ + --body "Release X.Y.Z of the jumpstarter-operator for the alpha channel." \ + --head GITHUB_USER:$BRANCH --base main +``` + +Replace `GITHUB_USER` with the actual GitHub username (default: `mangelajo`). + +### 3. Post-release steps + +#### Add new release branch to protection ruleset + +If a new `release-X.Y` branch was created, remind the user to add it to the repository's branch protection ruleset so it requires merge queue for future changes. This is done in GitHub Settings → Rules → Rulesets → select the main/release ruleset → add `release-X.Y` to the branch targeting pattern. + +#### Cherry-pick infrastructure fixes + +If the release included infrastructure-only changes to the contribute script or Makefile (not version-specific), cherry-pick them to `main` in a separate PR. -### 3. Post-release checklist +#### Checklist Present a checklist of what was done (mark completed items) and what remains: - [ ] Release branch created (if new `X.Y` cycle) -- [ ] Version references updated (types.go, Makefile, CRDs) +- [ ] Release branch added to protection ruleset (if new branch) +- [ ] Operator Makefile VERSION and REPLACES updated - [ ] Git tag created and pushed - [ ] GitHub Release created (with `--prerelease` for RCs) - [ ] CI image build completed - [ ] `operator-installer.yaml` asset uploaded (automated by CI) - [ ] OLM bundle generated and verified (`make bundle`) -- [ ] Community-operators PRs opened (`make contribute`) -- [ ] Infrastructure-only Makefile fixes cherry-picked to `main` (if applicable) +- [ ] Community-operators PRs created (`make contribute` + `gh pr create`) +- [ ] Infrastructure fixes cherry-picked to `main` (if applicable)