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