sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 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 v1beta1 18 19 import ( 20 "fmt" 21 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/util/validation/field" 24 25 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 26 "sigs.k8s.io/cluster-api/feature" 27 ) 28 29 // Format specifies the output format of the bootstrap data 30 // +kubebuilder:validation:Enum=cloud-config;ignition 31 type Format string 32 33 const ( 34 // CloudConfig make the bootstrap data to be of cloud-config format. 35 CloudConfig Format = "cloud-config" 36 37 // Ignition make the bootstrap data to be of Ignition format. 38 Ignition Format = "ignition" 39 ) 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 51 // KubeadmConfigSpec defines the desired state of KubeadmConfig. 52 // Either ClusterConfiguration and InitConfiguration should be defined or the JoinConfiguration should be defined. 53 type KubeadmConfigSpec struct { 54 // ClusterConfiguration along with InitConfiguration are the configurations necessary for the init command 55 // +optional 56 ClusterConfiguration *ClusterConfiguration `json:"clusterConfiguration,omitempty"` 57 58 // InitConfiguration along with ClusterConfiguration are the configurations necessary for the init command 59 // +optional 60 InitConfiguration *InitConfiguration `json:"initConfiguration,omitempty"` 61 62 // JoinConfiguration is the kubeadm configuration for the join command 63 // +optional 64 JoinConfiguration *JoinConfiguration `json:"joinConfiguration,omitempty"` 65 66 // Files specifies extra files to be passed to user_data upon creation. 67 // +optional 68 Files []File `json:"files,omitempty"` 69 70 // DiskSetup specifies options for the creation of partition tables and file systems on devices. 71 // +optional 72 DiskSetup *DiskSetup `json:"diskSetup,omitempty"` 73 74 // Mounts specifies a list of mount points to be setup. 75 // +optional 76 Mounts []MountPoints `json:"mounts,omitempty"` 77 78 // PreKubeadmCommands specifies extra commands to run before kubeadm runs 79 // +optional 80 PreKubeadmCommands []string `json:"preKubeadmCommands,omitempty"` 81 82 // PostKubeadmCommands specifies extra commands to run after kubeadm runs 83 // +optional 84 PostKubeadmCommands []string `json:"postKubeadmCommands,omitempty"` 85 86 // Users specifies extra users to add 87 // +optional 88 Users []User `json:"users,omitempty"` 89 90 // NTP specifies NTP configuration 91 // +optional 92 NTP *NTP `json:"ntp,omitempty"` 93 94 // Format specifies the output format of the bootstrap data 95 // +optional 96 Format Format `json:"format,omitempty"` 97 98 // Verbosity is the number for the kubeadm log level verbosity. 99 // It overrides the `--v` flag in kubeadm commands. 100 // +optional 101 Verbosity *int32 `json:"verbosity,omitempty"` 102 103 // UseExperimentalRetryJoin replaces a basic kubeadm command with a shell 104 // script with retries for joins. 105 // 106 // This is meant to be an experimental temporary workaround on some environments 107 // where joins fail due to timing (and other issues). The long term goal is to add retries to 108 // kubeadm proper and use that functionality. 109 // 110 // This will add about 40KB to userdata 111 // 112 // For more information, refer to https://github.com/kubernetes-sigs/cluster-api/pull/2763#discussion_r397306055. 113 // +optional 114 // 115 // Deprecated: This experimental fix is no longer needed and this field will be removed in a future release. 116 // When removing also remove from staticcheck exclude-rules for SA1019 in golangci.yml 117 UseExperimentalRetryJoin bool `json:"useExperimentalRetryJoin,omitempty"` 118 119 // Ignition contains Ignition specific configuration. 120 // +optional 121 Ignition *IgnitionSpec `json:"ignition,omitempty"` 122 } 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 360 // IgnitionSpec contains Ignition specific configuration. 361 type IgnitionSpec struct { 362 // ContainerLinuxConfig contains CLC specific configuration. 363 // +optional 364 ContainerLinuxConfig *ContainerLinuxConfig `json:"containerLinuxConfig,omitempty"` 365 } 366 367 // ContainerLinuxConfig contains CLC-specific configuration. 368 // 369 // We use a structured type here to allow adding additional fields, for example 'version'. 370 type ContainerLinuxConfig struct { 371 // AdditionalConfig contains additional configuration to be merged with the Ignition 372 // configuration generated by the bootstrapper controller. More info: https://coreos.github.io/ignition/operator-notes/#config-merging 373 // 374 // The data format is documented here: https://kinvolk.io/docs/flatcar-container-linux/latest/provisioning/cl-config/ 375 // +optional 376 AdditionalConfig string `json:"additionalConfig,omitempty"` 377 378 // Strict controls if AdditionalConfig should be strictly parsed. If so, warnings are treated as errors. 379 // +optional 380 Strict bool `json:"strict,omitempty"` 381 } 382 383 // KubeadmConfigStatus defines the observed state of KubeadmConfig. 384 type KubeadmConfigStatus struct { 385 // Ready indicates the BootstrapData field is ready to be consumed 386 // +optional 387 Ready bool `json:"ready"` 388 389 // DataSecretName is the name of the secret that stores the bootstrap data script. 390 // +optional 391 DataSecretName *string `json:"dataSecretName,omitempty"` 392 393 // FailureReason will be set on non-retryable errors 394 // +optional 395 FailureReason string `json:"failureReason,omitempty"` 396 397 // FailureMessage will be set on non-retryable errors 398 // +optional 399 FailureMessage string `json:"failureMessage,omitempty"` 400 401 // ObservedGeneration is the latest generation observed by the controller. 402 // +optional 403 ObservedGeneration int64 `json:"observedGeneration,omitempty"` 404 405 // Conditions defines current service state of the KubeadmConfig. 406 // +optional 407 Conditions clusterv1.Conditions `json:"conditions,omitempty"` 408 } 409 410 // +kubebuilder:object:root=true 411 // +kubebuilder:resource:path=kubeadmconfigs,scope=Namespaced,categories=cluster-api 412 // +kubebuilder:storageversion 413 // +kubebuilder:subresource:status 414 // +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".metadata.labels['cluster\\.x-k8s\\.io/cluster-name']",description="Cluster" 415 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of KubeadmConfig" 416 417 // KubeadmConfig is the Schema for the kubeadmconfigs API. 418 type KubeadmConfig struct { 419 metav1.TypeMeta `json:",inline"` 420 metav1.ObjectMeta `json:"metadata,omitempty"` 421 422 Spec KubeadmConfigSpec `json:"spec,omitempty"` 423 Status KubeadmConfigStatus `json:"status,omitempty"` 424 } 425 426 // GetConditions returns the set of conditions for this object. 427 func (c *KubeadmConfig) GetConditions() clusterv1.Conditions { 428 return c.Status.Conditions 429 } 430 431 // SetConditions sets the conditions on this object. 432 func (c *KubeadmConfig) SetConditions(conditions clusterv1.Conditions) { 433 c.Status.Conditions = conditions 434 } 435 436 // +kubebuilder:object:root=true 437 438 // KubeadmConfigList contains a list of KubeadmConfig. 439 type KubeadmConfigList struct { 440 metav1.TypeMeta `json:",inline"` 441 metav1.ListMeta `json:"metadata,omitempty"` 442 Items []KubeadmConfig `json:"items"` 443 } 444 445 func init() { 446 objectTypes = append(objectTypes, &KubeadmConfig{}, &KubeadmConfigList{}) 447 } 448 449 // Encoding specifies the cloud-init file encoding. 450 // +kubebuilder:validation:Enum=base64;gzip;gzip+base64 451 type Encoding string 452 453 const ( 454 // Base64 implies the contents of the file are encoded as base64. 455 Base64 Encoding = "base64" 456 // Gzip implies the contents of the file are encoded with gzip. 457 Gzip Encoding = "gzip" 458 // GzipBase64 implies the contents of the file are first base64 encoded and then gzip encoded. 459 GzipBase64 Encoding = "gzip+base64" 460 ) 461 462 // File defines the input for generating write_files in cloud-init. 463 type File struct { 464 // Path specifies the full path on disk where to store the file. 465 Path string `json:"path"` 466 467 // Owner specifies the ownership of the file, e.g. "root:root". 468 // +optional 469 Owner string `json:"owner,omitempty"` 470 471 // Permissions specifies the permissions to assign to the file, e.g. "0640". 472 // +optional 473 Permissions string `json:"permissions,omitempty"` 474 475 // Encoding specifies the encoding of the file contents. 476 // +optional 477 Encoding Encoding `json:"encoding,omitempty"` 478 479 // Append specifies whether to append Content to existing file if Path exists. 480 // +optional 481 Append bool `json:"append,omitempty"` 482 483 // Content is the actual content of the file. 484 // +optional 485 Content string `json:"content,omitempty"` 486 487 // ContentFrom is a referenced source of content to populate the file. 488 // +optional 489 ContentFrom *FileSource `json:"contentFrom,omitempty"` 490 } 491 492 // FileSource is a union of all possible external source types for file data. 493 // Only one field may be populated in any given instance. Developers adding new 494 // sources of data for target systems should add them here. 495 type FileSource struct { 496 // Secret represents a secret that should populate this file. 497 Secret SecretFileSource `json:"secret"` 498 } 499 500 // SecretFileSource adapts a Secret into a FileSource. 501 // 502 // The contents of the target Secret's Data field will be presented 503 // as files using the keys in the Data field as the file names. 504 type SecretFileSource struct { 505 // Name of the secret in the KubeadmBootstrapConfig's namespace to use. 506 Name string `json:"name"` 507 508 // Key is the key in the secret's data map for this value. 509 Key string `json:"key"` 510 } 511 512 // PasswdSource is a union of all possible external source types for passwd data. 513 // Only one field may be populated in any given instance. Developers adding new 514 // sources of data for target systems should add them here. 515 type PasswdSource struct { 516 // Secret represents a secret that should populate this password. 517 Secret SecretPasswdSource `json:"secret"` 518 } 519 520 // SecretPasswdSource adapts a Secret into a PasswdSource. 521 // 522 // The contents of the target Secret's Data field will be presented 523 // as passwd using the keys in the Data field as the file names. 524 type SecretPasswdSource struct { 525 // Name of the secret in the KubeadmBootstrapConfig's namespace to use. 526 Name string `json:"name"` 527 528 // Key is the key in the secret's data map for this value. 529 Key string `json:"key"` 530 } 531 532 // User defines the input for a generated user in cloud-init. 533 type User struct { 534 // Name specifies the user name 535 Name string `json:"name"` 536 537 // Gecos specifies the gecos to use for the user 538 // +optional 539 Gecos *string `json:"gecos,omitempty"` 540 541 // Groups specifies the additional groups for the user 542 // +optional 543 Groups *string `json:"groups,omitempty"` 544 545 // HomeDir specifies the home directory to use for the user 546 // +optional 547 HomeDir *string `json:"homeDir,omitempty"` 548 549 // Inactive specifies whether to mark the user as inactive 550 // +optional 551 Inactive *bool `json:"inactive,omitempty"` 552 553 // Shell specifies the user's shell 554 // +optional 555 Shell *string `json:"shell,omitempty"` 556 557 // Passwd specifies a hashed password for the user 558 // +optional 559 Passwd *string `json:"passwd,omitempty"` 560 561 // PasswdFrom is a referenced source of passwd to populate the passwd. 562 // +optional 563 PasswdFrom *PasswdSource `json:"passwdFrom,omitempty"` 564 565 // PrimaryGroup specifies the primary group for the user 566 // +optional 567 PrimaryGroup *string `json:"primaryGroup,omitempty"` 568 569 // LockPassword specifies if password login should be disabled 570 // +optional 571 LockPassword *bool `json:"lockPassword,omitempty"` 572 573 // Sudo specifies a sudo role for the user 574 // +optional 575 Sudo *string `json:"sudo,omitempty"` 576 577 // SSHAuthorizedKeys specifies a list of ssh authorized keys for the user 578 // +optional 579 SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` 580 } 581 582 // NTP defines input for generated ntp in cloud-init. 583 type NTP struct { 584 // Servers specifies which NTP servers to use 585 // +optional 586 Servers []string `json:"servers,omitempty"` 587 588 // Enabled specifies whether NTP should be enabled 589 // +optional 590 Enabled *bool `json:"enabled,omitempty"` 591 } 592 593 // DiskSetup defines input for generated disk_setup and fs_setup in cloud-init. 594 type DiskSetup struct { 595 // Partitions specifies the list of the partitions to setup. 596 // +optional 597 Partitions []Partition `json:"partitions,omitempty"` 598 599 // Filesystems specifies the list of file systems to setup. 600 // +optional 601 Filesystems []Filesystem `json:"filesystems,omitempty"` 602 } 603 604 // Partition defines how to create and layout a partition. 605 type Partition struct { 606 // Device is the name of the device. 607 Device string `json:"device"` 608 // Layout specifies the device layout. 609 // If it is true, a single partition will be created for the entire device. 610 // When layout is false, it means don't partition or ignore existing partitioning. 611 Layout bool `json:"layout"` 612 // Overwrite describes whether to skip checks and create the partition if a partition or filesystem is found on the device. 613 // Use with caution. Default is 'false'. 614 // +optional 615 Overwrite *bool `json:"overwrite,omitempty"` 616 // TableType specifies the tupe of partition table. The following are supported: 617 // 'mbr': default and setups a MS-DOS partition table 618 // 'gpt': setups a GPT partition table 619 // +optional 620 TableType *string `json:"tableType,omitempty"` 621 } 622 623 // Filesystem defines the file systems to be created. 624 type Filesystem struct { 625 // Device specifies the device name 626 Device string `json:"device"` 627 // Filesystem specifies the file system type. 628 Filesystem string `json:"filesystem"` 629 // Label specifies the file system label to be used. If set to None, no label is used. 630 Label string `json:"label"` 631 // Partition specifies the partition to use. The valid options are: "auto|any", "auto", "any", "none", and <NUM>, where NUM is the actual partition number. 632 // +optional 633 Partition *string `json:"partition,omitempty"` 634 // Overwrite defines whether or not to overwrite any existing filesystem. 635 // If true, any pre-existing file system will be destroyed. Use with Caution. 636 // +optional 637 Overwrite *bool `json:"overwrite,omitempty"` 638 // ReplaceFS is a special directive, used for Microsoft Azure that instructs cloud-init to replace a file system of <FS_TYPE>. 639 // NOTE: unless you define a label, this requires the use of the 'any' partition directive. 640 // +optional 641 ReplaceFS *string `json:"replaceFS,omitempty"` 642 // ExtraOpts defined extra options to add to the command for creating the file system. 643 // +optional 644 ExtraOpts []string `json:"extraOpts,omitempty"` 645 } 646 647 // MountPoints defines input for generated mounts in cloud-init. 648 type MountPoints []string