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
71 changes: 40 additions & 31 deletions .claude/commands/release.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
---
allowed-tools: Read, Write, Edit, Bash(*), Agent
description: Release orchestrator — opens a chart-bump PR, opens a changelog PR, and rewrites the release PR description to reference both. Usage: /release PR_NUMBER
description: Release orchestrator — opens a single release-prep PR combining changelog and chart bumps, and rewrites the release PR description. Usage: /release PR_NUMBER
---

# Release Orchestrator

You orchestrate the release process for a given release PR. Three deliverables, in order:
You orchestrate the release process for a given release PR. Two deliverables, in order:

1. A bump PR for helm chart versions (`release/bump-charts-<PR_NUMBER>`).
2. A changelog PR with the release notes (`release/changelog-<PR_NUMBER>`), using the bumped versions.
3. The release PR description updated with the changelog and references to both PRs.
1. A single prep PR (`release/prepare-<PR_NUMBER>`) combining the changelog entry and helm chart version bumps.
2. The release PR description updated with the changelog and a reference to the prep PR.

You are the only mutator. The investigator subagents — `release-digest`, `release-bump-planner`, `release-changelog-writer` — are read-only by construction. They return text; you apply edits, run git, push branches, and dispatch `pull-request-creator` to open PRs. Never call `gh pr create` directly.

---

## Phase 1: Setup

Read `AGENTS.md`. Capture `<PR_NUMBER>` from the user's invocation. Then:
Read `AGENTS.md`. Capture `<PR_NUMBER>` from the user's invocation. If no number was provided, find the open PR targeting `main` whose head branch matches a release pattern:

```sh
gh pr list --state open --base main --json number,title,headRefName | \
jq '.[] | select(.headRefName | test("release|bump-app-version"; "i"))'
```

If exactly one candidate is found, use it and tell the user which PR was detected. If none or multiple, abort and ask the user to specify the PR number explicitly.

Then:

```sh
git fetch origin main
git status --porcelain
git rev-parse --abbrev-ref HEAD
Expand Down Expand Up @@ -59,6 +67,7 @@ Save its full output as `<bump_plan>`. From the plan extract:
- The `### Bundle dependency updates` block — likewise.
- The `### Bundle self-bumps` block — likewise.
- The single `### Bumped Versions Summary` line — the only piece you forward to Phase 5. Save it as `<bumped_summary>`.
- The new cortex library version (the `<new>` side of `cortex <old>→<new>`) — save as `<cortex_new_version>`.

---

Expand All @@ -70,14 +79,7 @@ Starting from `main` with a clean tree, apply the plan to the working tree:
- For each line in `### Bundle dependency updates`, use `Edit` on the named `helm/bundles/<name>/Chart.yaml` to change the `version:` field of the dependency entry at the given index. Anchor your `Edit` on the specific old version string plus the dependency's `name:` and any `alias:` line so the match is unique.
- For each line in `### Bundle self-bumps`, use `Edit` on the named bundle's Chart.yaml to change the top-level `version:`. Anchor on the chart's `name: <bundle_name>` plus the version line to disambiguate from dependency `version:` entries.

Dispatch **`pull-request-creator`** with:

- `branch`: `release/bump-charts-<PR_NUMBER>`
- `commit_message`: `Bump chart versions for release PR #<PR_NUMBER>`
- `motivation`: `Bump helm chart versions for release PR #<PR_NUMBER>. Bumped: <bumped_summary>. This PR must be merged before #<PR_NUMBER>.`
- `assign_reviewers`: `false` (release-mechanics PRs route to the release owner regardless of code area)

Capture `<bump_pr_number>` and `<bump_pr_url>` from its report. The agent leaves the working tree clean on `release/bump-charts-<PR_NUMBER>` — switch back yourself with `git checkout main` before the next phase.
Do NOT commit yet — leave the edits uncommitted in the working tree.

---

Expand All @@ -100,40 +102,48 @@ Produce the changelog entry.

Save its full output as `<changelog_entry>`.

---
Read the first 100 lines of `CHANGELOG.md` (or the full file if it does not exist) to check whether an entry for `#<PR_NUMBER>` is already present (use `grep -F "[#<PR_NUMBER>]"`):

## Phase 6: Apply the changelog
- **First run** (no existing entry): prepend `<changelog_entry>` (followed by a blank line) directly under the `# Changelog` header, before any existing entries.
- **Update run** (entry already present): replace the entire existing entry for `#<PR_NUMBER>` — from its `##` heading line down to (but not including) the next `##` heading or end of file — with `<changelog_entry>`. Do not prepend a second entry.

