Skip to content

Commit 7babfb8

Browse files
committed
HYPERFLEET-1406 - feat: Define the HyperFleetConfig CRD (v1alpha1)
1 parent 921cb47 commit 7babfb8

16 files changed

Lines changed: 1160 additions & 114 deletions

PROJECT

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ repo: github.com/openshift-hyperfleet/hyperfleet-operator
1313
resources:
1414
- api:
1515
crdVersion: v1
16-
namespaced: true
16+
namespaced: false
1717
controller: true
1818
domain: redhat.com
1919
group: hyperfleet
2020
kind: HyperFleetConfig
21-
path: github.com/openshift-hyperfleet/hyperfleet-operator/api/v1alpha
22-
version: v1alpha
21+
path: github.com/openshift-hyperfleet/hyperfleet-operator/api/v1alpha1
22+
version: v1alpha1
2323
version: "3"

api/v1alpha/hyperfleetconfig_types.go

Lines changed: 0 additions & 64 deletions
This file was deleted.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package v1alpha contains API Schema definitions for the hyperfleet v1alpha API group.
17+
// Package v1alpha1 contains API Schema definitions for the hyperfleet v1alpha1 API group.
1818
// +kubebuilder:object:generate=true
1919
// +groupName=hyperfleet.redhat.com
20-
package v1alpha
20+
package v1alpha1
2121

