Skip to content

docs: correct and complete the documentation against the code - #591

Merged
slauger merged 7 commits into
developfrom
docs/review-corrections
Sep 3, 2026
Merged

slauger merged 7 commits into
developfrom
docs/review-corrections

Conversation

@slauger

@slauger slauger commented Sep 3, 2026

Copy link
Copy Markdown
Owner

A documentation review found that the docs had never been checked against the code. This works through the findings, verifying each one before changing anything -- two of the review's own claims did not survive that check and were left alone.

Work in progress; more commits will follow.

Examples the API server rejects

spec.code is a list of sources, not a single one. Ten examples showed it as an object. Verified with kubectl apply --dry-run=server:

list form:   config.openvox.voxpupuli.org/... created (server dry run)
object form: Error: unknown field "spec.code.image"

The image tags 8.12.1 and 8.13.0 appeared at ten places and cannot exist -- images/openvox-versions.yaml states the tag carries the operator release version, and the published tags are 0.12.0, develop, latest.

Three label selectors do not exist in labels.go: app.kubernetes.io/instance is never set, app.kubernetes.io/name is openvox not openvox-server, and the CA label has no hyphen. A selector matching nothing is worse than no command, because it reads like an answer.

Behaviour the controller does not have

Error is documented as a phase for Config, Server and Database. It exists in the enum but nothing assigns it: a failed reconcile keeps the previous phase and reports the reason in a condition. Someone waiting for Error waits forever. Replaced with the condition to watch; the jsonpath was tested against a live object.

serverRef on a Pool exists neither in the CRD nor the chart -- a Server names its pools via poolRefs. Removed, and the direction spelled out.

The deny-all trap

The operator sets autosign as soon as a CertificateAuthority exists, and evaluatePolicies returns false for an empty list. Every fresh install starts in deny-all, and nothing shows it: servers run, the Config reports Running, agents sit in --waitforcert.

Also separated two cases the troubleshooting guide conflated: a Certificate stuck in Pending is almost never a policy problem, because for an internal CA the operator signs its own Certificates over the CA API. Policies govern agents.

Reference pages against the CRDs

Compared every documented field in both directions. observedGeneration exists on all nine status types and appeared on two pages; added to the other seven. Certificate gained signedSpecHash and effectiveDNSAltNames, Server gained readOnlyRootFilesystem.

The ImageSpec table still listed defaults that #549 and #576 removed and contradicted the prose beneath it.

The missing guide

Nothing showed an agent connecting. New page covering where to point it, running one in and outside the cluster, signing by hand, revoking, and verifying. Two things it gets right that are easy to miss: ca_server is only needed once CA and compilers are in separate pools, and puppet agent --test returns 2 on success-with-changes.

It also states that openvox-agent-<major> is built by CI and not by the release workflow, so it has only a develop tag -- a test artifact, not a supported way to run agents.

Two review claims that did not hold

The report said the docs promise a new key on renewal and that the Renewing phase is described wrongly. Neither is true; both pages were already correct. Applying the list blindly would have broken working documentation. What was actually missing is that renewal reuses the key deliberately, so it extends validity without rotating -- now documented.

Three classes of error that make a copied example fail rather than mislead
slowly.

spec.code is a list of sources, not a single one. Ten examples showed it as
an object; the API server answers 'unknown field spec.code.image' and
refuses the manifest. Verified against the CRD with kubectl apply
--dry-run=server: the list form is accepted, the object form is not.

The image tags 8.12.1 and 8.13.0 appeared at ten places and cannot exist.
images/openvox-versions.yaml states that the image name encodes the OpenVox
major while the tag carries the operator release version; the published tags
are 0.12.0, develop and latest.

Three label selectors in the troubleshooting and CA import guides do not
exist in labels.go: app.kubernetes.io/instance is never set,
app.kubernetes.io/name is openvox rather than openvox-server, and the CA
label is certificateauthority without a hyphen. A selector that matches
nothing is worse than no command at all, because it reads like an answer.

Also corrects two resource names: the Config ConfigMap is {name}-config,
and the CA setup Job is {ca}-ca-setup. The remaining names in the reference
tables were checked against the code and are correct.
The Error phase is documented for Config, Server and Database as
'reconciliation failed'. It exists in the CRD enum but no code path assigns
it: a failing reconcile leaves the phase where it was and reports the reason
in a condition. Someone waiting for phase Error waits forever, which is the
worst kind of wrong - it looks like a working diagnosis. Replaced with the
condition to watch, and the jsonpath was checked against a live object.

The Gateway API concept showed serverRef on a Pool. That field exists
neither on the CRD nor in the chart; a Server names its pools through
poolRefs and the Pool selects those pods. Removed, and the direction of the
relationship spelled out, since getting it backwards is the natural guess.

Added what the code does but nothing described: renewal reuses the existing
private key deliberately, because the CA renews for the same public key. So
renewal extends validity without rotating the key, and replacing a key needs
a new Certificate.

