@@ -17,9 +17,13 @@ limitations under the License.
17
17
package v1beta1
18
18
19
19
import (
20
+ "fmt"
21
+
20
22
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23
+ "k8s.io/apimachinery/pkg/util/validation/field"
21
24
22
25
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
26
+ "sigs.k8s.io/cluster-api/feature"
23
27
)
24
28
25
29
// Format specifies the output format of the bootstrap data
@@ -34,6 +38,16 @@ const (
34
38
Ignition Format = "ignition"
35
39
)
36
40
41
+ var (
42
+ cannotUseWithIgnition = fmt .Sprintf ("not supported when spec.format is set to: %q" , Ignition )
43
+ conflictingFileSourceMsg = "only one of content or contentFrom may be specified for a single file"
44
+ conflictingUserSourceMsg = "only one of passwd or passwdFrom may be specified for a single user"
45
+ kubeadmBootstrapFormatIgnitionFeatureDisabledMsg = "can be set only if the KubeadmBootstrapFormatIgnition feature gate is enabled"
46
+ missingSecretNameMsg = "secret file source must specify non-empty secret name"
47
+ missingSecretKeyMsg = "secret file source must specify non-empty secret key"
48
+ pathConflictMsg = "path property must be unique among all files"
49
+ )
50
+
37
51
// KubeadmConfigSpec defines the desired state of KubeadmConfig.
38
52
// Either ClusterConfiguration and InitConfiguration should be defined or the JoinConfiguration should be defined.
39
53
type KubeadmConfigSpec struct {
@@ -107,6 +121,242 @@ type KubeadmConfigSpec struct {
107
121
Ignition * IgnitionSpec `json:"ignition,omitempty"`
108
122
}
109
123
124
+ // Default defaults a KubeadmConfigSpec.
125
+ func (c * KubeadmConfigSpec ) Default () {
126
+ if c .Format == "" {
127
+ c .Format = CloudConfig
128
+ }
129
+ if c .InitConfiguration != nil && c .InitConfiguration .NodeRegistration .ImagePullPolicy == "" {
130
+ c .InitConfiguration .NodeRegistration .ImagePullPolicy = "IfNotPresent"
131
+ }
132
+ if c .JoinConfiguration != nil && c .JoinConfiguration .NodeRegistration .ImagePullPolicy == "" {
133
+ c .JoinConfiguration .NodeRegistration .ImagePullPolicy = "IfNotPresent"
134
+ }
135
+ }
136
+
137
+ // Validate ensures the KubeadmConfigSpec is valid.
138
+ func (c * KubeadmConfigSpec ) Validate (pathPrefix * field.Path ) field.ErrorList {
139
+ var allErrs field.ErrorList
140
+
141
+ allErrs = append (allErrs , c .validateFiles (pathPrefix )... )
142
+ allErrs = append (allErrs , c .validateUsers (pathPrefix )... )
143
+ allErrs = append (allErrs , c .validateIgnition (pathPrefix )... )
144
+
145
+ return allErrs
146
+ }
147
+
148
+ func (c * KubeadmConfigSpec ) validateFiles (pathPrefix * field.Path ) field.ErrorList {
149
+ var allErrs field.ErrorList
150
+
151
+ knownPaths := map [string ]struct {}{}
152
+
153
+ for i := range c .Files {
154
+ file := c .Files [i ]
155
+ if file .Content != "" && file .ContentFrom != nil {
156
+ allErrs = append (
157
+ allErrs ,
158
+ field .Invalid (
159
+ pathPrefix .Child ("files" ).Index (i ),
160
+ file ,
161
+ conflictingFileSourceMsg ,
162
+ ),
163
+ )
164
+ }
165
+ // n.b.: if we ever add types besides Secret as a ContentFrom
166
+ // Source, we must add webhook validation here for one of the
167
+ // sources being non-nil.
168
+ if file .ContentFrom != nil {
169
+ if file .ContentFrom .Secret .Name == "" {
170
+ allErrs = append (
171
+ allErrs ,
172
+ field .Required (
173
+ pathPrefix .Child ("files" ).Index (i ).Child ("contentFrom" , "secret" , "name" ),
174
+ missingSecretNameMsg ,
175
+ ),
176
+ )
177
+ }
178
+ if file .ContentFrom .Secret .Key == "" {
179
+ allErrs = append (
180
+ allErrs ,
181
+ field .Required (
182
+ pathPrefix .Child ("files" ).Index (i ).Child ("contentFrom" , "secret" , "key" ),
183
+ missingSecretKeyMsg ,
184
+ ),
185
+ )
186
+ }
187
+ }
188
+ _ , conflict := knownPaths [file .Path ]
189
+ if conflict {
190
+ allErrs = append (
191
+ allErrs ,
192
+ field .Invalid (
193
+ pathPrefix .Child ("files" ).Index (i ).Child ("path" ),
194
+ file ,
195
+ pathConflictMsg ,
196
+ ),
197
+ )
198
+ }
199
+ knownPaths [file .Path ] = struct {}{}
200
+ }
201
+
202
+ return allErrs
203
+ }
204
+
205
+ func (c * KubeadmConfigSpec ) validateUsers (pathPrefix * field.Path ) field.ErrorList {
206
+ var allErrs field.ErrorList
207
+
208
+ for i := range c .Users {
209
+ user := c .Users [i ]
210
+ if user .Passwd != nil && user .PasswdFrom != nil {
211
+ allErrs = append (
212
+ allErrs ,
213
+ field .Invalid (
214
+ pathPrefix .Child ("users" ).Index (i ),
215
+ user ,
216
+ conflictingUserSourceMsg ,
217
+ ),
218
+ )
219
+ }
220
+ // n.b.: if we ever add types besides Secret as a PasswdFrom
221
+ // Source, we must add webhook validation here for one of the
222
+ // sources being non-nil.
223
+ if user .PasswdFrom != nil {
224
+ if user .PasswdFrom .Secret .Name == "" {
225
+ allErrs = append (
226
+ allErrs ,
227
+ field .Required (
228
+ pathPrefix .Child ("users" ).Index (i ).Child ("passwdFrom" , "secret" , "name" ),
229
+ missingSecretNameMsg ,
230
+ ),
231
+ )
232
+ }
233
+ if user .PasswdFrom .Secret .Key == "" {
234
+ allErrs = append (
235
+ allErrs ,
236
+ field .Required (
237
+ pathPrefix .Child ("users" ).Index (i ).Child ("passwdFrom" , "secret" , "key" ),
238
+ missingSecretKeyMsg ,
239
+ ),
240
+ )
241
+ }
242
+ }
243
+ }
244
+
245
+ return allErrs
246
+ }
247
+
248
+ func (c * KubeadmConfigSpec ) validateIgnition (pathPrefix * field.Path ) field.ErrorList {
249
+ var allErrs field.ErrorList
250
+
251
+ if ! feature .Gates .Enabled (feature .KubeadmBootstrapFormatIgnition ) {
252
+ if c .Format == Ignition {
253
+ allErrs = append (allErrs , field .Forbidden (
254
+ pathPrefix .Child ("format" ), kubeadmBootstrapFormatIgnitionFeatureDisabledMsg ))
255
+ }
256
+
257
+ if c .Ignition != nil {
258
+ allErrs = append (allErrs , field .Forbidden (
259
+ pathPrefix .Child ("ignition" ), kubeadmBootstrapFormatIgnitionFeatureDisabledMsg ))
260
+ }
261
+
262
+ return allErrs
263
+ }
264
+
265
+ if c .Format != Ignition {
266
+ if c .Ignition != nil {
267
+ allErrs = append (
268
+ allErrs ,
269
+ field .Invalid (
270
+ pathPrefix .Child ("format" ),
271
+ c .Format ,
272
+ fmt .Sprintf ("must be set to %q if spec.ignition is set" , Ignition ),
273
+ ),
274
+ )
275
+ }
276
+
277
+ return allErrs
278
+ }
279
+
280
+ for i , user := range c .Users {
281
+ if user .Inactive != nil && * user .Inactive {
282
+ allErrs = append (
283
+ allErrs ,
284
+ field .Forbidden (
285
+ pathPrefix .Child ("users" ).Index (i ).Child ("inactive" ),
286
+ cannotUseWithIgnition ,
287
+ ),
288
+ )
289
+ }
290
+ }
291
+
292
+ if c .UseExperimentalRetryJoin {
293
+ allErrs = append (
294
+ allErrs ,
295
+ field .Forbidden (
296
+ pathPrefix .Child ("useExperimentalRetryJoin" ),
297
+ cannotUseWithIgnition ,
298
+ ),
299
+ )
300
+ }
301
+
302
+ for i , file := range c .Files {
303
+ if file .Encoding == Gzip || file .Encoding == GzipBase64 {
304
+ allErrs = append (
305
+ allErrs ,
306
+ field .Forbidden (
307
+ pathPrefix .Child ("files" ).Index (i ).Child ("encoding" ),
308
+ cannotUseWithIgnition ,
309
+ ),
310
+ )
311
+ }
312
+ }
313
+
314
+ if c .DiskSetup == nil {
315
+ return allErrs
316
+ }
317
+
318
+ for i , partition := range c .DiskSetup .Partitions {
319
+ if partition .TableType != nil && * partition .TableType != "gpt" {
320
+ allErrs = append (
321
+ allErrs ,
322
+ field .Invalid (
323
+ pathPrefix .Child ("diskSetup" , "partitions" ).Index (i ).Child ("tableType" ),
324
+ * partition .TableType ,
325
+ fmt .Sprintf (
326
+ "only partition type %q is supported when spec.format is set to %q" ,
327
+ "gpt" ,
328
+ Ignition ,
329
+ ),
330
+ ),
331
+ )
332
+ }
333
+ }
334
+
335
+ for i , fs := range c .DiskSetup .Filesystems {
336
+ if fs .ReplaceFS != nil {
337
+ allErrs = append (
338
+ allErrs ,
339
+ field .Forbidden (
340
+ pathPrefix .Child ("diskSetup" , "filesystems" ).Index (i ).Child ("replaceFS" ),
341
+ cannotUseWithIgnition ,
342
+ ),
343
+ )
344
+ }
345
+
346
+ if fs .Partition != nil {
347
+ allErrs = append (
348
+ allErrs ,
349
+ field .Forbidden (
350
+ pathPrefix .Child ("diskSetup" , "filesystems" ).Index (i ).Child ("partition" ),
351
+ cannotUseWithIgnition ,
352
+ ),
353
+ )
354
+ }
355
+ }
356
+
357
+ return allErrs
358
+ }
359
+
110
360
// IgnitionSpec contains Ignition specific configuration.
111
361
type IgnitionSpec struct {
112
362
// ContainerLinuxConfig contains CLC specific configuration.
0 commit comments