2222
import (
2323
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -26,7 +26,7 @@ import (
2626

2727
var (
2828
// GroupVersion is group version used to register these objects.
29-
GroupVersion = schema.GroupVersion{Group: "hyperfleet.redhat.com", Version: "v1alpha"}
29+
GroupVersion = schema.GroupVersion{Group: "hyperfleet.redhat.com", Version: "v1alpha1"}
3030

3131
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
3232
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
Copyright 2026.
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+
// NOTE: json tags are required. Any new fields you add must have json tags for
24+
// the fields to be serialized. Run "make generate manifests" after editing.
25+
26+
// SingletonName is the only permitted name for a HyperFleetConfig. The resource
27+
// is a cluster-scoped singleton: a CEL validation rule pins the name to
28+
// "cluster", and cluster-scoped name uniqueness then guarantees at most one
29+
// instance (a second create is rejected by the API server as AlreadyExists, not
30+
// by CEL, since CEL cannot see other objects). No admission webhooks are used
31+
// (see architecture ADR-0019).
32+
const SingletonName = "cluster"
33+
34+
// BundleType selects one of the operator-internal bundle definitions. It is a
35+
// selector only: the CR carries the choice of a bundle, never its contents. The
36+
// bundle controller (HYPERFLEET-1407) resolves the selected bundle into a
37+
// concrete component set.
38+
//
39+
// These constants are the single source of truth for the valid bundle values
40+
// and MUST stay in lockstep with the bundle definitions shipped inside the
41+
// operator. Adding a bundle means adding a constant here and the matching enum
42+
// value in the +kubebuilder:validation:Enum marker below.
43+
//
44+
// +kubebuilder:validation:Enum=cloud-capi;onprem-agent
45+
type BundleType string
46+
47+
const (
48+
// BundleCloudCAPI is the cloud, CAPI-based provisioning deployment
49+
// (managed OpenShift on a public cloud).
50+
BundleCloudCAPI BundleType = "cloud-capi"
51+
// BundleOnPremAgent is the on-premise, air-gapped (agent-based) deployment.
52+
BundleOnPremAgent BundleType = "onprem-agent"
53+
)
54+
55+
// AllBundleTypes is the Go-level source of truth for the valid BundleType
56+
// values. The +kubebuilder:validation:Enum marker on BundleType must list
57+
// exactly these values; the lockstep guard test creates a HyperFleetConfig with
58+
// each entry and fails if any is not accepted by the CRD, catching drift between
59+
// the constants and the enum marker.
60+
var AllBundleTypes = []BundleType{BundleCloudCAPI, BundleOnPremAgent}
61+
62+
// SizingProfile expresses sizing intent, not replica engineering. The operator
63+
// maps each profile to concrete replicas, resource requests/limits and HPA/PDB
64+
// defaults for the operand.
65+
//
66+
// +kubebuilder:validation:Enum=small;medium;large
67+
type SizingProfile string
68+
69+
const (
70+
// SizingProfileSmall is the default, lowest-footprint sizing profile.
71+
SizingProfileSmall SizingProfile = "small"
72+
// SizingProfileMedium is a mid-range sizing profile.
73+
SizingProfileMedium SizingProfile = "medium"
74+
// SizingProfileLarge is the highest-footprint sizing profile.
75+
SizingProfileLarge SizingProfile = "large"
76+
)
77+
78+
// AllSizingProfiles is the Go-level source of truth for the valid SizingProfile
79+
// values; it must match the +kubebuilder:validation:Enum marker on
80+
// SizingProfile (asserted by the lockstep guard test).
81+
var AllSizingProfiles = []SizingProfile{SizingProfileSmall, SizingProfileMedium, SizingProfileLarge}
82+
83+
// Condition types reported on HyperFleetConfig status. This is deliberate
84+
// operator-layer vocabulary describing installation health, and is distinct from
85+
// the HyperFleet API's own resource-condition vocabulary (Available/Ready/
86+
// Reconciled/LastKnownReconciled/per-adapter; see architecture ADR-0007 and
87+
// ADR-0008). The bundle controller (HYPERFLEET-1409) populates these; this story
88+
// defines the schema only.
89+
const (
90+
// ConditionAvailable is True when the installed operand (the API) is
91+
// deployed and healthy.
92+
ConditionAvailable = "Available"
93+
// ConditionProgressing is True while the operator is actively rolling out a
94+
// change to the operand.
95+
ConditionProgressing = "Progressing"
96+
// ConditionDegraded is True when the operator cannot reach or maintain the
97+
// desired state.
98+
ConditionDegraded = "Degraded"
99+
)
100+
101+
// SecretReference references a Secret by name. Referenced Secrets must live in
102+
// the operator's own namespace: because HyperFleetConfig is cluster-scoped, no
103+
// namespace field is exposed (name-only + operator-namespace convention, decided
104+
// in the HYPERFLEET-1406 API review).
105+
type SecretReference struct {
106+
// name is the name of the Secret in the operator's namespace. It must be a
107+
// valid DNS-1123 subdomain (the same constraint the API server places on
108+
// Secret names) so an unresolvable reference is rejected at admission rather
109+
// than failing opaquely when the reference is later resolved.
110+
//
111+
// +kubebuilder:validation:Required
112+
// +kubebuilder:validation:MinLength=1
113+
// +kubebuilder:validation:MaxLength=253
114+
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
115+
Name string `json:"name"`
116+
}
117+
118+
// DatabaseSpec configures the HyperFleet API's connection to its external
119+
// PostgreSQL database. The database is partner-provided; the operator never
120+
// provisions it.
121+
type DatabaseSpec struct {
122+
// secretRef references a Secret holding the database connection credentials.
123+
// The Secret must provide the keys db.host, db.port, db.name, db.user and
124+
// db.password.
125+
//
126+
// +kubebuilder:validation:Required
127+
SecretRef SecretReference `json:"secretRef"`
128+
}
129+
130+
// AuthSpec configures partner-facing JWT authentication intent for the API.
131+
// Machinery details (JWKS rotation, public-path allowlist) remain
132+
// operator-internal defaults and are not exposed here.
133+
//
134+
// +kubebuilder:validation:XValidation:rule="!self.enabled || (has(self.issuer) && has(self.audience))",message="issuer and audience are required when auth is enabled"
135+
type AuthSpec struct {
136+
// enabled turns JWT authentication on for the API endpoint. It defaults to
137+
// true, so a config that omits it gets authentication ON. It is a pointer to
138+
// distinguish "unset" (apply the default, true) from an explicit false
139+
// (disable auth), which a non-pointer bool cannot express: with omitempty a
140+
// plain false is dropped and re-defaulted to true, so auth could never be
141+
// turned off via the typed client; without omitempty an unset field serializes
142+
// as false and suppresses the default. Only *bool avoids both traps.
143+
//
144+
// +kubebuilder:default=true
145+
// +optional
146+
Enabled *bool `json:"enabled,omitempty"`
147+
148+
// issuer is the OIDC issuer URL that mints accepted tokens. Required when
149+
// enabled is true. Whenever it is set (regardless of enabled) it must be a
150+
// valid https URL with a host, so a malformed issuer is rejected at admission
151+
// rather than surfacing later at token-validation time.
152+
//
153+
// +kubebuilder:validation:MinLength=1
154+
// +kubebuilder:validation:MaxLength=2048
155+
// +kubebuilder:validation:XValidation:rule="isURL(self) && url(self).getScheme() == 'https' && url(self).getHostname() != ''",message="issuer must be a valid https URL"
156+
// +optional
157+
Issuer string `json:"issuer,omitempty"`
158+
159+
// audience is the token audience the API requires. Required and non-empty
160+
// when enabled is true.
161+
//
162+
// +kubebuilder:validation:MinLength=1
163+
// +kubebuilder:validation:MaxLength=253
164+
// +optional
165+
Audience string `json:"audience,omitempty"`
166+
}
167+
168+
// TLSSpec configures TLS for the API endpoint. The certificate material is
169+
// referenced, not described.
170+
type TLSSpec struct {
171+
// secretRef references a kubernetes.io/tls Secret (providing tls.crt and
172+
// tls.key) used to serve the API endpoint.
173+
//
174+
// +kubebuilder:validation:Required
175+
SecretRef SecretReference `json:"secretRef"`
176+
}
177+
178+
// APISpec is the partner-facing configuration for the HyperFleet API component,
179+
// which lives in the shared tier of every bundle.
180+
type APISpec struct {
181+
// database configures the external PostgreSQL connection.
182+
//
183+
// +kubebuilder:validation:Required
184+
Database DatabaseSpec `json:"database"`
185+
186+
// auth configures partner-facing JWT authentication intent.
187+
//
188+
// +kubebuilder:validation:Required
189+
Auth AuthSpec `json:"auth"`
190+
191+
// tls optionally configures TLS for the API endpoint. When omitted, the
192+
// operator applies its default serving configuration.
193+
//
194+
// +optional
195+
TLS *TLSSpec `json:"tls,omitempty"`
196+
197+
// profile selects a sizing profile for the API. Defaults to "small".
198+
//
199+
// +kubebuilder:default=small
200+
// +optional
201+
Profile SizingProfile `json:"profile,omitempty"`
202+
}
203+
204+
// HyperFleetConfigSpec defines the desired state of HyperFleetConfig. It captures
205+
// partner intent only; internal machinery (broker, adapters, Sentinel) is never
206+
// expressed here.
207+
type HyperFleetConfigSpec struct {
208+
// bundle selects one of the operator-internal bundle definitions. It is
209+
// immutable after creation: switching deployments requires recreating the
210+
// resource.
211+
//
212+
// +kubebuilder:validation:Required
213+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="bundle is immutable"
214+
Bundle BundleType `json:"bundle"`
215+
216+
// api is the partner-facing configuration for the HyperFleet API component.
217+
//
218+
// +kubebuilder:validation:Required
219+
API APISpec `json:"api"`
220+
}
221+
222+
// HyperFleetConfigStatus defines the observed state of HyperFleetConfig. It is
223+
// populated by the bundle controller in later stories; this story defines the
224+
// schema only.
225+
type HyperFleetConfigStatus struct {
226+
// observedGeneration is the .metadata.generation the operator last acted on.
227+
//
228+
// +kubebuilder:validation:Minimum=0
229+
// +optional
230+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
231+
232+
// conditions represent the current installation health of the operand.
233+
// Recognized types are Available, Progressing and Degraded.
234+
//
235+
// +listType=map
236+
// +listMapKey=type
237+
// +optional
238+
Conditions []metav1.Condition `json:"conditions,omitempty"`
239+
}
240+
241+
// +kubebuilder:object:root=true
242+
// +kubebuilder:subresource:status
243+
// +kubebuilder:resource:scope=Cluster,shortName=hfc
244+
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="the only permitted name is 'cluster'; HyperFleetConfig is a cluster-scoped singleton"
245+
// +kubebuilder:printcolumn:name="Bundle",type=string,JSONPath=`.spec.bundle`
246+
// +kubebuilder:printcolumn:name="Profile",type=string,JSONPath=`.spec.api.profile`
247+
// +kubebuilder:printcolumn:name="Available",type=string,JSONPath=`.status.conditions[?(@.type=="Available")].status`
248+
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
249+
250+
// HyperFleetConfig is the Schema for the hyperfleetconfigs API. It is a
251+
// cluster-scoped singleton: exactly one instance, named "cluster", is permitted.
252+
type HyperFleetConfig struct {
253+
metav1.TypeMeta `json:",inline"`
254+
metav1.ObjectMeta `json:"metadata,omitempty"`
255+
256+
// +kubebuilder:validation:Required
257+
Spec HyperFleetConfigSpec `json:"spec"`
258+
Status HyperFleetConfigStatus `json:"status,omitempty"`
259+
}
260+
261+
// +kubebuilder:object:root=true
262+
263+
// HyperFleetConfigList contains a list of HyperFleetConfig.
264+
type HyperFleetConfigList struct {
265+
metav1.TypeMeta `json:",inline"`
266+
metav1.ListMeta `json:"metadata,omitempty"`
267+
Items []HyperFleetConfig `json:"items"`
268+
}
269+
270+
func init() {
271+
SchemeBuilder.Register(&HyperFleetConfig{}, &HyperFleetConfigList{})
272+
}

0 commit comments

Comments
 (0)