Checked and left alone: the Renewing phase description is already accurate,
and no page claims renewal generates a new key. The review report asserted
both; neither held up.
The operator writes autosign = /usr/local/bin/openvox-autosign as soon as a
CertificateAuthority exists, regardless of whether any SigningPolicy does,
and evaluatePolicies returns false for an empty list. An empty policy list is
therefore deny-all rather than off, which is the state every fresh install
starts in.

Nothing surfaces it: the servers run, the Config reports Running, and every
agent sits in --waitforcert until it gives up. Documented in the quickstart
and in the agent section of the troubleshooting guide, with the manual
puppetserver ca sign path as the immediate way out.

Also separates two cases the troubleshooting guide conflated. A Certificate
resource stuck in Pending is almost never a policy problem: for an internal
CA the operator signs its own Certificates over the CA API with the operator
signing certificate, so autosign is not involved. That entry now lists the
causes that do apply - CA not ready, signing certificate not yet available,
certname already claimed, external CA - each with a command that reads the
condition rather than the phase. Policies govern agents, and that is where
the note now lives.
Compared every documented field against the generated CRDs, in both
directions.

observedGeneration exists on all nine status types since #549 but appeared on
two reference pages. Added to the remaining seven, with the sentence that
makes it useful: a value below metadata.generation means the rest of the
status has not caught up yet.

Certificate gained signedSpecHash and effectiveDNSAltNames in the status
table. Both drive behaviour a reader needs to predict - the first decides
when a certificate is re-signed, the second says which alt names it is
actually issued for once Pools contribute.

Server gained readOnlyRootFilesystem, the per-Server override added in #575.

The ImageSpec table still listed defaults that #549 and #576 removed:
repository ghcr.io/slauger/openvox-server-8, tag latest, pullPolicy
IfNotPresent. It contradicted the prose directly beneath it. Corrected, and
the reason spelled out - a nested default is applied whether or not the
parent object was given, so a defaulted field can never mean inherit - plus
the pullSecrets rule that a Server list replaces rather than extends.

Checked and found correct: every other documented default matches its CRD,
and no reference page names a field that does not exist.
The user documentation never showed an agent connecting. puppet agent,
--waitforcert, ca_server and puppetserver ca sign appeared nowhere, so the
moment the product exists for was the one step a reader had to work out alone.

The guide covers what an agent needs, where to point it, running one inside
the cluster and reaching one from outside, signing by hand when no policy
matches, revoking, and how to tell from the server side that it worked.

Two things it states that are easy to get wrong. ca_server is only needed
once the CA and the compilers are in separate pools - with the default
poolRefs [ca, server] one address serves both, which is why the setting
appears nowhere in this repository. And puppet agent --test returns 2 when it
applied changes, so a Job that treats non-zero as failure reports a
successful run as broken.

It also names what the agent image is: openvox-agent-<major> is built by CI
and not by the release workflow, so it carries only the develop tag - no
latest, no version. Documented as the test artifact it is, rather than
implying it is a supported way to run agents.

Every claim was checked: the Service default is ClusterIP, the Deployment
carries the Server name, crlRefreshInterval defaults to 5m, and the image
tags are what ghcr actually holds.
Extracted all 42 manifests from the documentation and ran them against a
real API server with kubectl apply --dry-run=server. One was rejected for a
reason a reader could not guess: the NodeClassifier page abbreviated the
image block as 'image: ...', and the resulting error talks about a type
mismatch rather than the ellipsis. Replaced with the real block; all 42 are
accepted now.

The Database concept claimed the Config controller waits when the Database
is not Running. It does not: renderPuppetDBConf returns a puppetdb.conf
without server_urls and the servers start regardless. Together with
soft_write_failure = true, which the operator sets in every path, a server in
that state compiles catalogs normally while reports and exported resources go
nowhere and nothing is raised. That resolves itself during bring-up and only
bites when the Database never becomes ready, so the symptom is an empty
PuppetDB rather than an error.

Monitoring now states that CertificateAuthority and Certificate write the
same expiry metric with the same labels and nothing distinguishing them, so
the .*-ca matcher in the CA alert is a naming convention and not a guarantee.
Added an absence rule for the CRL metric: a staleness alert cannot fire on a
series that was never created, which is exactly the case when the operator
never got far enough to refresh one.
README and the feature list promise 'Heap size calculated from memory limits
(90%) - no manual -Xmx tuning needed'. The controller does contain that
calculation, but it is unreachable: ServerSpec.JavaArgs carries a CRD default,
so the field is never empty and the first branch of resolveJavaArgs always
wins. Every Server runs with -Xms512m -Xmx1024m no matter how much memory it
is given.

Replaced the claim with what actually applies, and noted it on the field in
the Server reference with a link to #592, where the fix is tracked. Once the
default is removed the derivation works and the wording can go back.

Third instance of the same pattern after #550 and #576: a CRD default takes
away the empty state a fallback depends on.
@slauger
slauger merged commit 51bd89a into develop Sep 3, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant