Skip to content

Guide Conventions ​

The how-to guides under docs/guides/ all lead an operator through a task on a running cluster. To keep them copy-pasteable, every guide must agree on the same few structural rules. Without them each guide re-derives its structure from a neighbour and inherits that neighbour's inconsistencies — most visibly, example names that name a resource the reader's cluster never created.

This page is the contract. New guides follow it from the start; existing guides are being brought into conformance.

Placement: one subdirectory per service ​

Mirroring the reference set, service-specific guides live in a per-service subdirectory — docs/guides/keystone/, docs/guides/horizon/, docs/guides/glance/, and so on — and register in the matching collapsed service group of the Guides sidebar section in docs/.vitepress/config.ts. Cross-service and general operational guides (observability, day-2 operations, tenancy, end-to-end SSO) sit flat under docs/guides/ and register above the service groups. A guide that walks a single service's CRs, operator, or backends belongs to that service's subdirectory even when it touches the ControlPlane that projects them.

The prepare-new-guide Claude Code skill applies this contract: its scaffold-guide.sh prints a conforming skeleton for a chosen devstack anchor, and its validate-guide.sh checks a draft against the conventions the docs gate does not cover (see Claude Code skills for how to invoke it, and .claude/skills/prepare-new-guide/ for the scripts).

One devstack per guide ​

A reader arriving at a guide has to know exactly which cluster to build before the first command runs. Every guide therefore opens with a standardized prerequisites block that names exactly one Getting-Started tutorial and its verbatim bring-up command.

