Skip to content

Commit fb9e1de

Browse files
kvapsclaude
andcommitted
feat(api): ControllerConfig typed singleton CRD (Phase 10.4 step 1)
New cluster-scoped ControllerConfig CRD (singleton, name=default) with structured fields: Spec.DRBDOptions — typed cluster-wide DRBD overrides Spec.PassphraseSecretRef — Secret pointer (no plaintext on Spec) Spec.ExtraProps — forward-compat for un-typed keys Phase 10.4 step 1 of the KVEntry-elimination plan. The ControllerConfig becomes the typed home for what used to live in the upstream-LINSTOR-shaped `ControllerProps` KVEntry instance — admission validation now enforces enum constraints (via the same +kubebuilder:validation:Enum markers that gate RD/RG DRBDOptions), and structured tooling (kubectl edit, GitOps) can operate on it directly. ResourceReconciler.resolveEffectiveProps now feeds ControllerConfig.Spec.DRBDOptions as the controller scope into the typed hierarchy resolver (was nil before this commit) and folds Spec.ExtraProps into the rendered Props bag. The legacy KVEntry-shaped controllerProps() reader stays as the forward- compat fallback for pre-migration clusters; both paths converge on the same Props output so a partially-migrated cluster keeps working. Subsequent steps will migrate the cluster passphrase, csi-volumes, and csi-snapshot-shippings KV instances onto their natural typed homes; once all four migrate, the KVEntry CRD itself goes. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 4803297 commit fb9e1de

4 files changed

Lines changed: 618 additions & 18 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright 2026 Cozystack contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// ControllerConfigName is the canonical singleton name of the
24+
// cluster-wide ControllerConfig instance. Operators install
25+
// blockstor with one ControllerConfig at this name; the reconciler
26+
// looks up `default` and refuses to read any other instance, so a
27+
// stale or duplicated ControllerConfig can't quietly take effect.
28+
const ControllerConfigName = "default"
29+
30+
// ControllerConfigSpec is the desired state of the cluster-wide
31+
// blockstor controller configuration. Phase 10.4: replaces the
32+
// upstream-LINSTOR-shaped `ControllerProps` KVEntry instance with a
33+
// typed singleton CRD so admission validation enforces enum
34+
// constraints and structured tooling (kubectl edit, GitOps) can
35+
// operate on it directly.
36+
//
37+
// Cluster-wide DRBD overrides live under Spec.DRBDOptions and feed
38+
// into the same hierarchy resolver used at RG/RD/Resource scopes
39+
// (controller → RG → RD → Resource); lower scopes still win
40+
// per non-nil field.
41+
type ControllerConfigSpec struct {
42+
// drbdOptions is the cluster-wide DRBD configuration. Acts as
43+
// the controller-scope input to the typed hierarchy resolver
44+
// (`pkg/drbd.ResolveDRBDOptions`).
45+
// +optional
46+
DRBDOptions *DRBDOptions `json:"drbdOptions,omitempty"`
47+
48+
// passphraseSecretRef points at the Secret carrying the
49+
// cluster-wide LUKS passphrase under the key `passphrase`. The
50+
// satellite reads it via the apiserver during reconcile;
51+
// passphrase never lands on Spec in plaintext. Replaces
52+
// `Props["DrbdOptions/Encryption/passphrase"]` on the legacy
53+
// ControllerProps instance.
54+
// +optional
55+
PassphraseSecretRef *PassphraseSecretRef `json:"passphraseSecretRef,omitempty"`
56+
57+
// extraProps carries upstream-LINSTOR-shaped Props keys we have
58+
// not yet typed into structured fields. Forward-compat shim
59+
// populated only by the REST shim when golinstor sends an
60+
// unknown key. Drained as we type more fields here.
61+
// +optional
62+
ExtraProps map[string]string `json:"extraProps,omitempty"`
63+
}
64+
65+
// PassphraseSecretRef is the cluster-wide passphrase Secret pointer.
66+
// Wrapper rather than a bare LocalObjectReference so we can attach
67+
// a Namespace field later without breaking the API (the secret
68+
// must live in the controller's own namespace today; once Phase 10.6
69+
// lands and the satellite reads via the apiserver it may move).
70+
type PassphraseSecretRef struct {
71+
// name is the Secret name. Required.
72+
// +required
73+
Name string `json:"name"`
74+
}
75+
76+
// ControllerConfigStatus reports observed state.
77+
type ControllerConfigStatus struct {
78+
// conditions track the current state of the ControllerConfig
79+
// (e.g. PassphraseSecretFound, DRBDOptionsValid).
80+
// +listType=map
81+
// +listMapKey=type
82+
// +optional
83+
Conditions []metav1.Condition `json:"conditions,omitempty"`
84+
}
85+
86+
// +kubebuilder:object:root=true
87+
// +kubebuilder:subresource:status
88+
// +kubebuilder:resource:scope=Cluster
89+
90+
// ControllerConfig is the cluster-wide singleton config for the
91+
// blockstor controller. Exactly one instance with name=`default`
92+
// is recognised; any other instance is ignored. Phase 10.4.
93+
type ControllerConfig struct {
94+
metav1.TypeMeta `json:",inline"`
95+
96+
// metadata is a standard object metadata
97+
// +optional
98+
metav1.ObjectMeta `json:"metadata,omitzero"`
99+
100+
// spec defines the desired state of ControllerConfig
101+
// +required
102+
Spec ControllerConfigSpec `json:"spec"`
103+
104+
// status defines the observed state of ControllerConfig
105+
// +optional
106+
Status ControllerConfigStatus `json:"status,omitzero"`
107+
}
108+
109+
// +kubebuilder:object:root=true
110+
111+
// ControllerConfigList contains a list of ControllerConfig.
112+
type ControllerConfigList struct {
113+
metav1.TypeMeta `json:",inline"`
114+
metav1.ListMeta `json:"metadata,omitzero"`
115+
Items []ControllerConfig `json:"items"`
116+
}
117+
118+
func init() {
119+
SchemeBuilder.Register(&ControllerConfig{}, &ControllerConfigList{})
120+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 139 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)