Skip to content

feat(c5c3): expose freeform service configuration on the ControlPlane#716

Merged
berendt merged 7 commits into
mainfrom
implement/issue-704-controlplane-extraconfig
Jul 22, 2026
Merged

feat(c5c3): expose freeform service configuration on the ControlPlane#716
berendt merged 7 commits into
mainfrom
implement/issue-704-controlplane-extraconfig

Conversation

@berendt

@berendt berendt commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Expose freeform service configuration on the ControlPlane. Today the ControlPlane surfaces only a curated subset of each service's settings; the freeform extraConfig field lives only on the child CRs, where a direct edit does not stick once the ControlPlane owns the field. This branch adds a global INI block spec.globalExtraConfig and per-service blocks spec.services.{keystone,glance,horizon}.extraConfig, merges the INI blocks key by key (per-service wins), projects the result onto the children, and runs the sibling issues' option-catalog and owned-key checks against the merged result at ControlPlane admission — every rejection and warning naming the block that carries the offending key.

Closes #704

Change set (in commit order)

  1. 6ed3a326 refactor(horizon): export the Python setting-name pattern — Renames the package-level pythonSettingName regexp to PythonSettingName (and updates its two uses) so the ControlPlane webhook validates services.horizon.extraConfig setting names with the exact identifier pattern the Horizon child webhook enforces, instead of copying the regexp and risking drift.

  2. 135e92c5 feat(c5c3): add extraConfig API fields and merge helper — Adds the four spec fields: GlobalExtraConfig and per-service ExtraConfig on Keystone/Glance (map[string]map[string]string) and Horizon (map[string]apiextensionsv1.JSON). Adds a CEL rule forbidding services.keystone.extraConfig in External mode. Adds the shared MergedExtraConfig(global, service) helper (in the api package so reconciler and webhook can never validate one merge and project another), normalized to nil on an empty merge, plus the nil-safe ServiceHorizonSpec.DerivedPublicEndpoint. Regenerates deepcopy + CRD artifacts and promotes k8s.io/apiextensions-apiserver to a direct require.

  3. 22dcab5c feat(c5c3): project merged extraConfig onto service children — Projects the freeform blocks onto the three children, each assigned unconditionally so clearing a ControlPlane block reverts the child rather than pinning the last value. Keystone and Glance get the MergedExtraConfig result; Horizon gets its flat Django block deep-copied verbatim (the global INI block never applies to it) so the child never aliases cp.Spec. Projection and the admission-time dashboard-origin check both call the api package's DerivedPublicEndpoint so the URL is derived identically.

  4. 47ebe973 feat(c5c3): validate ControlPlane extraConfig at admission — Wires two validation families into the ControlPlane webhook. Family A (shape and ownership, un-gated) rejects malformed section/key/Horizon-setting names, forbids overrides of operator-owned keys (Glance keystone_authtoken password, Keystone Horizon-derived trusted_dashboard, Horizon SECRET_KEY/websso/multi-domain), and warns on honored-but-owned overrides — attributing every finding to the concrete global or per-service block. Family B validates the merged INI of each declared INI service against the release option catalog, gated on update by an input-change check so an unrelated edit (e.g. a replica bump) never rejects a CR whose extraConfig went stale-invalid; it fails open with a single warning when no catalog resolves. The webhook also mirrors the External-mode CEL rule.

  5. 492ea721 test(e2e): add ControlPlane extraConfig invalid-cr fixtures — Adds four generated invalid-CR fixtures pinning the admission rejections against a real API server: unknown option in globalExtraConfig (keystone catalog, with provenance path), unknown option in services.glance.extraConfig (glance catalog), services.keystone.extraConfig in External mode (type-level CEL), and SECRET_KEY in services.horizon.extraConfig (ownership check). The canonical scaffold gains a globalExtraConfig placeholder that renders empty for every existing fixture, so all prior outputs stay byte-identical.

  6. 2b7e2048 docs: document ControlPlane freeform service configuration — Adds a Free-form service configuration section to the advanced-configuration guide (merge semantics, Horizon special case, provenance messages, External-mode posture, admission behavior, revert-on-clear, and the operator-build catalog-skew limitation with its ProjectionRejected condition names) and refreshes the stale "not exposed" claims. Extends the ControlPlane CRD reference with the globalExtraConfig and per-service extraConfig rows, the External-mode forbid rule, and an ExtraConfig admission-checks subsection.

Divergence from the issue

None material. The implementation follows the plan's binding decisions — key-by-key merge, Horizon as a per-service block only, and the ControlPlane webhook importing the service api packages directly (with the L1-import DECISION comment amended accordingly). As the plan anticipated, the shared dashboard-URL derivation horizonPublicEndpoint was moved out of internal/controller/identity_backends.go into the api package as ServiceHorizonSpec.DerivedPublicEndpoint, which is why that file shows a net deletion.


Implemented by planwerk-agent c4682b7 with Claude:claude-opus-4-8

