feat(c5c3): expose freeform service configuration on the ControlPlane#716
Merged
Conversation
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
force-pushed
the
implement/issue-704-controlplane-extraconfig
branch
from
July 22, 2026 13:11
2b7e204 to
54025ef
Compare
berendt
marked this pull request as ready for review
July 22, 2026 15:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Expose freeform service configuration on the ControlPlane. Today the ControlPlane surfaces only a curated subset of each service's settings; the freeform
extraConfigfield 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 blockspec.globalExtraConfigand per-service blocksspec.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)
6ed3a326refactor(horizon): export the Python setting-name pattern — Renames the package-levelpythonSettingNameregexp toPythonSettingName(and updates its two uses) so the ControlPlane webhook validatesservices.horizon.extraConfigsetting names with the exact identifier pattern the Horizon child webhook enforces, instead of copying the regexp and risking drift.135e92c5feat(c5c3): add extraConfig API fields and merge helper — Adds the four spec fields:GlobalExtraConfigand per-serviceExtraConfigon Keystone/Glance (map[string]map[string]string) and Horizon (map[string]apiextensionsv1.JSON). Adds a CEL rule forbiddingservices.keystone.extraConfigin External mode. Adds the sharedMergedExtraConfig(global, service)helper (in the api package so reconciler and webhook can never validate one merge and project another), normalized tonilon an empty merge, plus the nil-safeServiceHorizonSpec.DerivedPublicEndpoint. Regenerates deepcopy + CRD artifacts and promotesk8s.io/apiextensions-apiserverto a direct require.22dcab5cfeat(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 theMergedExtraConfigresult; Horizon gets its flat Django block deep-copied verbatim (the global INI block never applies to it) so the child never aliasescp.Spec. Projection and the admission-time dashboard-origin check both call the api package'sDerivedPublicEndpointso the URL is derived identically.47ebe973feat(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 (Glancekeystone_authtokenpassword, Keystone Horizon-derivedtrusted_dashboard, HorizonSECRET_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 whoseextraConfigwent stale-invalid; it fails open with a single warning when no catalog resolves. The webhook also mirrors the External-mode CEL rule.492ea721test(e2e): add ControlPlane extraConfig invalid-cr fixtures — Adds four generated invalid-CR fixtures pinning the admission rejections against a real API server: unknown option inglobalExtraConfig(keystone catalog, with provenance path), unknown option inservices.glance.extraConfig(glance catalog),services.keystone.extraConfigin External mode (type-level CEL), andSECRET_KEYinservices.horizon.extraConfig(ownership check). The canonical scaffold gains aglobalExtraConfigplaceholder that renders empty for every existing fixture, so all prior outputs stay byte-identical.2b7e2048docs: 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 itsProjectionRejectedcondition names) and refreshes the stale "not exposed" claims. Extends the ControlPlane CRD reference with theglobalExtraConfigand per-serviceextraConfigrows, 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
horizonPublicEndpointwas moved out ofinternal/controller/identity_backends.gointo the api package asServiceHorizonSpec.DerivedPublicEndpoint, which is why that file shows a net deletion.Implemented by planwerk-agent c4682b7 with Claude:claude-opus-4-8