The section is short and machine-checkable. It consists of:

  1. A ## Prerequisites heading.
  2. Whose first element is a ::: info Devstack container that holds, in order:
    • exactly one link to a Getting-Started tutorial (../quick-start.md, ../quick-start-extended.md, ../quick-start-controlplane.md, or ../quick-start-metal-stack.md — one ../ deeper from a service subdirectory);
    • exactly one fenced ```bash block with the tutorial's verbatim bring-up command, including every WITH_* opt-in flag the guide depends on (a guide that needs Prometheus names WITH_PROMETHEUS=true, a guide that needs a ControlPlane names WITH_CONTROLPLANE=true, and so on);
    • a completion pointer — how far the reader follows the tutorial (its final Verify step, so the devstack is running).
  3. Followed by at most one prose paragraph of guide-specific prerequisites (an external LDAP server, a Keycloak realm, a CNI that enforces NetworkPolicy, ...); omit it when there are none. Do not use a list or stack another callout box when the prerequisite fits in this paragraph.

The map shows what each of the five quick starts deploys and how they relate. A guide whose devstack is a ControlPlane cannot start from a standalone cluster: that step takes make teardown-infra and a fresh cluster.

A map of the five quick starts. Two of them run one standalone Keystone on a kind cluster on the workstation. The Quick Start deploys the infrastructure stack, the keystone-operator and one Keystone resource, and ends with an authenticated token. The Quick Start (Extended) is the same devstack in more depth, with the UIs, the opt-ins, local builds, the E2E suite and Tempest, and going there from the Quick Start needs make teardown-infra first, because its Step 2 creates the same cobaltcore cluster on port 443. The other three run a whole control plane from one ControlPlane resource. The Quick Start (ControlPlane) runs on a fresh kind cluster and ends with a token, an image, a secret, a network and the Horizon dashboard. The Quick Start (metal-stack) runs the same ControlPlane resource on a Gardener shoot on metal-stack, turns every worker into a KVM hypervisor, and ends with a server on every worker, a volume, a live migration, an eviction and a backup. The Quick Start (Orders) extends the ControlPlane devstack without a teardown: a second kind cluster joins it as a registered target, a service owner orders seven pieces from an assigned namespace on each cluster, and it ends with four Secrets on the target and four Jobs that use them. Going from a standalone Keystone to a ControlPlane is a mode change that needs make teardown-infra and a fresh cluster. Four of them begin at git clone; the Quick Start (Orders) begins on the ControlPlane devstack.

The five quick starts on the map, and the four devstacks a guide may name with the names each produces:

DevstackVerbatim bring-upNames the examples may use
Quick StartKIND_HOST_PORT=8443 make deploy-infra (then the operator, image, and CR steps)Keystone CR keystone in openstack; admin Secret keystone-admin; DB Secret keystone-db; gateway openstack-gw; endpoint https://keystone.127-0-0-1.nip.io:8443/v3
Quick Start (Extended)kind create cluster --name cobaltcore --config hack/kind-config.yaml then make deploy-infra (then the operator, image, and CR steps)Same standalone names as the base Quick Start (keystone, keystone-admin, keystone-db)
Quick Start (ControlPlane)KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true make deploy-infraControlPlane CR controlplane in openstack; projected children controlplane-keystone / controlplane-horizon; admin Secret and ExternalSecret controlplane-keystone-admin-credentials; DB ExternalSecret controlplane-keystone-db-credentials; shared openstack-db / openstack-memcached / openstack-gw
Quick Start (metal-stack)EXTERNAL_CLUSTER=true WITH_CONTROLPLANE=true WITH_CEPH=true make deploy-infra (then Part 1, and Part 2 for a guide that needs servers)ControlPlane CR controlplane in openstack with the projected children of the ControlPlane devstack; Cinder child controlplane-cinder with the RBD backends ceph1 and cephbk on the lab Ceph, whose toolbox is rook-ceph/rook-ceph-tools; OVNCentral controlplane-ovn; OVNChassis lab-chassis; NeutronMetadataAgent lab-metadata-agent; NovaCompute lab; namespace hypervisor-system; the *.127-0-0-1.nip.io:8443 endpoints behind kubectl port-forward

Opt-in flags compose, so a guide that needs both a ControlPlane and Prometheus names KIND_HOST_PORT=8443 WITH_CONTROLPLANE=true WITH_PROMETHEUS=true make deploy-infra.

ControlPlane-first naming ​

The primary path of a guide uses the resource names its declared devstack actually produces — never a placeholder name that no tutorial creates. A reader on that devstack must be able to copy a command and have it hit a resource that exists.

When a guide's devstack is the ControlPlane Quick Start, the primary path uses the ControlPlane devstack's real names: the projected Keystone child is controlplane-keystone, its admin credential lives in controlplane-keystone-admin-credentials, and so on. Where a standalone (non-ControlPlane) installation differs, the differences go in a separate ## Standalone Keystone, without a ControlPlane section at the end, modeled on End-to-End SSO. Do not interleave the two naming worlds in the primary walkthrough.

Placeholder example names that no tutorial produces are banned. If a value genuinely varies per reader, express it as a substitution rule anchored on a concrete devstack name, not as an invented literal.

Never edit operator-projected children ​

On a ControlPlane devstack the Keystone and Horizon CRs are projected by the ControlPlane operator; editing them by hand is reverted on the next reconcile. A guide sets a knob on the ControlPlane CR, and lets the operator project it down. A knob the ControlPlane CRD does not expose is documented as standalone-only — in the guide's ## Standalone Keystone, without a ControlPlane section, applied to a Keystone CR the reader owns.

Every guide is testable ​

A guide describes behaviour the project also asserts in an end-to-end suite. Every guide closes with a terminal section titled exactly ## Tested by that names its mirroring suite(s) and the local invocation, so a reader can run the same flow the guide walks through. The invocation uses the --test-dir form, one line per suite:

bash
chainsaw test --test-dir tests/e2e/keystone/<suite>

tests/unit/docs/guide_devstack_and_tested_by_test.sh enforces this per guide: the ## Tested by section must exist, and every path it names with chainsaw test --test-dir must resolve to a real chainsaw-test.yaml.

Single source of truth for manifests ​

Where a guide mirrors an e2e suite, embed the fixture the suite applies rather than hand-maintaining a second copy that drifts. A VitePress snippet import keeps the rendered YAML in lockstep with the tested fixture; mark the region to import with column-0 # region <name> / # endregion <name> YAML comments around the CR document in the fixture:

md
<<< @/../tests/e2e/keystone/<suite>/00-keystone-cr.yaml#<region>

The fixture and the walkthrough serve different masters, so they carry different names. A suite fixture is isolation-named — a distinct CR name, its own logical database, deletionPolicy: Delete, dev-tag image pins — so the suite runs safely in the parallel suite pool. The walkthrough is devstack-named — it uses the resource names the guide's declared devstack actually produces (see ControlPlane-first naming), so a reader can copy a command and have it hit a resource that exists. Reconcile the two by keeping the walkthrough YAML devstack-named and embedding the isolation-named fixture as a labelled ::: details exhibit inside ## Tested by, prefaced by a sentence that states which names the exhibit uses and why. The import is real, so the exhibit cannot drift from the tested fixture, and the walkthrough keeps the names the reader's devstack can actually copy.

Housekeeping ​

  • Frontmatter (title, quadrant: operator) starts on line 1. VitePress renders anything above the frontmatter as body text.
  • Register a new guide in the Guides sidebar in docs/.vitepress/config.ts.
  • Source describes behaviour, not internal tracking. Do not put internal feature or requirement IDs, or issue references, in a guide.
  • Guides are written in English.