From 20aaee999e6bc1c2d4cdf3e9255d5c601d060544 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 4 Aug 2026 12:12:56 -0500 Subject: [PATCH 1/2] docs: add per-version upgrade notes under docs/release-notes GitHub release bodies are auto-generated PR lists: they say what changed but not what an operator has to *do* about it. This adds a place to record required deploy repo changes, new secrets, and one-time manual steps. One page per minor series, versions newest-first. A version needing no operator action gets no section, so most versions are absent; empty pages would only teach operators not to open them. The Unreleased section is authored in the same PR as each change, so it is immediately visible to deployments tracking main (understack_ref defaults to HEAD). No history is reconstructed: per-version notes start at the next tag, and the v0.4.25-and-earlier stub links only the upgrade guides that exist. MD024 becomes siblings_only so repeated per-section headings (e.g. "Action required") lint cleanly. --- .markdownlint.yml | 8 ++++-- docs/operator-guide/index.md | 7 +++++ docs/release-notes/index.md | 54 ++++++++++++++++++++++++++++++++++++ docs/release-notes/v0.4.md | 48 ++++++++++++++++++++++++++++++++ properdocs.yml | 7 +++++ 5 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 docs/release-notes/index.md create mode 100644 docs/release-notes/v0.4.md diff --git a/.markdownlint.yml b/.markdownlint.yml index 5a3d241e2..6ea1054ab 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -23,9 +23,11 @@ MD013: false MD014: false # MD024 - Multiple headings with the same content -# Multiple identical headings in the document are not allowed -# This is disabled on all of the release notes pages in docs/release-notes -MD024: true +# Identical headings are only an error when they are siblings under the same +# parent. The release notes pages in docs/release-notes repeat headings such as +# "Action required" once per version section, which is intended. +MD024: + siblings_only: true # MD033 - Inline HTML # Triggered when raw HTML is used in a markdown document diff --git a/docs/operator-guide/index.md b/docs/operator-guide/index.md index 1bac29615..48d23dc78 100644 --- a/docs/operator-guide/index.md +++ b/docs/operator-guide/index.md @@ -33,6 +33,13 @@ clouds: In the above case `uc-prod-infra` would be the operator area while `uc-prod` would be the regular project area. +## Upgrading + +- [Release Notes](../release-notes/index.md) - What you have to do to move a + deployment between versions. If you deploy from `main`, read the + [Unreleased section](../release-notes/v0.4.md#unreleased) of the current + series. + ## Infrastructure Topics - [Gateway API Migration Guide](gateway-api.md) - Migration from ingress-nginx to Kubernetes Gateway API with Envoy Gateway diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md new file mode 100644 index 000000000..b1c8c0c96 --- /dev/null +++ b/docs/release-notes/index.md @@ -0,0 +1,54 @@ +# Release Notes + +These pages document **what an operator has to do** to move a deployment from +one version of UnderStack to another: required changes to your deployment +repository, new or removed secrets, one-time manual steps, and how to roll +back. + +They are deliberately **not** a changelog. For the full list of merged pull +requests in a given tag, see the +[GitHub releases page](https://github.com/rackerlabs/understack/releases). + +!!! tip "If you deploy from `main`" + The default deployment model sets `understack_ref: HEAD`, so most + deployments track `main` continuously rather than a tag. Read the + [Unreleased section](v0.4.md#unreleased) of the current series page. It + covers everything merged to `main` since the most recent tag, and it is + updated in the same pull request as the change it describes. + +## Series + +| Series | Status | +| ------ | ------ | +| [v0.4.x](v0.4.md) | Current | + +## How to read these pages + +- There is one page per **minor** series. Each page lists versions + newest-first. +- **Only versions that require operator action get a section.** Most do not, so + most versions are absent from these pages. If a version has no section, it + needed nothing beyond a normal resync. +- Each section states its **Impact** (`Action required` or `Informational`) and + which cluster types it **applies to**. + +## Pinning a version + +To upgrade deliberately rather than continuously, pin `understack_ref` to a tag +in your cluster values file instead of leaving it at `HEAD`. See +[ArgoCD Application Management](../operator-guide/argocd-helm-chart.md) for the +full explanation of how refs are resolved. + +```yaml title="$CLUSTER_NAME/deploy.yaml" +understack_ref: v0.4.26 +``` + +## Related upgrade guides + +Some upgrades are large enough to warrant a standalone guide. Release notes link +to these rather than duplicating them. + +- [Gateway API Migration Guide](../operator-guide/gateway-api.md) — moving from + ingress-nginx to Envoy Gateway. +- [MariaDB Operator Upgrade Runbook](../operator-guide/mariadb-upgrade-runbook.md) + — upgrading the operator, with backup and restore steps. diff --git a/docs/release-notes/v0.4.md b/docs/release-notes/v0.4.md new file mode 100644 index 000000000..1510d9c88 --- /dev/null +++ b/docs/release-notes/v0.4.md @@ -0,0 +1,48 @@ + +# Release Notes: v0.4.x + +Operator-facing upgrade notes for the v0.4 series, newest first. See the +[Release Notes index](index.md) for how to read these pages. + +## Unreleased + +Changes merged to `main` but not yet in a tagged release. If you deploy with +`understack_ref: HEAD`, this section applies to you now. + +### Action required + +- _Nothing yet._ + +### Deploy repo changes + +- _Nothing yet._ + +### Deprecations and removals + +- _Nothing yet._ + +### Notes + +- _Nothing yet._ + +## v0.4.25 and earlier + +Per-version upgrade notes begin with **v0.4.26**. For earlier versions the only +record is the auto-generated pull request list on the +[GitHub releases page](https://github.com/rackerlabs/understack/releases). + +We have deliberately not reconstructed per-version notes for those releases: +inferring operator impact from pull request titles after the fact produces +confident-sounding instructions nobody has verified. The operator-affecting +changes from before v0.4.26 that _are_ documented have their own guides: + +- [Gateway API Migration Guide](../operator-guide/gateway-api.md) — the move + from ingress-nginx to Envoy Gateway. Required if your deployment still runs + ingress-nginx. +- [MariaDB Operator Upgrade Runbook](../operator-guide/mariadb-upgrade-runbook.md) + — upgrading the MariaDB operator, including backup and restore. diff --git a/properdocs.yml b/properdocs.yml index 1cc2c637a..6c8a27329 100644 --- a/properdocs.yml +++ b/properdocs.yml @@ -262,6 +262,13 @@ nav: - 'Scripts and Tools': - operator-guide/scripts.md - operator-guide/understackctl.md + # Keep this list newest-series-first and maintain it by hand. Do not switch it + # to include_dir_to_nav: that plugin only does an ascending ASCII sort, so + # v0.10.md would sort before v0.9.md, and its reverse toggle is global and + # would also flip the generated Workflows section. + - 'Release Notes': + - release-notes/index.md + - release-notes/v0.4.md - 'User Guide': - user-guide/index.md - user-guide/openstack-cli.md From 2ed4694d1f826ea0bcd523e0a6cd78db1e2f961c Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 4 Aug 2026 12:12:57 -0500 Subject: [PATCH 2/2] ci: require a release note on upgrade-impacting pull requests Adds RELEASING.md (the tag-cutting checklist and section template) plus a check that a flagged change actually documents itself. A PR is flagged either by the upgrade-impact label or by `!` in the title; the check is a no-op otherwise, so Renovate and routine PRs see no new friction. Commit-type autodetection is not used, since the history has no `!` or BREAKING CHANGE markers to key off. The workflow has no paths: filter (a path-filtered workflow reports no status on non-matching PRs and so can never be marked required) and runs with only contents: read, passing the title via the environment, so it works on fork PRs. --- .github/pull_request_template.md | 18 ++ .github/workflows/release-note-check.yaml | 73 ++++++++ RELEASING.md | 199 ++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/release-note-check.yaml create mode 100644 RELEASING.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..4726f755b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ + + +## What does this change do? + +## Upgrade impact + +- [ ] This change requires operator action to upgrade. If checked, add the + `upgrade-impact` label and a note under `Unreleased` in + `docs/release-notes/`. See [RELEASING.md](../RELEASING.md). + +Operator action means anything a deployment has to do beyond a normal resync: +deploy repo or values changes, new or removed secrets, enabling or disabling a +component, or a manual one-time step. diff --git a/.github/workflows/release-note-check.yaml b/.github/workflows/release-note-check.yaml new file mode 100644 index 000000000..ac9117de4 --- /dev/null +++ b/.github/workflows/release-note-check.yaml @@ -0,0 +1,73 @@ +name: Release note check + +# Deliberately has no `paths:` filter. A path-filtered workflow never reports a +# status on pull requests that do not match it, which means it can never be made +# a required check. Instead this always runs and short-circuits to success +# unless the pull request has been flagged as upgrade-impacting, so the majority +# of changes see no new friction. +on: + pull_request: + types: [opened, synchronize, reopened, edited, labeled, unlabeled] + merge_group: + types: [checks_requested] + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-note: + name: Release note for upgrade-impacting change + runs-on: ubuntu-latest + permissions: + contents: read + steps: + # merge_group events carry no pull request context, so there is nothing to + # check. Succeed so this workflow is still safe to mark as required. + - name: Skip on merge queue + if: github.event_name == 'merge_group' + run: echo "No pull request context on merge_group, skipping." + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + if: github.event_name == 'pull_request' + with: + fetch-depth: 0 + + - name: Require a release note when flagged + if: github.event_name == 'pull_request' + env: + # The title is attacker-controlled, so pass it through the environment + # rather than interpolating it into the script. + PR_TITLE: ${{ github.event.pull_request.title }} + PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + flagged=0 + if printf '%s' "$PR_TITLE" \ + | grep -Eq '^(feat|fix|docs|test|ci|chore)(\([^)]*\))?!:'; then + flagged=1 + echo "Flagged by '!' in the pull request title." + fi + if printf '%s' "$PR_LABELS" | grep -q '"upgrade-impact"'; then + flagged=1 + echo "Flagged by the upgrade-impact label." + fi + + if [ "$flagged" -eq 0 ]; then + echo "Not flagged as upgrade-impacting, nothing to check." + exit 0 + fi + + if git diff --name-only "$BASE_SHA" "$HEAD_SHA" \ + | grep -q '^docs/release-notes/'; then + echo "Release note present." + exit 0 + fi + + echo "::error::This pull request is flagged as upgrade-impacting but does not touch docs/release-notes/. Add a bullet to the Unreleased section of the current series page describing what an operator has to do, or remove the upgrade-impact label and the '!' from the title if no operator action is needed." + exit 1 diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..5d2c4238d --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,199 @@ +# Releasing UnderStack + +Repository-wide `vX.Y.Z` tags are created **manually**. No workflow creates +them. Pushing the tag triggers `containers.yaml` and `containers-openstack.yaml`, +which publish container images tagged with the git ref. + +Per-artifact tags (`understackctl/vX.Y.Z`, `nautobotop-vX.Y.Z`, +`kubectl-us-net/vX.Y.Z`, `ironic-hardware-exporter/vX.Y.Z`, `dexop-vX.Y.Z`) are +released independently and are not covered here. + +## Where upgrade notes live + +Operator-facing upgrade notes live in `docs/release-notes/`, one page per minor +series, listing versions newest-first. Anyone whose change requires operator +action adds a bullet under `Unreleased` on the current series page **in the same +pull request as the change**, and labels the pull request `upgrade-impact`. + +Two invariants: + +- There is **exactly one `Unreleased` section** across all series pages, always + on the highest-numbered page. +- **Most releases will not need a note at all.** Tags are cut frequently and + usually carry nothing an operator has to act on. A version that requires no + operator action gets **no section**, and cutting it involves no docs change + whatsoever. Empty sections teach operators that these pages are not worth + opening, which is the one outcome that makes them useless. + +Because `understack_ref` defaults to `HEAD` and the docs site only publishes +`main`, a note merged alongside its change is immediately visible to the +deployments that track `main`. That is the reason notes are written up front +rather than assembled at release time. + +## Cutting a patch release + +### 1. Review the diff for undocumented operator impact + +Not every operator-affecting change gets flagged. Before promoting the notes, +compare the previous tag against `main` over the paths where impact hides: + +```bash +git fetch --tags +git diff v0.4.25..main --stat -- \ + charts/argocd-understack/values.yaml \ + charts/argocd-understack/templates/ \ + components/images-openstack.yaml +``` + +Look specifically for: + +- new, renamed or removed `enabled:` keys under `global:` or `site:` +- OpenStack-Helm `chartVersion` bumps, especially ones crossing a release + series (for example `2025.2` to `2026.1`) +- OpenStack image series changes in `components/images-openstack.yaml` +- added or removed `application-*.yaml` templates, or new services added to the + service list in `application-openstack-helm.yaml` + +Anything you find here that is not already under `Unreleased` is a gap. Add it +now. + +### 2. Promote the `Unreleased` section + +**If every subsection under `Unreleased` still says `_Nothing yet._`, there is +nothing to do here.** Leave the page alone and skip to step 3 to tag. This is +the common case. + +Otherwise, in the current series page, for example `docs/release-notes/v0.4.md`: + +1. Change `## Unreleased` to `## v0.4.26` and add the metadata lines + (`**Released:**`, `**Impact:**`, `**Applies to:**`). +2. Delete any subsection whose only content is `_Nothing yet._`. +3. Insert a fresh `## Unreleased` block above it, copied from the template + below. + +Then open a pull request titled `docs: release notes for v0.4.26` and merge it +before tagging, so the tag contains its own notes. + +### 3. Tag the release + +Tag the current tip of `main`: + +```bash +git checkout main && git pull +git tag -a v0.4.26 -m "v0.4.26" +git push origin v0.4.26 +``` + +### 4. Create the GitHub release + +Create the release from the tag with auto-generated notes. If this version got a +section in step 2, add one line at the top of the body pointing at it: + +```markdown +**Upgrading?** See the +[v0.4.26 upgrade notes](https://rackerlabs.github.io/understack/release-notes/v0.4/#v0426). +``` + +If it got no section, leave the body as generated. Do not link a section that +does not exist. + +### 5. Confirm the builds + +Check that `containers.yaml` and `containers-openstack.yaml` succeeded for the +tag. + +## Cutting a minor release + +A new series always gets a page, whether or not the release itself has notes, +because that page is where subsequent `Unreleased` notes go. Same as above, but +first: + +1. Create `docs/release-notes/v0.5.md` with a `# Release Notes: v0.5.x` + heading. +2. Move the `Unreleased` block out of `v0.4.md` into `v0.5.md`. If it has + content, promote that content to `## v0.5.0` and leave a fresh `Unreleased` + block above it. If it is empty, just leave the empty `Unreleased` block on + the new page. Either way `v0.4.md` ends up with no `Unreleased` section. +3. Add `- release-notes/v0.5.md` to `properdocs.yml` **above** the v0.4 entry. + Forgetting this fails the docs build, which is intentional. +4. Update the series table in `docs/release-notes/index.md`: mark v0.5.x + Current and v0.4.x Maintenance. +5. Update the `Unreleased` anchor in the "If you deploy from `main`" callout in + `docs/release-notes/index.md` to point at the new page. + +## Section template + +Copy this for a new version section. Include only the subsections that apply +and delete the rest: an empty `Rollback` heading is worse than no `Rollback` +heading. + +Two authoring constraints in this repo. The `nl2br` extension is enabled, so a +single newline inside a paragraph renders as a line break. And markdownlint sets +`MD007: indent: 4`, so nested list items are indented by four spaces. + +````markdown +## v0.4.26 + +**Released:** 2026-08-11 +**Impact:** Action required +**Applies to:** site clusters + +### Summary + +One or two sentences on what changed and why an operator has to care. Link the +pull request or issue for the underlying detail. + +### Action required + +Ordered and copy-pasteable, in the order the steps must be performed. + +1. Do the first thing. + + ```bash + kubectl -n openstack get pods + ``` + +2. Do the second thing. + +### Deploy repo changes + +Show before and after, using the real path. + +```yaml title="$CLUSTER_NAME/deploy.yaml" +site: + neutron: + enabled: true +``` + +### Secrets + +- **New:** `` in namespace ``, keys ``, ``. +- **Removed:** ``, safe to delete after upgrading. + +### Chart and image versions + +- OpenStack-Helm `neutron` chart: `` to `` + (`charts/argocd-understack/values.yaml`). +- `keystone` image series: `` to `` + (`components/images-openstack.yaml`). Series tags are moving tags rebuilt + from `main`, and `pull_policy` is `Always`, so a resync pulls the current + build. + +### Verification + +How an operator confirms the upgrade worked. + +```bash +kubectl -n openstack get pods +``` + +### Rollback + +State honestly whether rollback is possible. If it is, set `understack_ref` +back to `v0.4.25` and resync. If it is not, say so plainly and give the +recovery path instead. + +### Known issues + +- None. +````