diff --git a/api/v1alpha1/nutanix_node_types.go b/api/v1alpha1/nutanix_node_types.go index 58a7ce3b5..4dfe4287a 100644 --- a/api/v1alpha1/nutanix_node_types.go +++ b/api/v1alpha1/nutanix_node_types.go @@ -62,6 +62,11 @@ type NutanixMachineDetails struct { // systemDiskSize is size (in Quantity format) of the system disk of the VM // The minimum systemDiskSize is 20Gi bytes SystemDiskSize resource.Quantity `json:"systemDiskSize"` + + // add the virtual machines to the project defined in Prism Central. + // The project must already be present in the Prism Central. + // +optional + Project *NutanixResourceIdentifier `json:"project,omitempty"` } func (NutanixMachineDetails) VariableSchema() clusterv1.VariableSchema { @@ -112,6 +117,10 @@ func (NutanixMachineDetails) VariableSchema() clusterv1.VariableSchema { Description: "systemDiskSize is size (in Quantity format) of the system disk of the VM eg. 20Gi", Type: "string", }, + "project": NutanixResourceIdentifier{}.VariableSchemaFromDescription( + //nolint:lll // Long description. + "add the virtual machines to the project defined in Prism Central. The project must already be present in the Prism Central.", + ).OpenAPIV3Schema, }, Required: []string{ "vcpusPerSocket", diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index eb051f5a0..01029e04d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -766,6 +766,11 @@ func (in *NutanixMachineDetails) DeepCopyInto(out *NutanixMachineDetails) { copy(*out, *in) } out.SystemDiskSize = in.SystemDiskSize.DeepCopy() + if in.Project != nil { + in, out := &in.Project, &out.Project + *out = new(NutanixResourceIdentifier) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NutanixMachineDetails. diff --git a/docs/content/customization/nutanix/machine-details.md b/docs/content/customization/nutanix/machine-details.md index e3ff70c14..632651e84 100644 --- a/docs/content/customization/nutanix/machine-details.md +++ b/docs/content/customization/nutanix/machine-details.md @@ -6,7 +6,7 @@ Configure Machine Details of Control plane and Worker nodes ## Examples -### Set Machine details of Control Plane and Worker nodes +### (Required) Set Machine details for Control Plane and Worker nodes ```yaml apiVersion: cluster.x-k8s.io/v1beta1 @@ -119,3 +119,121 @@ spec: vcpuSockets: 2 vcpusPerSocket: 1 ``` + +### (Optional) Set Additional Categories for Control Plane and Worker nodes + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + controlPlane: + nutanix: + machineDetails: + additionalCategories: + - key: example-key + value: example-value + - name: workerConfig + value: + nutanix: + machineDetails: + additionalCategories: + - key: example-key + value: example-value +``` + +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: + additionalCategories: + - key: example-key + value: example-value +``` + +- worker `NutanixMachineTemplate`: + +```yaml +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: NutanixMachineTemplate +metadata: + name: nutanix-quick-start-md-nmt +spec: + template: + spec: + additionalCategories: + - key: example-key + value: example-value +``` + +### (Optional) Set Project for Control Plane and Worker nodes + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + controlPlane: + nutanix: + machineDetails: + project: + type: name + name: project-name + - name: workerConfig + value: + nutanix: + machineDetails: + project: + type: name + name: project-name +``` + +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: + project: + type: name + name: project-name +``` + +- worker `NutanixMachineTemplate`: + +```yaml +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: NutanixMachineTemplate +metadata: + name: nutanix-quick-start-md-nmt +spec: + template: + spec: + project: + type: name + name: project-name +``` diff --git a/pkg/handlers/nutanix/mutation/machinedetails/inject.go b/pkg/handlers/nutanix/mutation/machinedetails/inject.go index 139842ed4..243bd7200 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/inject.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/inject.go @@ -8,6 +8,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/utils/ptr" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" ctrl "sigs.k8s.io/controller-runtime" @@ -116,6 +117,10 @@ func (h *nutanixMachineDetailsPatchHandler) Mutate( spec.AdditionalCategories[i] = capxv1.NutanixCategoryIdentifier(category) } + if nutanixMachineDetailsVar.Project != nil { + spec.Project = ptr.To(capxv1.NutanixResourceIdentifier(*nutanixMachineDetailsVar.Project)) + } + obj.Spec.Template.Spec = spec return nil }, 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 9a87d6eec..060b1a73d 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/inject_control_plane_test.go @@ -51,6 +51,10 @@ var ( Value: "fake-value2", }, }, + Project: ptr.To(v1alpha1.NutanixResourceIdentifier{ + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-project"), + }), } matchersForAllFieldsSet = []capitest.JSONPatchMatcher{ @@ -118,6 +122,14 @@ var ( ), ), }, + { + Operation: "add", + Path: "/spec/template/spec/project", + ValueMatcher: gomega.SatisfyAll( + gomega.HaveKeyWithValue("type", "name"), + gomega.HaveKeyWithValue("name", "fake-project"), + ), + }, } ) diff --git a/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go b/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go index b9dda5d33..e11a9eb22 100644 --- a/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go +++ b/pkg/handlers/nutanix/mutation/machinedetails/variables_test.go @@ -17,8 +17,46 @@ import ( ) func TestVariableValidation(t *testing.T) { - testImageName := "fake-image" - testPEClusterName := "fake-pe-cluster" + requiredFields := minimumClusterConfigSpec() + + withAdditionalCategories := minimumClusterConfigSpec() + //nolint:lll // gofumpt formats is this way + withAdditionalCategories.ControlPlane.Nutanix.MachineDetails.AdditionalCategories = []v1alpha1.NutanixCategoryIdentifier{ + { + Key: "fake-key", + Value: "fake-value1", + }, + { + Key: "fake-key", + Value: "fake-value2", + }, + } + + withProject := minimumClusterConfigSpec() + withProject.ControlPlane.Nutanix.MachineDetails.Project = ptr.To( + v1alpha1.NutanixResourceIdentifier{ + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-project"), + }, + ) + + invalidBootType := minimumClusterConfigSpec() + invalidBootType.ControlPlane.Nutanix.MachineDetails.BootType = "invalid-boot-type" + + invalidImageType := minimumClusterConfigSpec() + invalidImageType.ControlPlane.Nutanix.MachineDetails.Image.Type = "invalid-image-type" + + invalidClusterType := minimumClusterConfigSpec() + invalidClusterType.ControlPlane.Nutanix.MachineDetails.Cluster.Type = "invalid-cluster-type" + + invalidProjectType := minimumClusterConfigSpec() + invalidProjectType.ControlPlane.Nutanix.MachineDetails.Project = ptr.To( + v1alpha1.NutanixResourceIdentifier{ + Type: "invalid-project-type", + Name: ptr.To("fake-project"), + }, + ) + capitest.ValidateDiscoverVariables( t, clusterconfig.MetaVariableName, @@ -26,117 +64,61 @@ func TestVariableValidation(t *testing.T) { true, nutanixclusterconfig.NewVariable, capitest.VariableTestDef{ - Name: "all fields set", - Vals: v1alpha1.ClusterConfigSpec{ - ControlPlane: &v1alpha1.NodeConfigSpec{ - Nutanix: &v1alpha1.NutanixNodeSpec{ - MachineDetails: v1alpha1.NutanixMachineDetails{ - BootType: v1alpha1.NutanixBootType(capxv1.NutanixBootTypeLegacy), - VCPUSockets: 2, - VCPUsPerSocket: 1, - Image: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testImageName, - }, - Cluster: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testPEClusterName, - }, - MemorySize: resource.MustParse("8Gi"), - SystemDiskSize: resource.MustParse("40Gi"), - Subnets: []v1alpha1.NutanixResourceIdentifier{}, - AdditionalCategories: []v1alpha1.NutanixCategoryIdentifier{ - { - Key: "fake-key", - Value: "fake-value1", - }, - { - Key: "fake-key", - Value: "fake-value2", - }, - }, - }, - }, - }, - }, + Name: "required fields set", + Vals: requiredFields, }, capitest.VariableTestDef{ - Name: "invalid boot type", - Vals: v1alpha1.ClusterConfigSpec{ - ControlPlane: &v1alpha1.NodeConfigSpec{ - Nutanix: &v1alpha1.NutanixNodeSpec{ - MachineDetails: v1alpha1.NutanixMachineDetails{ - BootType: "invalid", - VCPUSockets: 2, - VCPUsPerSocket: 1, - Image: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testImageName, - }, - Cluster: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testPEClusterName, - }, - MemorySize: resource.MustParse("8Gi"), - SystemDiskSize: resource.MustParse("40Gi"), - Subnets: []v1alpha1.NutanixResourceIdentifier{}, - }, - }, - }, - }, + Name: "additional categories set", + Vals: withAdditionalCategories, + }, + capitest.VariableTestDef{ + Name: "project set", + Vals: withProject, + }, + capitest.VariableTestDef{ + Name: "invalid boot type", + Vals: invalidBootType, ExpectError: true, }, capitest.VariableTestDef{ - Name: "invalid image type", - Vals: v1alpha1.ClusterConfigSpec{ - ControlPlane: &v1alpha1.NodeConfigSpec{ - Nutanix: &v1alpha1.NutanixNodeSpec{ - MachineDetails: v1alpha1.NutanixMachineDetails{ - BootType: v1alpha1.NutanixBootType(capxv1.NutanixBootTypeLegacy), - VCPUSockets: 2, - VCPUsPerSocket: 1, - Image: v1alpha1.NutanixResourceIdentifier{ - Type: "invalid", - Name: &testImageName, - }, - Cluster: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testPEClusterName, - }, - MemorySize: resource.MustParse("8Gi"), - SystemDiskSize: resource.MustParse("40Gi"), - Subnets: []v1alpha1.NutanixResourceIdentifier{}, - }, - }, - }, - }, + Name: "invalid image type", + Vals: invalidImageType, + ExpectError: true, + }, + capitest.VariableTestDef{ + Name: "invalid cluster type", + Vals: invalidClusterType, ExpectError: true, }, capitest.VariableTestDef{ - Name: "invalid cluster type", - Vals: v1alpha1.ClusterConfigSpec{ - ControlPlane: &v1alpha1.NodeConfigSpec{ - Nutanix: &v1alpha1.NutanixNodeSpec{ - MachineDetails: v1alpha1.NutanixMachineDetails{ - BootType: v1alpha1.NutanixBootType(capxv1.NutanixBootTypeLegacy), - VCPUSockets: 2, - VCPUsPerSocket: 1, - Image: v1alpha1.NutanixResourceIdentifier{ - Type: capxv1.NutanixIdentifierName, - Name: &testImageName, - }, - Cluster: v1alpha1.NutanixResourceIdentifier{ - Type: "invalid", - Name: &testPEClusterName, - }, - MemorySize: resource.MustParse("8Gi"), - SystemDiskSize: resource.MustParse("40Gi"), - Subnets: []v1alpha1.NutanixResourceIdentifier{}, - }, + Name: "invalid project type", + Vals: invalidProjectType, + ExpectError: true, + }, + ) +} + +func minimumClusterConfigSpec() v1alpha1.ClusterConfigSpec { + return v1alpha1.ClusterConfigSpec{ + ControlPlane: &v1alpha1.NodeConfigSpec{ + Nutanix: &v1alpha1.NutanixNodeSpec{ + MachineDetails: v1alpha1.NutanixMachineDetails{ + BootType: v1alpha1.NutanixBootType(capxv1.NutanixBootTypeLegacy), + VCPUSockets: 2, + VCPUsPerSocket: 1, + Image: v1alpha1.NutanixResourceIdentifier{ + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-image"), + }, + Cluster: v1alpha1.NutanixResourceIdentifier{ + Type: capxv1.NutanixIdentifierName, + Name: ptr.To("fake-pe-cluster"), }, + MemorySize: resource.MustParse("8Gi"), + SystemDiskSize: resource.MustParse("40Gi"), + Subnets: []v1alpha1.NutanixResourceIdentifier{}, }, }, - ExpectError: true, }, - ) + } }