Changelog System
Overview
The changelog system provides staff users with context-aware release information, including what changed, whether changes affect their specific deployment, and what actions are needed after upgrading. It replaces the basic "new version available" notification with structured, filterable changelogs that include security alerts, impact analysis, and deployment-specific relevance matching.
Architecture
flowchart TD
A[Release script generates<br/>changelog JSON per version] --> B[Published to<br/>docs.waldur.com/latest/changelog/]
B --> C[GET /api/version/<br/>fetches index.json, cached 1h]
C --> D{New version<br/>available?}
D -->|Yes| E[changelog_summary +<br/>security_alert in response]
D -->|Yes| F[Celery: compute_changelog_impact<br/>background task]
E --> G[Frontend: badge +<br/>security banner]
F --> H[ChangelogImpactAnalysis<br/>model in DB]
H --> I[GET /api/changelog/pending/<br/>merges static + analysis]
I --> J[Frontend: /administration/changelog/]
Components
| Component | Location | Purpose |
|---|---|---|
| Changelog JSON files | docs.waldur.com/latest/changelog/releases/ |
Structured release data (static) |
index.json manifest |
docs.waldur.com/latest/changelog/index.json |
Version list with summary stats |
waldur_core.changelog app |
src/waldur_core/changelog/ |
Backend API, models, utils |
ChangelogImpactAnalysis |
Database model | Stores background analysis results |
changelog/schema.json |
Repo root | JSON Schema for release files |
assemble_changelog |
Management command | Assembles fragments into release files |
Changelog Data Format
Each release has a structured JSON file following changelog/schema.json. Key fields per entry:
Entry structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Entry types
| Type | Description |
|---|---|
breaking |
Backward-incompatible changes |
security |
Security fixes (must include security object) |
deprecation |
Will be removed in a future version |
feature |
New functionality |
improvement |
Enhancement to existing functionality |
fix |
Bug fix |
Scope
| Value | Meaning | Relevance filtering |
|---|---|---|
core |
Affects all deployments | Always shown |
plugin |
Requires specific plugins | Filtered by relevant_when |
infra |
Deployment infrastructure only | Always shown (same as core - component isn't consulted for relevance) |
dev |
Development/testing only | Hidden in production views |
Component
Array of stack layers affected: backend, frontend, helm, docker, site-agent, sdk.
Categories
marketplace, auth, identity, openstack, slurm, invoices, ai_assistant, reporting, policy, proposal, support, ui, infrastructure, notifications.
Impact and risk
| Risk | Meaning |
|---|---|
high |
Breaking change, data migration, or service disruption |
medium |
Behavior change that may affect workflows |
low |
Minor change, unlikely to affect workflows |
none |
Informational, no action needed |
affected_scope provides a broad impact category when a specific Django model doesn't apply: all_users, all_resources, infrastructure, api_consumers, none.
affected_resources with model and filter enables the Celery task to run live count queries (e.g., "47 pending orders affected"). Both model and the keys of filter are checked server-side against an explicit allowlist (ALLOWED_IMPACT_MODELS in changelog/tasks.py) before any query runs — this JSON is remote, unauthenticated input, so only pre-approved (model, field) combinations are queryable, with exact-match values only (no __ lookups). Currently allowlisted: marketplace.Resource.state, marketplace.Offering.{state,type}, marketplace.Order.{state,type}, and is_staff/is_support/is_active for affected_users. state fields are FSMIntegerFields, so filter values must be the numeric state (e.g. {"state": 1}), not a display string like "pending-consumer" — a non-matching model, field, or value type is skipped (logged, not counted) rather than erroring. Extending the allowlist is a deliberate, reviewed code change in tasks.py, not something a changelog entry can request on its own.
Security entries
Entries with type: security must include a security object:
1 2 3 4 5 6 7 8 9 10 11 | |
Security urgency levels control frontend notification behavior. changelog_summary.security_alert (and the badge/banner it drives) is only ever populated for critical/high — build_changelog_summary filters on exactly those two (utils.py) — so moderate/low entries never reach any dedicated notification; they appear only as regular entries wherever the changelog is listed, ranked by their own impact.risk:
| Urgency | UI behavior |
|---|---|
critical |
Red banner (SecurityAlertBanner, bg-danger), persistent, cannot dismiss; red footer badge |
high |
Orange/warning banner (bg-warning), dismissible per session (sessionStorage); orange footer badge |
moderate |
No dedicated banner or badge — standard changelog entry only |
low |
No dedicated banner or badge — standard changelog entry only |
Version model (stable and RC)
Waldur uses SemVer with -rc.N pre-releases. Each release file contains:
entries[]— cumulative since the previous stable release (base_stable_version)since_previous[]— incremental since the immediately preceding version (previous_version, RC or stable)component_activity— per-repo commit counts with compare URLs (optional;release.shdoes not produce it yet)
This supports three views: stable-to-stable, RC-to-RC, and RC-to-stable.
assemble_changelog builds both lists. An RC's fragments cover only the commits since the previous RC, so with --previous-release pointing at that RC's file, its entries are the previous RC's entries (ids kept) followed by the new ones, and since_previous is the new ones. A stable release's fragments cover the whole cycle, so they are its entries; with --previous-release pointing at the last RC, since_previous keeps only the entries that list a commit the RC's entries don't.
Which entries a deployment is shown
Every pending release is listed, RCs included, but entries are never repeated. The pending releases are walked in version order: while a release's previous_version is the version just before it (the deployment's own version for the first one), its since_previous is used; when the chain breaks (a release file missing, or one without since_previous), that release falls back to its cumulative entries. Entry ids already shown are skipped, which covers an RC's carried-over entries after such a break. utils.select_new_entries() implements this, and pending, changelog-entries and the impact analysis all use it.
API Endpoints
All endpoints require IsAuthenticated + IsStaffOrSupportUser.
| Endpoint | Description |
|---|---|
GET /api/version/ |
Returns version + changelog_summary with security alerts |
GET /api/changelog/pending/ |
Cumulative changelog from current to latest, with relevance and impact analysis |
GET /api/changelog/{version}/ |
Full changelog for one version |
GET /api/changelog/{version}/delta/ |
Only since_previous entries |
GET /api/changelog/compare/{from}/{to}/ |
Merged deltas between arbitrary versions |
GET /api/changelog-entries/ |
Flat, paginated, filterable list of entries across all pending versions (table view) |
GET /api/changelog-upgrade-report/ |
Markdown upgrade report, maintenance announcement text and upgrade commands covering every pending entry |
Version endpoint response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
latest_version and versions_behind depend on what the deployment runs. A stable deployment is offered the index's latest_stable, and versions_behind counts pending stable releases. A deployment running a release candidate (including a development build of one, such as 8.1.3-rc.15+49.gb97210b96) already tracks RCs, so it is offered the newer of latest_stable and latest_rc, and versions_behind counts every pending release. utils.get_latest_version() and utils.count_versions_behind() implement this; the pending and entries endpoints use the same count.
Pending endpoint response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Flat entries endpoint
GET /api/changelog-entries/ flattens the entries of every pending version, selected as described in "Which entries a deployment is shown", into one paginated list — for the standard Waldur table component, rather than the per-release grouping pending returns. Query parameters:
| Param | Description |
|---|---|
type |
Filter by entry type |
risk |
Filter by impact.risk |
scope |
Filter by scope |
version |
Filter to entries from one release version |
highlight |
true/1 — only entries with highlight: true |
relevant_only |
true/1 — only entries relevant to this deployment |
search |
Case-insensitive match against title/description |
page, page_size |
Pagination (page_size clamped to 1-200) |
Results are sorted relevant-first, then by risk (high to none). Each entry additionally carries version, release_date, release_type (from the release it belongs to).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Upgrade report endpoint
GET /api/changelog-upgrade-report/ backs the admin changelog page's "Download report", "Schedule upgrade" and "Upgrade commands" actions. They describe the whole upgrade, so the response is built from every pending entry (selected as for changelog-entries), independent of the table's filters and pagination. report.py builds it:
report— Markdown brief: breaking changes, security fixes, post-upgrade actions, every change, the upgrade commands and a pre-upgrade checklist.announcement/announcement_type— prefilled maintenance announcement text;warningwhen the upgrade brings security fixes or breaking changes, otherwiseinformation.commands—helmanddocker_compose, following the waldur-helm and waldur-docker-compose READMEs. The Helm chart version equals the Waldur version and its repository is added aswaldur-charts. Docker Compose pins the images in.env, so the commands updateWALDUR_MASTERMIND_IMAGE_TAGandWALDUR_HOMEPORT_IMAGE_TAGbeforepull,downandup -d; migrations then run on start-up in thewaldur-mastermind-db-migrationservice.
1 2 3 4 5 6 7 8 9 | |
Relevance Matching
Entries are matched against the deployment's configuration:
- Plugins:
relevant_when.pluginschecked againstINSTALLED_APPS - Feature flags:
relevant_when.feature_flagschecked against the features registry - Settings:
relevant_when.settingschecked for non-default constance values
Entries with scope: core or scope: infra, or empty relevant_when arrays, are always relevant.
Background Impact Analysis
The compute_changelog_impact Celery task runs automatically when /api/version/ detects a new release. It computes:
- Affected resource counts — runs
Model.objects.filter(**filter).count()for entries withaffected_resources.model, restricted to theALLOWED_IMPACT_MODELS/ALLOWED_USER_IMPACT_FIELDSallowlist (see "Impact and risk" above) - Settings diffs — captures current vs default values for referenced constance settings
- Plugin state — records which referenced plugins are installed
Results are stored in the ChangelogImpactAnalysis model and refreshed every 24 hours. The /api/changelog/pending/ endpoint merges these results into the response.
Configuration
Disabling the changelog
Set in override.conf.py or Helm values:
1 | |
1 2 3 4 | |
When disabled:
/api/version/returns only{version}— no changelog data/api/changelog/*endpoints return 404- The Celery impact analysis task is a no-op
- No frontend changelog UI elements are rendered
Management Command: assemble_changelog
Assembles changelog fragments from changelog/next/ into a release file:
1 2 3 4 5 6 | |
Options
| Flag | Required | Description |
|---|---|---|
--release-version |
Yes | Version string (e.g., 8.0.8 or 8.0.8-rc.1) |
--date |
Yes | Release date (ISO 8601) |
--release-type |
No | stable (default) or rc |
--base-stable |
No | Previous stable version for cumulative entries |
--previous |
No | Immediately preceding version |
--previous-release |
No | Path to the previous version's release file: an RC carries its entries forward, a stable release derives since_previous from it |
--stable-target |
No | Target stable version (for RC releases) |
--summary |
No | Release summary text |
--no-clear |
No | Don't clear changelog/next/ after assembly |
--dry-run |
No | Validate and print without writing |
Fragment format
Each fragment in changelog/next/ is a JSON file with the same structure as an entry, minus the id field (auto-assigned):
1 2 3 4 5 6 7 8 9 10 | |
Validation rules
Each assembled entry is validated against changelog/schema.json (the $defs/entry sub-schema) via jsonschema — schema.json is the source of truth, so the constraints below track it automatically rather than needing to be kept in sync by hand:
typemust be one of:breaking,security,deprecation,feature,improvement,fixcategorymust match the predefined category listscopemust becore,plugin,infra, ordevcomponentvalues must be from:backend,frontend,helm,docker,site-agent,sdkimpact.riskmust behigh,medium,low, ornone
One rule the schema can't express and is checked separately: security entries (type: security) must include a security object with urgency, affected_versions, exploitability, and mitigation.
Schema Versioning
The JSON schema at changelog/schema.json includes a schema_version field (currently 1.0.0). Release files include this version for forward compatibility — consumers can check the schema version before parsing.
Release Script Integration
Changelog entries are generated at release time by waldur-docs/scripts/release.sh — a local, interactive script a dev runs to cut a release (./scripts/release.sh <VERSION>). It's not CI-driven: there's no dedicated pipeline job for changelog generation, staff/support-facing or otherwise. The script already collects git log data across all core repos (mastermind, homeport, helm, docker-compose) between the previous tag and the new one, and drives two independent Claude Code (claude --print) calls against that same commit data:
scripts/prompts/changelog-prompt.md— produces free-form Markdown, prepended todocs/about/CHANGELOG.md. This is the pre-existing, human-facing changelog and is unaffected by the structured system described in this document.scripts/prompts/changelog-json-prompt.md— produces a single{summary, entries[]}JSON object classifying the same changes into this schema's fields (type,category,scope,component,impact,relevant_when, etc.). Each entry inentries[]is written to its own file underwaldur-mastermind/changelog/next/(a fragment, matching this document's fragment format above), thenassemble_changelogvalidates and assembles them intochangelog/releases/{version}.jsoninside this repo (git-ignored), passing the previous version's file fromwaldur-docs/docs/changelog/releases/as--previous-release. That file is copied intowaldur-docs/docs/changelog/releases/{version}.json, andscripts/update_changelog_index.pyupserts its summary row intowaldur-docs/docs/changelog/index.json.
Both stable and RC releases go through this same flow — there's no separate "daily RC" cadence script.
Publishing happens for free: docs/changelog/ lives in waldur-docs' normal docs_dir, so the site's existing "Deploy MkDocs pages" CI job picks it up like any other doc content. That job runs mike deploy latest on every push to master — including the release script's changelog commit — and mike deploy $TAG (the version alone, no alias) on a stable tag. docs.waldur.com/latest/changelog/... is therefore the only stable, unversioned URL for the changelog, which is why CHANGELOG_BASE_URL in utils.py points at /latest/changelog, not /changelog.
waldur-docs is the only place the release files are committed; the copy assemble_changelog writes into this repo is transient.