If `CHANGELOG.md` does not exist, write it with `# Changelog\n\n` followed by `<changelog_entry>`. Otherwise, read the file and prepend `<changelog_entry>` (followed by a blank line) directly under the `# Changelog` header, before any existing entries.
If `CHANGELOG.md` does not exist at all, create it with `# Changelog\n\n` followed by `<changelog_entry>`.

Do NOT commit yet — both `helm/` edits and `CHANGELOG.md` remain uncommitted in the working tree.

---

## Phase 6: Open the prep PR

Dispatch **`pull-request-creator`** with:

- `branch`: `release/changelog-<PR_NUMBER>`
- `commit_message`: `Add changelog entry for release PR #<PR_NUMBER>`
- `motivation`: `Add changelog entry for release PR #<PR_NUMBER>. Merge after #<PR_NUMBER>.`
- `branch`: `release/prepare-<PR_NUMBER>`
- `commit_message`: `Release cortex <cortex_new_version>`
- `motivation`: `Release prep for #<PR_NUMBER>: changelog entry and helm chart version bumps. Merge this before merging #<PR_NUMBER>.`
- `assign_reviewers`: `false`

Capture `<changelog_pr_number>` and `<changelog_pr_url>`. The agent leaves the working tree clean on `release/changelog-<PR_NUMBER>` — `git checkout main` yourself before Phase 7.
Capture `<prep_pr_number>` and `<prep_pr_url>` from its report. The agent leaves the working tree clean on `release/prepare-<PR_NUMBER>` — switch back yourself with `git checkout main` before Phase 7.

`pull-request-creator`'s idempotency handles the "update" case: if the branch already exists with only bot commits, it resets and force-pushes automatically.

---

## Phase 7: Update the release PR description

Build the new release PR description: `<changelog_entry>` followed by a Dependencies footer linking the bump PR and the changelog PR. Write it to a tempfile and pass `--body-file` to avoid shell quoting issues.
Build the new release PR description: `<changelog_entry>` followed by a Dependencies footer. Write it to a tempfile and pass `--body-file` to avoid shell quoting issues.

```
```sh
TMP=$(mktemp)
cat > "$TMP" <<'BODY'
## Changelog
## Release cortex <cortex_new_version>

<changelog_entry>

## Dependencies

- Bump PR: #<bump_pr_number> (must be merged before this PR)
- Changelog PR: #<changelog_pr_number> (merge after this PR)
- Prep PR: #<prep_pr_number> (must be merged before this PR)
BODY
gh pr edit <PR_NUMBER> --body-file "$TMP"
gh pr edit <PR_NUMBER> --title "Release cortex <cortex_new_version>" --body-file "$TMP"
rm "$TMP"
```

Expand All @@ -148,9 +158,8 @@ Print:
```
## Release #<PR_NUMBER> Post-Open Summary