berendt added 7 commits July 22, 2026 12:39
Export the package-level regexp that validates extraConfig setting
names so it can be reused outside the Horizon webhook package. The
ControlPlane webhook in the c5c3 operator will validate
services.horizon.extraConfig setting names at admission and must apply
the exact identifier pattern the Horizon child webhook enforces,
rather than copying the regexp and risking drift.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
Add free-form service configuration to the ControlPlane CRD: a global
INI block spec.globalExtraConfig, per-service INI blocks
spec.services.keystone.extraConfig and spec.services.glance.extraConfig,
and flat Django settings spec.services.horizon.extraConfig
(map[string]JSON). The global block and a service's own INI block are
merged key by key: sections are unioned, the per-service value wins per
key, and a global key with no per-service counterpart stays effective.
The global block is INI-only and never applies to Horizon, which renders
flat Django settings. On Keystone the block is forbidden in External
mode (new CEL rule, later mirrored by the webhook).

Add MergedExtraConfig, the single merge helper both the reconciler and
the validating webhook will consume so admission and projection can
never drift, normalized to nil on an empty merge. Add the nil-safe
ServiceHorizonSpec.DerivedPublicEndpoint, the shared dashboard-URL
derivation moved here from the identity-backend projection. Projection
and webhook validation land in later packages. Regenerate the deepcopy
and CRD artifacts and promote k8s.io/apiextensions-apiserver to a direct
require.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
Project the ControlPlane's freeform config blocks onto the three
service children, each assigned unconditionally so clearing a
ControlPlane block reverts the child rather than pinning the last
projected value. Keystone and Glance receive the key-by-key merge of
globalExtraConfig and their per-service block through the shared
MergedExtraConfig helper, so admission and reconciliation agree on the
effective INI. Horizon receives its flat Django block deep-copied
verbatim (the global INI block never applies to it) so the child never
aliases cp.Spec. This projection and the admission-time
dashboard-origin check both call the api package's
DerivedPublicEndpoint so they derive the URL identically.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
Validate the ControlPlane's freeform config blocks at admission with
two validation families wired into the ControlPlane webhook. Family A
(shape and ownership) runs un-gated on every create and update: it
rejects malformed section, key, and Horizon setting names, forbids
overrides of the operator-owned keys the ControlPlane projects
(Glance's keystone_authtoken password, Keystone's Horizon-derived
trusted_dashboard, and Horizon's SECRET_KEY / websso / multi-domain
settings), and warns on honored-but-owned overrides, attributing
every finding to the concrete global or per-service block that
carries it. Family B validates the merged INI of each declared INI
service against the release option catalog and is gated on update by
an input-change check, so a CR whose extraConfig went stale-invalid
is never rejected by an unrelated edit such as a replica bump. Family
B fails open with a single warning when no catalog resolves for a
service, so a digest pin, an unparseable tag, or a release the build
ships no catalog for never blocks admission. The webhook also mirrors
the CEL rule forbidding services.keystone.extraConfig in External
mode.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
Pin the freeform extraConfig admission rejections to deterministic
admission failures against a real API server: an unknown option in
spec.globalExtraConfig rejected by the keystone option catalog with its
provenance path, an unknown option in services.glance.extraConfig
rejected by the glance option catalog, services.keystone.extraConfig in
External mode rejected by the type-level CEL rule, and SECRET_KEY in
services.horizon.extraConfig rejected by the webhook's ownership check.
The four numbered fixtures are generated from the canonical scaffold,
which gains a globalExtraConfig placeholder that renders empty for every
existing fixture so all prior outputs stay byte-identical.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
Add a Free-form service configuration section to the
advanced-configuration guide covering spec.globalExtraConfig and the
per-service extraConfig blocks: the key-by-key merge with per-service
precedence, the Horizon flat-Django special case, the
provenance-carrying rejection paths, the External-mode posture, the
admission behavior (owned-key warnings and rejections, option-catalog
validation, fail-open, and update gating), the revert-on-clear
child-field ownership, and the operator-build catalog-skew limitation
with its ProjectionRejected condition names. Refresh the stale "not
exposed" claims for extraConfig in the feature-pointer table and the
standalone section so they cross-link the new material.

Extend the ControlPlane CRD reference with a globalExtraConfig row, an
extraConfig row on the Keystone, Horizon, and Glance service tables,
the External-mode forbid rule for services.keystone.extraConfig, and a
new ExtraConfig admission checks subsection documenting the
webhook-only shape, ownership, and option-catalog checks.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
govulncheck fails on GO-2026-5970, an infinite loop on invalid input
in golang.org/x/text/unicode/norm, reachable from all five workspace
modules and fixed upstream in v0.39.0. The advisory predates this
branch: main also requires v0.38.0, but its last CI run skipped the
path-filtered govulncheck job, so the finding surfaces on any branch
that touches Go files.

Bump golang.org/x/text to v0.39.0 in all five modules and tidy. The
accompanying go work sync also aligns github.com/go-logr/logr to
v1.4.4 in the three modules whose go.mod was stale against the
workspace selection; workspace builds already resolved v1.4.4.

On-behalf-of: @SAP
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@23technologies.cloud>
@berendt
berendt force-pushed the implement/issue-704-controlplane-extraconfig branch from 2b7e204 to 54025ef Compare July 22, 2026 13:11
@berendt
berendt marked this pull request as ready for review July 22, 2026 15:06
@berendt
berendt merged commit ff4817b into main Jul 22, 2026
53 checks passed
@berendt
berendt deleted the implement/issue-704-controlplane-extraconfig branch July 22, 2026 15:06
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.

Expose freeform service configuration on the ControlPlane

1 participant