Skip to content

Commit 0ce6378

Browse files
committed
feat: add CEL validation rules to CRP API types
Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
1 parent 2d6f9e5 commit 0ce6378

21 files changed

Lines changed: 3103 additions & 41 deletions

apis/placement/v1/clusterresourceplacement_types.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,15 @@ import (
5151
//
5252
// `ClusterSchedulingPolicySnapshot` and `ClusterResourceSnapshot` objects are created when there are changes in the
5353
// system to keep the history of the changes affecting a `ClusterResourcePlacement`.
54+
// +kubebuilder:validation:XValidation:rule="size(self.metadata.name) <= 63",message="name must not exceed 63 characters"
5455
type ClusterResourcePlacement struct {
5556
metav1.TypeMeta `json:",inline"`
5657
metav1.ObjectMeta `json:"metadata,omitempty"`
5758

5859
// The desired state of ClusterResourcePlacement.
5960
// +required
61+
// +kubebuilder:validation:XValidation:rule="!(has(oldSelf.policy) && !has(self.policy) && oldSelf.policy.placementType != 'PickAll')",message="policy cannot be removed once set"
62+
// +kubebuilder:validation:XValidation:rule="has(oldSelf.policy) || !has(self.policy) || self.policy.placementType == 'PickAll'",message="placement type is immutable"
6063
Spec ClusterResourcePlacementSpec `json:"spec"`
6164

6265
// The observed status of ClusterResourcePlacement.
@@ -77,6 +80,7 @@ type ClusterResourcePlacementSpec struct {
7780
// Policy defines how to select member clusters to place the selected resources.
7881
// If unspecified, all the joined member clusters are selected.
7982
// +optional
83+
// +kubebuilder:validation:XValidation:rule="self.placementType == oldSelf.placementType",message="placement type is immutable"
8084
Policy *PlacementPolicy `json:"policy,omitempty"`
8185

8286
// The rollout strategy to use to replace existing placement with new ones.
@@ -97,6 +101,7 @@ type ClusterResourcePlacementSpec struct {
97101
// ClusterResourceSelector is used to select cluster scoped resources as the target resources to be placed.
98102
// If a namespace is selected, ALL the resources under the namespace are selected automatically.
99103
// All the fields are `ANDed`. In other words, a resource must match all the fields to be selected.
104+
// +kubebuilder:validation:XValidation:rule="!has(self.labelSelector) || !has(self.name) || size(self.name) == 0",message="labelSelector and name are mutually exclusive"
100105
type ClusterResourceSelector struct {
101106
// Group name of the cluster-scoped resource.
102107
// Use an empty string to select resources under the core API group (e.g., namespaces).
@@ -130,6 +135,19 @@ type ClusterResourceSelector struct {
130135
//
131136
// You can only specify at most one of the two fields: ClusterNames and Affinity.
132137
// If none is specified, all the joined clusters are selected.
138+
//
139+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickFixed' || (has(self.clusterNames) && size(self.clusterNames) > 0)",message="clusterNames cannot be empty for PickFixed placement type"
140+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickFixed' || !has(self.numberOfClusters)",message="numberOfClusters must not be set for PickFixed placement type"
141+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickFixed' || !has(self.affinity)",message="affinity must not be set for PickFixed placement type"
142+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickFixed' || !has(self.topologySpreadConstraints) || size(self.topologySpreadConstraints) == 0",message="topologySpreadConstraints must be empty for PickFixed placement type"
143+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickFixed' || !has(self.tolerations) || size(self.tolerations) == 0",message="tolerations must be empty for PickFixed placement type"
144+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickAll' || !has(self.clusterNames) || size(self.clusterNames) == 0",message="clusterNames must be empty for PickAll placement type"
145+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickAll' || !has(self.numberOfClusters)",message="numberOfClusters must not be set for PickAll placement type"
146+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickAll' || !has(self.topologySpreadConstraints) || size(self.topologySpreadConstraints) == 0",message="topologySpreadConstraints must be empty for PickAll placement type"
147+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickAll' || !has(self.affinity) || !has(self.affinity.clusterAffinity) || !has(self.affinity.clusterAffinity.preferredDuringSchedulingIgnoredDuringExecution) || size(self.affinity.clusterAffinity.preferredDuringSchedulingIgnoredDuringExecution) == 0",message="preferredDuringSchedulingIgnoredDuringExecution is not allowed for PickAll placement type"
148+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickN' || !has(self.clusterNames) || size(self.clusterNames) == 0",message="clusterNames must be empty for PickN placement type"
149+
// +kubebuilder:validation:XValidation:rule="self.placementType != 'PickN' || has(self.numberOfClusters)",message="numberOfClusters must be set for PickN placement type"
150+
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.tolerations) || oldSelf.tolerations.all(t, has(self.tolerations) && self.tolerations.exists(nt, nt == t))",message="tolerations have been updated/deleted, only additions to tolerations are allowed"
133151
type PlacementPolicy struct {
134152
// Type of placement. Can be "PickAll", "PickN" or "PickFixed". Default is PickAll.
135153
// +kubebuilder:validation:Enum=PickAll;PickN;PickFixed
@@ -138,6 +156,7 @@ type PlacementPolicy struct {
138156
PlacementType PlacementType `json:"placementType,omitempty"`
139157

140158
// +kubebuilder:validation:MaxItems=100
159+
// +kubebuilder:validation:items:MaxLength=63
141160
// ClusterNames contains a list of names of MemberCluster to place the selected resources.
142161
// Only valid if the placement type is "PickFixed"
143162
// +optional
@@ -200,13 +219,15 @@ type ClusterAffinity struct {
200219
PreferredDuringSchedulingIgnoredDuringExecution []PreferredClusterSelector `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"`
201220
}
202221

222+
// +kubebuilder:validation:XValidation:rule="self.clusterSelectorTerms.all(t, !has(t.propertySorter))",message="propertySorter is not allowed in requiredDuringSchedulingIgnoredDuringExecution affinity terms"
203223
type ClusterSelector struct {
204224
// +kubebuilder:validation:MaxItems=10
205225
// ClusterSelectorTerms is a list of cluster selector terms. The terms are `ORed`.
206226
// +required
207227
ClusterSelectorTerms []ClusterSelectorTerm `json:"clusterSelectorTerms"`
208228
}
209229

230+
// +kubebuilder:validation:XValidation:rule="!has(self.preference.propertySelector)",message="propertySelector is not allowed in preferredDuringSchedulingIgnoredDuringExecution affinity terms"
210231
type PreferredClusterSelector struct {
211232
// Weight associated with matching the corresponding clusterSelectorTerm, in the range [-100, 100].
212233
// +required
@@ -286,6 +307,7 @@ const (
286307
type PropertySelectorRequirement struct {
287308
// Name is the name of the property; it should be a Kubernetes label name.
288309
// +required
310+
// +kubebuilder:validation:MaxLength=317
289311
Name string `json:"name"`
290312

291313
// Operator specifies the relationship between a cluster's observed value of the specified
@@ -304,6 +326,7 @@ type PropertySelectorRequirement struct {
304326
// or `Le` (less than or equal to), Eq (equal to), or Ne (ne), exactly one value must be
305327
// specified in the list.
306328
//
329+
// +kubebuilder:validation:MinItems=1
307330
// +kubebuilder:validation:MaxItems=1
308331
// +required
309332
Values []string `json:"values"`
@@ -321,10 +344,12 @@ type PropertySelector struct {
321344
type PropertySorter struct {
322345
// Name is the name of the property which Fleet sorts clusters by.
323346
// +required
347+
// +kubebuilder:validation:MaxLength=317
324348
Name string `json:"name"`
325349

326350
// SortOrder explains how Fleet should perform the sort; specifically, whether Fleet should
327351
// sort in ascending or descending order.
352+
// +kubebuilder:validation:Enum=Descending;Ascending
328353
// +required
329354
SortOrder PropertySortOrder `json:"sortOrder"`
330355
}
@@ -391,6 +416,7 @@ type TopologySpreadConstraint struct {
391416
// but giving higher precedence to topologies that would help reduce the skew.
392417
// It's an optional field.
393418
// +optional
419+
// +kubebuilder:validation:Enum=DoNotSchedule;ScheduleAnyway
394420
WhenUnsatisfiable UnsatisfiableConstraintAction `json:"whenUnsatisfiable,omitempty"`
395421
}
396422

@@ -431,6 +457,7 @@ type RolloutStrategy struct {
431457
// and whether it's allowed to be co-owned by other non-fleet appliers.
432458
// Note: If multiple CRPs try to place the same resource with different apply strategy, the later ones will fail with the
433459
// reason ApplyConflictBetweenPlacements.
460+
// +kubebuilder:validation:XValidation:rule="self.type == 'ServerSideApply' || !has(self.serverSideApplyConfig)",message="serverSideApplyConfig is only valid for ServerSideApply strategy type"
434461
type ApplyStrategy struct {
435462
// ComparisonOption controls how Fleet compares the desired state of a resource, as kept in
436463
// a hub cluster manifest, with the current state of the resource (if applicable) in the
@@ -793,6 +820,7 @@ type RollingUpdateConfig struct {
793820
// have passed since they were successfully applied to the target cluster.
794821
// Default is 60.
795822
// +kubebuilder:default=60
823+
// +kubebuilder:validation:Minimum=0
796824
// +optional
797825
UnavailablePeriodSeconds *int `json:"unavailablePeriodSeconds,omitempty"`
798826
}
@@ -1052,9 +1080,15 @@ type DiffedResourcePlacement struct {
10521080

10531081
// Toleration allows ClusterResourcePlacement to tolerate any taint that matches
10541082
// the triple <key,value,effect> using the matching operator <operator>.
1083+
// +kubebuilder:validation:XValidation:rule="self.operator != 'Exists' || !has(self.value) || size(self.value) == 0",message="value must be empty when operator is Exists"
1084+
// +kubebuilder:validation:XValidation:rule="self.operator != 'Equal' || (has(self.key) && size(self.key) > 0)",message="key must not be empty when operator is Equal"
1085+
// +kubebuilder:validation:XValidation:rule="!has(self.key) || size(self.key) == 0 || self.key.matches('^([a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*[/])?[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$')",message="toleration key must be a valid qualified name"
1086+
// +kubebuilder:validation:XValidation:rule="!has(self.key) || size(self.key) == 0 || self.key.matches('^([^/]+/)?[a-zA-Z0-9]([a-zA-Z0-9._-]{0,61}[a-zA-Z0-9])?$')",message="toleration key name segment must not exceed 63 characters"
1087+
// +kubebuilder:validation:XValidation:rule="!has(self.value) || size(self.value) == 0 || self.value.matches('^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$')",message="toleration value must be a valid label value"
10551088
type Toleration struct {
10561089
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
10571090
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
1091+
// +kubebuilder:validation:MaxLength=317
10581092
// +optional
10591093
Key string `json:"key,omitempty"`
10601094

@@ -1069,6 +1103,7 @@ type Toleration struct {
10691103

10701104
// Value is the taint value the toleration matches to.
10711105
// If the operator is Exists, the value should be empty, otherwise just a regular string.
1106+
// +kubebuilder:validation:MaxLength=63
10721107
// +optional
10731108
Value string `json:"value,omitempty"`
10741109

0 commit comments

Comments
 (0)