- Bump PR: #<bump_pr_number> (<bump_pr_url>)
- Changelog PR: #<changelog_pr_number> (<changelog_pr_url>)
- Release PR #<PR_NUMBER>: description updated with changelog and PR references
- Prep PR: #<prep_pr_number> (<prep_pr_url>)
- Release PR #<PR_NUMBER>: description updated with changelog and prep PR reference
- Bumped: <bumped_summary>
```

Expand All @@ -161,5 +170,5 @@ If any phase aborted, list which phase and why, and skip the remaining phases
## Critical rules

- Phases 2 → 7 strictly in order. Each depends on the previous.
- Never read chart files or `CHANGELOG.md` for analysis — that is what the investigator agents do. You read those files only for the mechanical `Edit` and prepend in Phases 4 and 6.
- Never read chart files or `CHANGELOG.md` for analysis — that is what the investigator agents do. You read those files only for the mechanical `Edit` in Phase 4 and the mechanical prepend/replace in Phase 5 (reading the first 100 lines to detect an existing entry is explicitly permitted).
- All PR creation flows through `pull-request-creator`. Do not call `gh pr create` directly. The agent owns branch reset, commit, force-push, the human-commit guard, and clean-tree postcondition — you only stage the working-tree edits.
2 changes: 2 additions & 0 deletions .github/workflows/update-appversion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ jobs:
bump app version [skip ci]

${{ steps.summary.outputs.log }}

Signed-off-by: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
title: bump app version [skip ci]
body: |
bump app version [skip ci]
Expand Down
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
# Changelog

## 2026-07-16 — [#1051](https://github.com/cobaltcore-dev/cortex/pull/1051)

### cortex v0.3.1 (sha-fc47858e)

Non-breaking changes:
- Add configurable HTTP User-Agent for service identity in SSO/HTTP client layer ([#1045](https://github.com/cobaltcore-dev/cortex/pull/1045))
- Enhance multicluster client 'no cluster matched' error log messages with richer diagnostic context ([#1043](https://github.com/cobaltcore-dev/cortex/pull/1043))
- Account for CPU & memory as a binding constraint in slot counting ([#1044](https://github.com/cobaltcore-dev/cortex/pull/1044))
- Persist pipeline results before history upsert to prevent data loss on error ([#1047](https://github.com/cobaltcore-dev/cortex/pull/1047))
- Fix broken CI workflows with large GitHub runner configuration ([#1050](https://github.com/cobaltcore-dev/cortex/pull/1050))
- Add missing `SkipCommittedResourceTracking` to Options table and failover docs ([#1034](https://github.com/cobaltcore-dev/cortex/pull/1034))
- Update external dependencies ([#1040](https://github.com/cobaltcore-dev/cortex/pull/1040), [#1032](https://github.com/cobaltcore-dev/cortex/pull/1032))

### cortex-shim v0.1.7 (sha-7e36a65c)

Includes updated image sha-7e36a65c.

### cortex-postgres v0.6.9 (sha-e06153f8)

Non-breaking changes:
- Rebuild image to resolve CVEs ([#1033](https://github.com/cobaltcore-dev/cortex/pull/1033))

### cortex-nova v0.0.81

Includes updated charts cortex v0.3.1, cortex-postgres v0.6.9.

### cortex-cinder v0.0.81

Includes updated charts cortex v0.3.1, cortex-postgres v0.6.9.

### cortex-manila v0.0.81

Includes updated charts cortex v0.3.1, cortex-postgres v0.6.9.

### cortex-crds v0.0.81

Includes updated chart cortex v0.3.1.

### cortex-ironcore v0.0.81

Includes updated chart cortex v0.3.1.

### cortex-pods v0.0.81

Includes updated chart cortex v0.3.1.

### cortex-placement-shim v0.1.7

Includes updated chart cortex-shim v0.1.7.

## 2026-07-13 — [#1036](https://github.com/cobaltcore-dev/cortex/pull/1036)

### cortex v0.3.0 (sha-ee9cd485)
Expand Down
6 changes: 6 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ if len(env_set_overrides) > 0:
else:
print("=== No CORTEX_ environment variables found ===")

region = os.getenv('OS_REGION_NAME')
if region:
print("=== Deriving region-scoped URLs from OS_REGION_NAME=" + region + " ===")
env_set_overrides.append('openstack.url=https://identity-3.' + region + '.cloud.sap/v3')
env_set_overrides.append('prometheus.url=https://metrics-internal.scaleout.' + region + '.cloud.sap/')

load('ext://helm_resource', 'helm_resource', 'helm_repo')
helm_repo(
'Prometheus Community Helm Repo',
Expand Down
10 changes: 8 additions & 2 deletions api/v1alpha1/flavor_group_capacity_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,17 @@ type FlavorGroupCapacityStatus struct {
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="FlavorGroup",type="string",JSONPath=".spec.flavorGroup"
// +kubebuilder:printcolumn:name="Group",type="string",JSONPath=".spec.flavorGroup"
// +kubebuilder:printcolumn:name="AZ",type="string",JSONPath=".spec.availabilityZone"
// +kubebuilder:printcolumn:name="Running",type="integer",JSONPath=".status.runningInstances"
// +kubebuilder:printcolumn:name="LastReconcile",type="date",JSONPath=".status.lastReconcileAt"
// +kubebuilder:printcolumn:name="Avail",type="integer",JSONPath=".status.exclusivelyFreeSlots"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
// +kubebuilder:printcolumn:name="Reconciled",type="date",JSONPath=".status.lastReconcileAt"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",priority=1
// +kubebuilder:printcolumn:name="Free_Mem",type="string",JSONPath=".status.freeCapacity.memory",priority=1
// +kubebuilder:printcolumn:name="Excl_Mem",type="string",JSONPath=".status.exclusivelyFreeCapacity.memory",priority=1
// +kubebuilder:printcolumn:name="Free_CPU",type="string",JSONPath=".status.freeCapacity.cores",priority=1
// +kubebuilder:printcolumn:name="Excl_CPU",type="string",JSONPath=".status.exclusivelyFreeCapacity.cores",priority=1

// FlavorGroupCapacity caches pre-computed capacity data for one flavor group in one AZ.
// One CRD exists per (flavor group × AZ) pair, updated by the capacity controller on a fixed interval.
Expand Down
21 changes: 21 additions & 0 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import (
"github.com/cobaltcore-dev/cortex/pkg/conf"
"github.com/cobaltcore-dev/cortex/pkg/monitoring"
"github.com/cobaltcore-dev/cortex/pkg/multicluster"
"github.com/cobaltcore-dev/cortex/pkg/sso"
"github.com/cobaltcore-dev/cortex/pkg/task"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
"github.com/sapcc/go-bits/httpext"
Expand All @@ -90,13 +91,24 @@ func init() {
// +kubebuilder:scaffold:scheme
}

// UserAgentConfig identifies this cortex deployment to the services it talks
// to. Rendered by helm from the release name and chart version.
type UserAgentConfig struct {
// Component is the service name, e.g. "cortex-nova".
Component string `json:"component,omitempty"`
// Version is the deployed version, e.g. "sha-70af93a8".
Version string `json:"version,omitempty"`
}

type MainConfig struct {
// ID used to identify leader election participants.
LeaderElectionID string `json:"leaderElectionID,omitempty"`
// List of enabled controllers.
EnabledControllers []string `json:"enabledControllers"`
// List of enabled tasks.
EnabledTasks []string `json:"enabledTasks"`
// User-Agent sent with all outgoing HTTP requests.
UserAgent UserAgentConfig `json:"userAgent,omitempty"`
}

//nolint:gocyclo
Expand All @@ -105,6 +117,15 @@ func main() {
mainConfig := conf.GetConfigOrDie[MainConfig]()
restConfig := ctrl.GetConfigOrDie()

// Identify this cortex deployment to the services it talks to via the
// User-Agent header, before any HTTP requests are made. The shared
// http.DefaultTransport covers http.DefaultClient and any http.Client
// without its own transport; SSO clients build their own transport and
// are handled separately by pkg/sso.
httpext.WrapTransport(&http.DefaultTransport).
SetOverrideUserAgent(mainConfig.UserAgent.Component, mainConfig.UserAgent.Version)
sso.SetUserAgent(mainConfig.UserAgent.Component, mainConfig.UserAgent.Version)

// Custom entrypoint for scheduler e2e tests.
// Usage: /main <subcommand> [json-override]
// The optional json-override is merged on top of the ConfigMap config, e.g.:
Expand Down
25 changes: 25 additions & 0 deletions cmd/shim/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/cobaltcore-dev/cortex/pkg/conf"
"github.com/cobaltcore-dev/cortex/pkg/monitoring"
"github.com/cobaltcore-dev/cortex/pkg/multicluster"
"github.com/cobaltcore-dev/cortex/pkg/sso"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
"github.com/sapcc/go-bits/httpext"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -50,11 +51,35 @@ func init() {
utilruntime.Must(hv1.AddToScheme(scheme)) // Hypervisor crd
}

// UserAgentConfig identifies this cortex deployment to the services it talks
// to. Rendered by helm from the release name and chart version.
type UserAgentConfig struct {
// Component is the service name, e.g. "cortex-nova".
Component string `json:"component,omitempty"`
// Version is the deployed version, e.g. "sha-70af93a8".
Version string `json:"version,omitempty"`
}

type MainConfig struct {
// User-Agent sent with all outgoing HTTP requests.
UserAgent UserAgentConfig `json:"userAgent,omitempty"`
}

func main() {
ctx := ctrl.SetupSignalHandler()

mainConfig := conf.GetConfigOrDie[MainConfig]()
restConfig := ctrl.GetConfigOrDie()

// Identify this cortex deployment to the services it talks to via the
// User-Agent header, before any HTTP requests are made. The shared
// http.DefaultTransport covers http.DefaultClient and any http.Client
// without its own transport; SSO clients build their own transport and
// are handled separately by pkg/sso.
httpext.WrapTransport(&http.DefaultTransport).
SetOverrideUserAgent(mainConfig.UserAgent.Component, mainConfig.UserAgent.Version)
sso.SetUserAgent(mainConfig.UserAgent.Component, mainConfig.UserAgent.Version)

var metricsAddr string
var apiBindAddr string
var metricsCertPath, metricsCertName, metricsCertKey string
Expand Down
Loading
Loading