diff --git a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanix_types.go b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanix_types.go index aa261577e..a49ad1063 100644 --- a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanix_types.go +++ b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanix_types.go @@ -74,6 +74,24 @@ type NutanixResourceIdentifier struct { Name *string `json:"name,omitempty"` } +func (nri NutanixResourceIdentifier) String() string { + if nri.Type == NutanixIdentifierUUID && nri.UUID != nil { + return *nri.UUID + } + if nri.Type == NutanixIdentifierName && nri.Name != nil { + return *nri.Name + } + return "" +} + +func (nri NutanixResourceIdentifier) IsUUID() bool { + return nri.Type == NutanixIdentifierUUID && nri.UUID != nil +} + +func (nri NutanixResourceIdentifier) IsName() bool { + return nri.Type == NutanixIdentifierName && nri.Name != nil +} + type NutanixCategoryIdentifier struct { // key is the Key of category in PC. // +optional diff --git a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanixmachine_types.go b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanixmachine_types.go index 35f7344e2..ede9d163f 100644 --- a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanixmachine_types.go +++ b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/nutanixmachine_types.go @@ -45,9 +45,59 @@ const ( // to Image, the NutanixMachine will be created with the image mounted // as a CD-ROM. NutanixMachineBootstrapRefKindImage = "Image" + + // NutanixMachineDiskModeStandard represents the standard disk mode. + NutanixMachineDiskModeStandard NutanixMachineDiskMode = "Standard" + + // NutanixMachineDiskModeFlash represents the flash disk mode. + NutanixMachineDiskModeFlash NutanixMachineDiskMode = "Flash" + + // NutanixMachineDiskDeviceTypeDisk represents the disk device type. + NutanixMachineDiskDeviceTypeDisk NutanixMachineDiskDeviceType = "Disk" + + // NutanixMachineDiskDeviceTypeCDRom represents the CD-ROM device type. + NutanixMachineDiskDeviceTypeCDRom NutanixMachineDiskDeviceType = "CDRom" + + // NutanixMachineDiskAdapterTypeSCSI represents the SCSI adapter type. + NutanixMachineDiskAdapterTypeSCSI NutanixMachineDiskAdapterType = "SCSI" + + // NutanixMachineDiskAdapterTypeIDE represents the IDE adapter type. + NutanixMachineDiskAdapterTypeIDE NutanixMachineDiskAdapterType = "IDE" + + // NutanixMachineDiskAdapterTypePCI represents the PCI adapter type. + NutanixMachineDiskAdapterTypePCI NutanixMachineDiskAdapterType = "PCI" + + // NutanixMachineDiskAdapterTypeSATA represents the SATA adapter type. + NutanixMachineDiskAdapterTypeSATA NutanixMachineDiskAdapterType = "SATA" + + // NutanixMachineDiskAdapterTypeSPAPR represents the SPAPR adapter type. + NutanixMachineDiskAdapterTypeSPAPR NutanixMachineDiskAdapterType = "SPAPR" ) +// NutanixImageLookup defines how to fetch images for the cluster +// using the fields combined. +type NutanixImageLookup struct { + // Format is the naming format to look up the image for this + // machine It will be ignored if an explicit image is set. Supports + // substitutions for {{.BaseOS}} and {{.K8sVersion}} with the base OS and + // kubernetes version, respectively. The BaseOS will be the value in + // BaseOS and the K8sVersion is the value in the Machine .spec.version, with the v prefix removed. + // This is effectively the defined by the packages produced by kubernetes/release without v as a + // prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default + // image format of {{.BaseOS}}-?{{.K8sVersion}}-* and BaseOS as "rhel-8.10" will end up + // searching for images that match the pattern rhel-8.10-1.30.5-* for a + // Machine that is targeting kubernetes v1.30.5. See + // also: https://golang.org/pkg/text/template/ + // +kubebuilder:default:="capx-{{.BaseOS}}-{{.K8sVersion}}-*" + Format *string `json:"format,omitempty"` + // BaseOS is the name of the base operating system to use for + // image lookup. + // +kubebuilder:validation:MinLength:=1 + BaseOS string `json:"baseOS"` +} + // NutanixMachineSpec defines the desired state of NutanixMachine +// +kubebuilder:validation:XValidation:rule="has(self.image) != has(self.imageLookup)",message="Either 'image' or 'imageLookup' must be set, but not both" type NutanixMachineSpec struct { // SPEC FIELDS - desired state of NutanixMachine // Important: Run "make" to regenerate code after modifying this file @@ -67,11 +117,16 @@ type NutanixMachineSpec struct { // The minimum memorySize is 2Gi bytes // +kubebuilder:validation:Required MemorySize resource.Quantity `json:"memorySize"` - // image is to identify the rhcos image uploaded to the Prism Central (PC) + // image is to identify the nutanix machine image uploaded to the Prism Central (PC) // The image identifier (uuid or name) can be obtained from the Prism Central console // or using the prism_central API. - // +kubebuilder:validation:Required - Image NutanixResourceIdentifier `json:"image"` + // +kubebuilder:validation:Optional + // +optional + Image *NutanixResourceIdentifier `json:"image,omitempty"` + // imageLookup is a container that holds how to look up rhcos images for the cluster. + // +kubebuilder:validation:Optional + // +optional + ImageLookup *NutanixImageLookup `json:"imageLookup,omitempty"` // cluster is to identify the cluster (the Prism Element under management // of the Prism Central), in which the Machine's VM will be created. // The cluster identifier (uuid or name) can be obtained from the Prism Central console @@ -93,22 +148,100 @@ type NutanixMachineSpec struct { // +kubebuilder:validation:Optional // +kubebuilder:validation:Enum:=legacy;uefi BootType NutanixBootType `json:"bootType,omitempty"` - // systemDiskSize is size (in Quantity format) of the system disk of the VM // The minimum systemDiskSize is 20Gi bytes // +kubebuilder:validation:Required SystemDiskSize resource.Quantity `json:"systemDiskSize"` + // dataDisks hold the list of data disks to be attached to the VM + // +kubebuilder:validation:Optional + DataDisks []NutanixMachineVMDisk `json:"dataDisks,omitempty"` + // BootstrapRef is a reference to a bootstrap provider-specific resource // that holds configuration details. // +optional BootstrapRef *corev1.ObjectReference `json:"bootstrapRef,omitempty"` - // List of GPU devices that need to be added to the machines. // +kubebuilder:validation:Optional GPUs []NutanixGPU `json:"gpus,omitempty"` } +// NutanixMachineVMDisk defines the disk configuration for a NutanixMachine +type NutanixMachineVMDisk struct { + // diskSize is the size (in Quantity format) of the disk attached to the VM. + // See https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Format for the Quantity format and example documentation. + // The minimum diskSize is 1GB. + // +kubebuilder:validation:Required + DiskSize resource.Quantity `json:"diskSize"` + + // deviceProperties are the properties of the disk device. + // +optional + // +kubebuilder:validation:Optional + DeviceProperties *NutanixMachineVMDiskDeviceProperties `json:"deviceProperties,omitempty"` + + // storageConfig are the storage configuration parameters of the VM disks. + // +optional + // +kubebuilder:validation:Optional + StorageConfig *NutanixMachineVMStorageConfig `json:"storageConfig,omitempty"` + + // dataSource refers to a data source image for the VM disk. + // +optional + // +kubebuilder:validation:Optional + DataSource *NutanixResourceIdentifier `json:"dataSource,omitempty"` +} + +// NutanixMachineVMDiskDeviceProperties defines the device properties for a NutanixMachineVMDisk +type NutanixMachineVMDiskDeviceProperties struct { + // deviceType specifies the disk device type. + // The valid values are "Disk" and "CDRom", and the default is "Disk". + // +kubebuilder:default=Disk + // +kubebuilder:validation:Required + DeviceType NutanixMachineDiskDeviceType `json:"deviceType"` + + // adapterType is the adapter type of the disk address. + // If the deviceType is "Disk", the valid adapterType can be "SCSI", "IDE", "PCI", "SATA" or "SPAPR". + // If the deviceType is "CDRom", the valid adapterType can be "IDE" or "SATA". + // +kubebuilder:validation:Required + AdapterType NutanixMachineDiskAdapterType `json:"adapterType,omitempty"` + + // deviceIndex is the index of the disk address. The valid values are non-negative integers, with the default value 0. + // For a Machine VM, the deviceIndex for the disks with the same deviceType.adapterType combination should + // start from 0 and increase consecutively afterwards. Note that for each Machine VM, the Disk.SCSI.0 + // and CDRom.IDE.0 are reserved to be used by the VM's system. So for dataDisks of Disk.SCSI and CDRom.IDE, + // the deviceIndex should start from 1. + // +kubebuilder:default=0 + // +kubebuilder:validation:Minimum=0 + // +optional + // +kubebuilder:validation:Optional + DeviceIndex int32 `json:"deviceIndex,omitempty"` +} + +// NutanixMachineVMStorageConfig defines the storage configuration for a NutanixMachineVMDisk +type NutanixMachineVMStorageConfig struct { + // diskMode specifies the disk mode. + // The valid values are Standard and Flash, and the default is Standard. + // +kubebuilder:default=Standard + // +kubebuilder:validation:Required + DiskMode NutanixMachineDiskMode `json:"diskMode"` + + // storageContainer refers to the storage_container used by the VM disk. + // +optional + // +kubebuilder:validation:Optional + StorageContainer *NutanixResourceIdentifier `json:"storageContainer"` +} + +// NutanixMachineDiskMode is an enumeration of different disk modes. +// +kubebuilder:validation:Enum=Standard;Flash +type NutanixMachineDiskMode string + +// NutanixMachineDiskDeviceType is the VM disk device type. +// +kubebuilder:validation:Enum=Disk;CDRom +type NutanixMachineDiskDeviceType string + +// NutanixMachineDiskAdapterType is an enumeration of different disk device adapter types. +// +kubebuilder:validation:Enum:=SCSI;IDE;PCI;SATA;SPAPR +type NutanixMachineDiskAdapterType string + // NutanixMachineStatus defines the observed state of NutanixMachine type NutanixMachineStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -144,14 +277,13 @@ type NutanixMachineStatus struct { FailureMessage *string `json:"failureMessage,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:resource:path=nutanixmachines,shortName=nma,scope=Namespaced,categories=cluster-api -//+kubebuilder:subresource:status -//+kubebuilder:storageversion -//+kubebuilder:printcolumn:name="Address",type="string",JSONPath=".status.addresses[0].address",description="The VM address" +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=nutanixmachines,shortName=nma,scope=Namespaced,categories=cluster-api +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Address",type="string",JSONPath=".status.addresses[0].address",description="The VM address" // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="NutanixMachine ready status" // +kubebuilder:printcolumn:name="ProviderID",type="string",JSONPath=".spec.providerID",description="NutanixMachine instance ID" - // NutanixMachine is the Schema for the nutanixmachines API type NutanixMachine struct { metav1.TypeMeta `json:",inline"` diff --git a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/zz_generated.deepcopy.go b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/zz_generated.deepcopy.go index 20bba54fd..21ceff00c 100644 --- a/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/zz_generated.deepcopy.go +++ b/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1/zz_generated.deepcopy.go @@ -307,6 +307,26 @@ func (in *NutanixGPU) DeepCopy() *NutanixGPU { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NutanixImageLookup) DeepCopyInto(out *NutanixImageLookup) { + *out = *in + if in.Format != nil { + in, out := &in.Format, &out.Format + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixImageLookup. +func (in *NutanixImageLookup) DeepCopy() *NutanixImageLookup { + if in == nil { + return nil + } + out := new(NutanixImageLookup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NutanixMachine) DeepCopyInto(out *NutanixMachine) { *out = *in @@ -370,7 +390,16 @@ func (in *NutanixMachineList) DeepCopyObject() runtime.Object { func (in *NutanixMachineSpec) DeepCopyInto(out *NutanixMachineSpec) { *out = *in out.MemorySize = in.MemorySize.DeepCopy() - in.Image.DeepCopyInto(&out.Image) + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(NutanixResourceIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ImageLookup != nil { + in, out := &in.ImageLookup, &out.ImageLookup + *out = new(NutanixImageLookup) + (*in).DeepCopyInto(*out) + } in.Cluster.DeepCopyInto(&out.Cluster) if in.Subnets != nil { in, out := &in.Subnets, &out.Subnets @@ -390,6 +419,13 @@ func (in *NutanixMachineSpec) DeepCopyInto(out *NutanixMachineSpec) { (*in).DeepCopyInto(*out) } out.SystemDiskSize = in.SystemDiskSize.DeepCopy() + if in.DataDisks != nil { + in, out := &in.DataDisks, &out.DataDisks + *out = make([]NutanixMachineVMDisk, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.BootstrapRef != nil { in, out := &in.BootstrapRef, &out.BootstrapRef *out = new(v1.ObjectReference) @@ -547,6 +583,72 @@ func (in *NutanixMachineTemplateSpec) DeepCopy() *NutanixMachineTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NutanixMachineVMDisk) DeepCopyInto(out *NutanixMachineVMDisk) { + *out = *in + out.DiskSize = in.DiskSize.DeepCopy() + if in.DeviceProperties != nil { + in, out := &in.DeviceProperties, &out.DeviceProperties + *out = new(NutanixMachineVMDiskDeviceProperties) + **out = **in + } + if in.StorageConfig != nil { + in, out := &in.StorageConfig, &out.StorageConfig + *out = new(NutanixMachineVMStorageConfig) + (*in).DeepCopyInto(*out) + } + if in.DataSource != nil { + in, out := &in.DataSource, &out.DataSource + *out = new(NutanixResourceIdentifier) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixMachineVMDisk. +func (in *NutanixMachineVMDisk) DeepCopy() *NutanixMachineVMDisk { + if in == nil { + return nil + } + out := new(NutanixMachineVMDisk) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NutanixMachineVMDiskDeviceProperties) DeepCopyInto(out *NutanixMachineVMDiskDeviceProperties) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixMachineVMDiskDeviceProperties. +func (in *NutanixMachineVMDiskDeviceProperties) DeepCopy() *NutanixMachineVMDiskDeviceProperties { + if in == nil { + return nil + } + out := new(NutanixMachineVMDiskDeviceProperties) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NutanixMachineVMStorageConfig) DeepCopyInto(out *NutanixMachineVMStorageConfig) { + *out = *in + if in.StorageContainer != nil { + in, out := &in.StorageContainer, &out.StorageContainer + *out = new(NutanixResourceIdentifier) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixMachineVMStorageConfig. +func (in *NutanixMachineVMStorageConfig) DeepCopy() *NutanixMachineVMStorageConfig { + if in == nil { + return nil + } + out := new(NutanixMachineVMStorageConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NutanixResourceIdentifier) DeepCopyInto(out *NutanixResourceIdentifier) { *out = *in diff --git a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml index 89ae1b461..3b1835ea3 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml @@ -384,6 +384,33 @@ spec: required: - type type: object + imageLookup: + description: imageLookup is a container that holds how to look up vm images for the cluster. + properties: + baseOS: + description: |- + BaseOS is the name of the base operating system to use for + image lookup. + minLength: 1 + type: string + format: + default: capx-{{.BaseOS}}-{{.K8sVersion}}-* + description: |- + Format is the naming format to look up the image for this + machine It will be ignored if an explicit image is set. Supports + substitutions for {{.BaseOS}} and {{.K8sVersion}} with the base OS and + kubernetes version, respectively. The BaseOS will be the value in + BaseOS and the K8sVersion is the value in the Machine .spec.version, with the v prefix removed. + This is effectively the defined by the packages produced by kubernetes/release without v as a + prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default + image format of {{.BaseOS}}-?{{.K8sVersion}}-* and BaseOS as "rhel-8.10" will end up + searching for images that match the pattern rhel-8.10-1.30.5-* for a + Machine that is targeting kubernetes v1.30.5. See + also: https://golang.org/pkg/text/template/ + type: string + required: + - baseOS + type: object memorySize: description: memorySize is the memory size (in Quantity format) of the VM pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ @@ -449,13 +476,15 @@ spec: type: integer required: - cluster - - image - memorySize - subnets - systemDiskSize - vcpuSockets - vcpusPerSocket type: object + x-kubernetes-validations: + - message: Either 'image' or 'imageLookup' must be set, but not both + rule: has(self.image) != has(self.imageLookup) required: - machineDetails type: object diff --git a/api/v1alpha1/crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml index bc96d9833..025ec59eb 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml @@ -126,6 +126,33 @@ spec: required: - type type: object + imageLookup: + description: imageLookup is a container that holds how to look up vm images for the cluster. + properties: + baseOS: + description: |- + BaseOS is the name of the base operating system to use for + image lookup. + minLength: 1 + type: string + format: + default: capx-{{.BaseOS}}-{{.K8sVersion}}-* + description: |- + Format is the naming format to look up the image for this + machine It will be ignored if an explicit image is set. Supports + substitutions for {{.BaseOS}} and {{.K8sVersion}} with the base OS and + kubernetes version, respectively. The BaseOS will be the value in + BaseOS and the K8sVersion is the value in the Machine .spec.version, with the v prefix removed. + This is effectively the defined by the packages produced by kubernetes/release without v as a + prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default + image format of {{.BaseOS}}-?{{.K8sVersion}}-* and BaseOS as "rhel-8.10" will end up + searching for images that match the pattern rhel-8.10-1.30.5-* for a + Machine that is targeting kubernetes v1.30.5. See + also: https://golang.org/pkg/text/template/ + type: string + required: + - baseOS + type: object memorySize: description: memorySize is the memory size (in Quantity format) of the VM pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ @@ -191,13 +218,15 @@ spec: type: integer required: - cluster - - image - memorySize - subnets - systemDiskSize - vcpuSockets - vcpusPerSocket type: object + x-kubernetes-validations: + - message: Either 'image' or 'imageLookup' must be set, but not both + rule: has(self.image) != has(self.imageLookup) required: - machineDetails type: object diff --git a/api/v1alpha1/nutanix_node_types.go b/api/v1alpha1/nutanix_node_types.go index f9cae83d7..1af2d87f3 100644 --- a/api/v1alpha1/nutanix_node_types.go +++ b/api/v1alpha1/nutanix_node_types.go @@ -13,6 +13,7 @@ type NutanixNodeSpec struct { MachineDetails NutanixMachineDetails `json:"machineDetails"` } +// +kubebuilder:validation:XValidation:rule="has(self.image) != has(self.imageLookup)",message="Either 'image' or 'imageLookup' must be set, but not both" type NutanixMachineDetails struct { // vcpusPerSocket is the number of vCPUs per socket of the VM // +kubebuilder:validation:Required @@ -28,8 +29,14 @@ type NutanixMachineDetails struct { // image identifies the image uploaded to Prism Central (PC). The identifier // (uuid or name) can be obtained from the console or API. - // +kubebuilder:validation:Required - Image capxv1.NutanixResourceIdentifier `json:"image"` + // +kubebuilder:validation:Optional + // +optional + Image *capxv1.NutanixResourceIdentifier `json:"image,omitempty"` + + // imageLookup is a container that holds how to look up vm images for the cluster. + // +kubebuilder:validation:Optional + // +optional + ImageLookup *capxv1.NutanixImageLookup `json:"imageLookup,omitempty"` // cluster identifies the Prism Element in which the machine will be created. // The identifier (uuid or name) can be obtained from the console or API. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 24bfd42e6..10073f739 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1505,7 +1505,16 @@ func (in *NutanixControlPlaneSpec) DeepCopy() *NutanixControlPlaneSpec { func (in *NutanixMachineDetails) DeepCopyInto(out *NutanixMachineDetails) { *out = *in out.MemorySize = in.MemorySize.DeepCopy() - in.Image.DeepCopyInto(&out.Image) + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(v1beta1.NutanixResourceIdentifier) + (*in).DeepCopyInto(*out) + } + if in.ImageLookup != nil { + in, out := &in.ImageLookup, &out.ImageLookup + *out = new(v1beta1.NutanixImageLookup) + (*in).DeepCopyInto(*out) + } in.Cluster.DeepCopyInto(&out.Cluster) if in.Subnets != nil { in, out := &in.Subnets, &out.Subnets diff --git a/docs/content/customization/nutanix/machine-details.md b/docs/content/customization/nutanix/machine-details.md index f0f3c8889..12c3ec402 100644 --- a/docs/content/customization/nutanix/machine-details.md +++ b/docs/content/customization/nutanix/machine-details.md @@ -111,6 +111,70 @@ spec: vcpusPerSocket: 1 ``` +### (Optional) Using templates for VM Image lookup + +Users can set format look up the image for a VM, It will be ignored if an explicit image is set. +Supports substitutions for `{{.BaseOS}}` and `{{.K8sVersion}}` with the base OS and +kubernetes version, respectively. The BaseOS will be the value in BaseOS and the K8sVersion +is the value in the Machine `.spec.version`, with the v prefix removed. +This is effectively the defined by the packages produced by kubernetes/release without v as a +prefix: 1.13.0, 1.12.5-mybuild.1, or 1.17.3. For example, the default +image format of `{{.BaseOS}}-?{{.K8sVersion}}-*` and `BaseOS` as "rhel-8.10" will end up +searching for images that match the pattern rhel-8.10-1.30.5-* for a +Machine that is targeting Kubernetes version `v1.30.5`. See +also [go text template](https://golang.org/pkg/text/template/) + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + controlPlane: + nutanix: + machineDetails: + bootType: legacy + cluster: + name: pe-cluster-name + type: name + imageLookup: + baseOS: "rockylinux-9" + format: {{.BaseOS}}-kube-v{{.K8sVersion}}.* +``` + +Applying this configuration will result in the following value being set: + +- control-plane `NutanixMachineTemplate`: + +```yaml +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: NutanixMachineTemplate +metadata: + name: nutanix-quick-start-cp-nmt +spec: + template: + spec: + bootType: legacy + cluster: + name: pe-cluster-name + type: name + imageLookup: + baseOS: "rockylinux-9" + format: {{.BaseOS}}-kube-v{{.K8sVersion}}.* + memorySize: 4Gi + providerID: nutanix://vm-uuid + subnet: + - name: subnet-name + type: name + systemDiskSize: 40Gi + vcpuSockets: 2 + vcpusPerSocket: 1 +``` + ### (Optional) Set Additional Categories for Control Plane and Worker nodes ```yaml diff --git a/hack/third-party/capx/go.mod b/hack/third-party/capx/go.mod index b1e4350a8..2ff08b68f 100644 --- a/hack/third-party/capx/go.mod +++ b/hack/third-party/capx/go.mod @@ -5,7 +5,7 @@ module github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/ex go 1.22.1 -require github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.5.3 +require github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.6.0 require ( github.com/emicklei/go-restful/v3 v3.12.1 // indirect @@ -24,8 +24,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nutanix-cloud-native/prism-go-client v0.5.0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/hack/third-party/capx/go.sum b/hack/third-party/capx/go.sum index bbaf17435..68752393b 100644 --- a/hack/third-party/capx/go.sum +++ b/hack/third-party/capx/go.sum @@ -72,8 +72,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.5.3 h1:6F2IrYW1Ky3oo44rGcvvGgUo6wqPA8fjjou3kjKVNbk= -github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.5.3/go.mod h1:cujPL3lTeW8+zr8RUJCPDss1HHe81Hc9XxXMNrdTSqQ= +github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.6.0 h1:sU5GtB254rbZpID7+0kx1sPKKa0JQfG9MmYNlq8+slM= +github.com/nutanix-cloud-native/cluster-api-provider-nutanix v1.6.0/go.mod h1:FfhlVQQKKrZGLKXuqbS4zzcKI5lLe+ZMMu6i4QKRn6w= github.com/nutanix-cloud-native/prism-go-client v0.5.0 h1:aSNuKDOK7+q676MQyetYXcySY41IjSvN2UmrDIU3+6s= github.com/nutanix-cloud-native/prism-go-client v0.5.0/go.mod h1:QhLX+sEep0cStzHVYU6mPgIlnA8U3DySskagrbDprRk= github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= @@ -120,20 +120,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/pkg/handlers/nutanix/mutation/machinedetails/inject.go b/pkg/handlers/nutanix/mutation/machinedetails/inject.go index 77ae20f5e..21d6819cf 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/inject.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/inject.go @@ -5,6 +5,7 @@ package machinedetails import ( "context" + "errors" "slices" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -32,6 +33,9 @@ type nutanixMachineDetailsPatchHandler struct { patchSelector clusterv1.PatchSelector } +// ErrNoImageOrImageLookupSet is an error that gets returned only if image and lookup are both set. +var ErrNoImageOrImageLookupSet = errors.New("image or image lookup must be set") + func newNutanixMachineDetailsPatchHandler( metaVariableName string, variableFieldPath []string, @@ -94,7 +98,15 @@ func (h *nutanixMachineDetailsPatchHandler) Mutate( spec.BootType = nutanixMachineDetailsVar.BootType spec.Cluster = nutanixMachineDetailsVar.Cluster - spec.Image = nutanixMachineDetailsVar.Image + + switch { + case nutanixMachineDetailsVar.Image != nil: + spec.Image = nutanixMachineDetailsVar.Image.DeepCopy() + case nutanixMachineDetailsVar.ImageLookup != nil: + spec.ImageLookup = nutanixMachineDetailsVar.ImageLookup.DeepCopy() + default: + return ErrNoImageOrImageLookupSet + } spec.VCPUSockets = nutanixMachineDetailsVar.VCPUSockets spec.VCPUsPerSocket = nutanixMachineDetailsVar.VCPUsPerSocket diff --git a/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go b/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go index cb14f3056..b25efed29 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go @@ -23,7 +23,7 @@ var ( BootType: capxv1.NutanixBootTypeLegacy, VCPUSockets: 2, VCPUsPerSocket: 1, - Image: capxv1.NutanixResourceIdentifier{ + Image: &capxv1.NutanixResourceIdentifier{ Type: capxv1.NutanixIdentifierName, Name: ptr.To("fake-image"), }, @@ -65,6 +65,79 @@ var ( }, } + variableWithImageTemplating = v1alpha1.NutanixMachineDetails{ + BootType: capxv1.NutanixBootTypeLegacy, + VCPUSockets: 2, + VCPUsPerSocket: 1, + ImageLookup: &capxv1.NutanixImageLookup{ + BaseOS: "rockylinux-9", + }, + Cluster: capxv1.NutanixResourceIdentifier{ + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-pe-cluster"), + }, + MemorySize: resource.MustParse("8Gi"), + SystemDiskSize: resource.MustParse("40Gi"), + Subnets: []capxv1.NutanixResourceIdentifier{ + { + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-subnet"), + }, + }, + } + matchersForAllImageTemplating = []capitest.JSONPatchMatcher{ + // boot type + { + Operation: "add", + Path: "/spec/template/spec/bootType", + ValueMatcher: gomega.BeEquivalentTo(capxv1.NutanixBootTypeLegacy), + }, + // cluster + { + Operation: "add", + Path: "/spec/template/spec/cluster/name", + ValueMatcher: gomega.BeEquivalentTo("fake-pe-cluster"), + }, + { + Operation: "replace", + Path: "/spec/template/spec/cluster/type", + ValueMatcher: gomega.BeEquivalentTo(capxv1.NutanixIdentifierName), + }, + + { + Operation: "replace", + Path: "/spec/template/spec/vcpuSockets", + ValueMatcher: gomega.BeEquivalentTo(2), + }, + { + Operation: "replace", + Path: "/spec/template/spec/vcpusPerSocket", + ValueMatcher: gomega.BeEquivalentTo(1), + }, + { + Operation: "replace", + Path: "/spec/template/spec/memorySize", + ValueMatcher: gomega.BeEquivalentTo("8Gi"), + }, + { + Operation: "replace", + Path: "/spec/template/spec/systemDiskSize", + ValueMatcher: gomega.BeEquivalentTo("40Gi"), + }, + { + Operation: "replace", + Path: "/spec/template/spec/subnet", + ValueMatcher: gomega.HaveLen(1), + }, + { + Operation: "add", + Path: "/spec/template/spec/imageLookup", + ValueMatcher: gomega.SatisfyAll( + gomega.HaveKeyWithValue("baseOS", "rockylinux-9"), + ), + }, + } + matchersForAllFieldsSet = []capitest.JSONPatchMatcher{ { Operation: "add", @@ -85,16 +158,6 @@ var ( Path: "/spec/template/spec/bootType", ValueMatcher: gomega.BeEquivalentTo(capxv1.NutanixBootTypeLegacy), }, - { - Operation: "add", - Path: "/spec/template/spec/image/name", - ValueMatcher: gomega.BeEquivalentTo("fake-image"), - }, - { - Operation: "replace", - Path: "/spec/template/spec/image/type", - ValueMatcher: gomega.BeEquivalentTo(capxv1.NutanixIdentifierName), - }, { Operation: "add", Path: "/spec/template/spec/cluster/name", @@ -130,6 +193,14 @@ var ( Path: "/spec/template/spec/subnet", ValueMatcher: gomega.HaveLen(1), }, + { + Operation: "add", + Path: "/spec/template/spec/image", + ValueMatcher: gomega.SatisfyAll( + gomega.HaveKeyWithValue("type", "name"), + gomega.HaveKeyWithValue("name", "fake-image"), + ), + }, { Operation: "add", Path: "/spec/template/spec/additionalCategories", @@ -182,6 +253,20 @@ var _ = Describe("Generate Nutanix Machine Details patches for ControlPlane", fu RequestItem: request.NewCPNutanixMachineTemplateRequestItem(""), ExpectedPatchMatchers: matchersForAllFieldsSet, }, + { + Name: "image templating set for control-plane", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + variableWithImageTemplating, + v1alpha1.ControlPlaneConfigVariableName, + v1alpha1.NutanixVariableName, + VariableName, + ), + }, + RequestItem: request.NewCPNutanixMachineTemplateRequestItem(""), + ExpectedPatchMatchers: matchersForAllImageTemplating, + }, } // create test node for each case diff --git a/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go b/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go index 8c16b198b..38489dcc6 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go @@ -105,7 +105,7 @@ func minimumClusterConfigSpec() v1alpha1.NutanixClusterConfigSpec { BootType: capxv1.NutanixBootTypeLegacy, VCPUSockets: 2, VCPUsPerSocket: 1, - Image: capxv1.NutanixResourceIdentifier{ + Image: &capxv1.NutanixResourceIdentifier{ Type: capxv1.NutanixIdentifierName, Name: ptr.To("fake-image"), }, diff --git a/test/e2e/data/shared/v1beta1-capx/metadata.yaml b/test/e2e/data/shared/v1beta1-capx/metadata.yaml index 03bb6bad0..bcad32287 100644 --- a/test/e2e/data/shared/v1beta1-capx/metadata.yaml +++ b/test/e2e/data/shared/v1beta1-capx/metadata.yaml @@ -11,5 +11,5 @@ apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3 kind: Metadata releaseSeries: - major: 1 - minor: 5 + minor: 6 contract: v1beta1