Skip to content

Commit 6dfd0c7

Browse files
committed
feat: add CEL validation rules to MemberCluster taint API types
Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
1 parent 4d96d72 commit 6dfd0c7

7 files changed

Lines changed: 1272 additions & 3 deletions

File tree

apis/cluster/v1/membercluster_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type MemberClusterSpec struct {
7171
//
7272
// This field is beta-level and is for the taints and tolerations feature.
7373
// +kubebuilder:validation:MaxItems=100
74+
// +kubebuilder:validation:XValidation:rule="self.all(t, self.filter(u, u.key == t.key && (has(u.value) ? u.value : '') == (has(t.value) ? t.value : '') && u.effect == t.effect).size() == 1)",message="taints must be unique"
7475
// +optional
7576
Taints []Taint `json:"taints,omitempty"`
7677
}
@@ -136,12 +137,21 @@ type MemberClusterStatus struct {
136137

137138
// Taint attached to MemberCluster has the "effect" on
138139
// any ClusterResourcePlacement that does not tolerate the Taint.
140+
// +kubebuilder:validation:XValidation:rule="(self.key.contains('/') ? self.key.substring(self.key.indexOf('/') + 1) : self.key).matches('^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$')",message="taint key name segment must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
141+
// +kubebuilder:validation:XValidation:rule="!self.key.contains('/') || self.key.substring(0, self.key.indexOf('/')).split('.').all(label, label.matches('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$') && label.size() <= 63)",message="taint key prefix must be a lowercase DNS subdomain"
142+
// +kubebuilder:validation:XValidation:rule="(self.key.contains('/') ? self.key.size() - self.key.indexOf('/') - 1 : self.key.size()) <= 63",message="taint key name segment must be 63 characters or less"
143+
// +kubebuilder:validation:XValidation:rule="!self.key.contains('/') || self.key.indexOf('/') <= 253",message="taint key prefix must be 253 characters or less"
144+
// +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="taint value must be a valid label value"
139145
type Taint struct {
140146
// The taint key to be applied to a MemberCluster.
147+
// MaxLength is 253 (prefix) + 1 (slash) + 63 (name segment) = 317.
148+
// +kubebuilder:validation:MaxLength=317
149+
// +kubebuilder:validation:MinLength=1
141150
// +required
142151
Key string `json:"key"`
143152

144153
// The taint value corresponding to the taint key.
154+
// +kubebuilder:validation:MaxLength=63
145155
// +optional
146156
Value string `json:"value,omitempty"`
147157

apis/cluster/v1beta1/membercluster_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type MemberClusterSpec struct {
7272
//
7373
// This field is beta-level and is for the taints and tolerations feature.
7474
// +kubebuilder:validation:MaxItems=100
75+
// +kubebuilder:validation:XValidation:rule="self.all(t, self.filter(u, u.key == t.key && (has(u.value) ? u.value : '') == (has(t.value) ? t.value : '') && u.effect == t.effect).size() == 1)",message="taints must be unique"
7576
// +optional
7677
Taints []Taint `json:"taints,omitempty"`
7778

@@ -156,12 +157,21 @@ type MemberClusterStatus struct {
156157

157158
// Taint attached to MemberCluster has the "effect" on
158159
// any ClusterResourcePlacement that does not tolerate the Taint.
160+
// +kubebuilder:validation:XValidation:rule="(self.key.contains('/') ? self.key.substring(self.key.indexOf('/') + 1) : self.key).matches('^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$')",message="taint key name segment must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
161+
// +kubebuilder:validation:XValidation:rule="!self.key.contains('/') || self.key.substring(0, self.key.indexOf('/')).split('.').all(label, label.matches('^[a-z0-9]([a-z0-9-]*[a-z0-9])?$') && label.size() <= 63)",message="taint key prefix must be a lowercase DNS subdomain"
162+
// +kubebuilder:validation:XValidation:rule="(self.key.contains('/') ? self.key.size() - self.key.indexOf('/') - 1 : self.key.size()) <= 63",message="taint key name segment must be 63 characters or less"
163+
// +kubebuilder:validation:XValidation:rule="!self.key.contains('/') || self.key.indexOf('/') <= 253",message="taint key prefix must be 253 characters or less"
164+
// +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="taint value must be a valid label value"
159165
type Taint struct {
160166
// The taint key to be applied to a MemberCluster.
167+
// MaxLength is 253 (prefix) + 1 (slash) + 63 (name segment) = 317.
168+
// +kubebuilder:validation:MaxLength=317
169+
// +kubebuilder:validation:MinLength=1
161170
// +required
162171
Key string `json:"key"`
163172

164173
// The taint value corresponding to the taint key.
174+
// +kubebuilder:validation:MaxLength=63
165175
// +optional
166176
Value string `json:"value,omitempty"`
167177

config/crd/bases/cluster.kubernetes-fleet.io_memberclusters.yaml

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,45 @@ spec:
128128
- NoSchedule
129129
type: string
130130
key:
131-
description: The taint key to be applied to a MemberCluster.
131+
description: |-
132+
The taint key to be applied to a MemberCluster.
133+
MaxLength is 253 (prefix) + 1 (slash) + 63 (name segment) = 317.
134+
maxLength: 317
135+
minLength: 1
132136
type: string
133137
value:
134138
description: The taint value corresponding to the taint key.
139+
maxLength: 63
135140
type: string
136141
required:
137142
- effect
138143
- key
139144
type: object
145+
x-kubernetes-validations:
146+
- message: taint key name segment must consist of alphanumeric characters,
147+
'-', '_' or '.', and must start and end with an alphanumeric
148+
character
149+
rule: '(self.key.contains(''/'') ? self.key.substring(self.key.indexOf(''/'')
150+
+ 1) : self.key).matches(''^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$'')'
151+
- message: taint key prefix must be a lowercase DNS subdomain
152+
rule: '!self.key.contains(''/'') || self.key.substring(0, self.key.indexOf(''/'')).split(''.'').all(label,
153+
label.matches(''^[a-z0-9]([a-z0-9-]*[a-z0-9])?$'') && label.size()
154+
<= 63)'
155+
- message: taint key name segment must be 63 characters or less
156+
rule: '(self.key.contains(''/'') ? self.key.size() - self.key.indexOf(''/'')
157+
- 1 : self.key.size()) <= 63'
158+
- message: taint key prefix must be 253 characters or less
159+
rule: '!self.key.contains(''/'') || self.key.indexOf(''/'') <=
160+
253'
161+
- message: taint value must be a valid label value
162+
rule: '!has(self.value) || size(self.value) == 0 || self.value.matches(''^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$'')'
140163
maxItems: 100
141164
type: array
165+
x-kubernetes-validations:
166+
- message: taints must be unique
167+
rule: 'self.all(t, self.filter(u, u.key == t.key && (has(u.value)
168+
? u.value : '''') == (has(t.value) ? t.value : '''') && u.effect
169+
== t.effect).size() == 1)'
142170
required:
143171
- identity
144172
type: object
@@ -500,17 +528,45 @@ spec:
500528
- NoSchedule
501529
type: string
502530
key:
503-
description: The taint key to be applied to a MemberCluster.
531+
description: |-
532+
The taint key to be applied to a MemberCluster.
533+
MaxLength is 253 (prefix) + 1 (slash) + 63 (name segment) = 317.
534+
maxLength: 317
535+
minLength: 1
504536
type: string
505537
value:
506538
description: The taint value corresponding to the taint key.
539+
maxLength: 63
507540
type: string
508541
required:
509542
- effect
510543
- key
511544
type: object
545+
x-kubernetes-validations:
546+
- message: taint key name segment must consist of alphanumeric characters,
547+
'-', '_' or '.', and must start and end with an alphanumeric
548+
character
549+
rule: '(self.key.contains(''/'') ? self.key.substring(self.key.indexOf(''/'')
550+
+ 1) : self.key).matches(''^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$'')'
551+
- message: taint key prefix must be a lowercase DNS subdomain
552+
rule: '!self.key.contains(''/'') || self.key.substring(0, self.key.indexOf(''/'')).split(''.'').all(label,
553+
label.matches(''^[a-z0-9]([a-z0-9-]*[a-z0-9])?$'') && label.size()
554+
<= 63)'
555+
- message: taint key name segment must be 63 characters or less
556+
rule: '(self.key.contains(''/'') ? self.key.size() - self.key.indexOf(''/'')
557+
- 1 : self.key.size()) <= 63'
558+
- message: taint key prefix must be 253 characters or less
559+
rule: '!self.key.contains(''/'') || self.key.indexOf(''/'') <=
560+
253'
561+
- message: taint value must be a valid label value
562+
rule: '!has(self.value) || size(self.value) == 0 || self.value.matches(''^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$'')'
512563
maxItems: 100
513564
type: array
565+
x-kubernetes-validations:
566+
- message: taints must be unique
567+
rule: 'self.all(t, self.filter(u, u.key == t.key && (has(u.value)
568+
? u.value : '''') == (has(t.value) ? t.value : '''') && u.effect
569+
== t.effect).size() == 1)'
514570
required:
515571
- identity
516572
type: object

pkg/utils/validator/membercluster_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ func TestValidateTaints(t *testing.T) {
6666
wantErr: true,
6767
wantErrMsg: "taints must be unique",
6868
},
69+
"valid taints, same key and effect with different values": {
70+
taints: []clusterv1beta1.Taint{
71+
{
72+
Key: "key1",
73+
Value: "value1",
74+
Effect: "NoSchedule",
75+
},
76+
{
77+
Key: "key1",
78+
Value: "value2",
79+
Effect: "NoSchedule",
80+
},
81+
},
82+
wantErr: false,
83+
},
6984
"valid taints": {
7085
taints: []clusterv1beta1.Taint{
7186
{

0 commit comments

Comments
 (0)