From c73ce72b1e7a5063fbebfc4e60c5b9170f3637d7 Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Wed, 6 Aug 2025 13:38:25 +0100 Subject: [PATCH 1/3] feat: Add EKS handlers Reusing as much as possible of the AWS handlers, with slight tweaks. --- api/v1alpha1/clusterconfig_types.go | 43 ++- api/v1alpha1/constants.go | 2 + .../caren.nutanix.com_eksclusterconfigs.yaml | 348 ++++++++++++++++++ ...aren.nutanix.com_eksworkernodeconfigs.yaml | 137 +++++++ ...ren.nutanix.com_genericclusterconfigs.yaml | 156 ++++++++ api/v1alpha1/eks_clusterconfig_types.go | 14 + api/v1alpha1/nodeconfig_types.go | 41 ++- api/v1alpha1/zz_generated.deepcopy.go | 172 ++++++++- api/variables/aggregate_types.go | 6 +- cmd/main.go | 6 + .../patches/selectors/selectors.go | 14 + go.mod | 1 + go.sum | 2 + pkg/handlers/aws/mutation/ami/inject.go | 2 +- .../aws/mutation/ami/inject_control_plane.go | 2 +- .../aws/mutation/ami/inject_worker.go | 2 +- .../iaminstanceprofile/inject_suite_test.go | 2 +- .../iaminstanceprofile/inject_worker.go | 4 +- .../mutation/instancetype/inject_worker.go | 4 +- .../mutation/placementgroup/inject_worker.go | 4 +- .../aws/mutation/securitygroups/inject.go | 2 +- .../securitygroups/inject_control_plane.go | 2 +- .../mutation/securitygroups/inject_worker.go | 2 +- pkg/handlers/eks/clusterconfig/variables.go | 48 +++ pkg/handlers/eks/handlers.go | 34 ++ .../eks/mutation/ami/inject_suite_test.go | 16 + .../eks/mutation/ami/inject_worker.go | 25 ++ .../eks/mutation/ami/inject_worker_test.go | 111 ++++++ pkg/handlers/eks/mutation/handlers.go | 27 ++ .../iaminstanceprofile/inject_suite_test.go | 16 + .../iaminstanceprofile/inject_worker.go | 18 + .../iaminstanceprofile/inject_worker_test.go | 64 ++++ .../instancetype/inject_suite_test.go | 16 + .../mutation/instancetype/inject_worker.go | 18 + .../instancetype/inject_worker_test.go | 64 ++++ .../eks/mutation/metapatch_handler.go | 51 +++ pkg/handlers/eks/mutation/network/inject.go | 127 +++++++ .../eks/mutation/network/inject_test.go | 120 ++++++ .../eks/mutation/network/variables_test.go | 67 ++++ .../placementgroup/inject_suite_test.go | 16 + .../mutation/placementgroup/inject_worker.go | 18 + .../placementgroup/inject_worker_test.go | 64 ++++ pkg/handlers/eks/mutation/region/inject.go | 108 ++++++ .../eks/mutation/region/inject_test.go | 64 ++++ .../eks/mutation/region/variables_test.go | 32 ++ .../securitygroups/inject_suite_test.go | 16 + .../mutation/securitygroups/inject_worker.go | 25 ++ .../securitygroups/inject_worker_test.go | 80 ++++ .../eks/mutation/testutils/request.go | 78 ++++ pkg/handlers/eks/workerconfig/variables.go | 48 +++ .../eks/workerconfig/variables_test.go | 39 ++ .../mutation/coredns/variables_test.go | 56 +-- .../generic/mutation/etcd/variables_test.go | 30 +- .../mutation/httpproxy/variables_test.go | 10 +- .../credentials/variables_test.go | 52 +-- .../generic/mutation/kubeproxymode/inject.go | 43 ++- .../mutation/kubeproxymode/inject_test.go | 51 ++- .../variables_test.go | 8 +- .../mutation/mirrors/variables_test.go | 30 +- pkg/handlers/generic/mutation/ntp/inject.go | 18 + .../generic/mutation/ntp/inject_test.go | 32 ++ .../generic/mutation/ntp/variables_test.go | 18 +- .../generic/mutation/taints/inject_worker.go | 49 ++- .../mutation/taints/inject_worker_test.go | 66 ++++ pkg/handlers/generic/mutation/users/inject.go | 56 ++- .../generic/mutation/users/inject_test.go | 25 ++ .../generic/mutation/users/variables_test.go | 24 +- pkg/webhook/addons/registry/webhook_test.go | 2 +- .../preflight/generic/registry_test.go | 34 +- 69 files changed, 2836 insertions(+), 146 deletions(-) create mode 100644 api/v1alpha1/crds/caren.nutanix.com_eksclusterconfigs.yaml create mode 100644 api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml create mode 100644 api/v1alpha1/eks_clusterconfig_types.go create mode 100644 pkg/handlers/eks/clusterconfig/variables.go create mode 100644 pkg/handlers/eks/handlers.go create mode 100644 pkg/handlers/eks/mutation/ami/inject_suite_test.go create mode 100644 pkg/handlers/eks/mutation/ami/inject_worker.go create mode 100644 pkg/handlers/eks/mutation/ami/inject_worker_test.go create mode 100644 pkg/handlers/eks/mutation/handlers.go create mode 100644 pkg/handlers/eks/mutation/iaminstanceprofile/inject_suite_test.go create mode 100644 pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker.go create mode 100644 pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker_test.go create mode 100644 pkg/handlers/eks/mutation/instancetype/inject_suite_test.go create mode 100644 pkg/handlers/eks/mutation/instancetype/inject_worker.go create mode 100644 pkg/handlers/eks/mutation/instancetype/inject_worker_test.go create mode 100644 pkg/handlers/eks/mutation/metapatch_handler.go create mode 100644 pkg/handlers/eks/mutation/network/inject.go create mode 100644 pkg/handlers/eks/mutation/network/inject_test.go create mode 100644 pkg/handlers/eks/mutation/network/variables_test.go create mode 100644 pkg/handlers/eks/mutation/placementgroup/inject_suite_test.go create mode 100644 pkg/handlers/eks/mutation/placementgroup/inject_worker.go create mode 100644 pkg/handlers/eks/mutation/placementgroup/inject_worker_test.go create mode 100644 pkg/handlers/eks/mutation/region/inject.go create mode 100644 pkg/handlers/eks/mutation/region/inject_test.go create mode 100644 pkg/handlers/eks/mutation/region/variables_test.go create mode 100644 pkg/handlers/eks/mutation/securitygroups/inject_suite_test.go create mode 100644 pkg/handlers/eks/mutation/securitygroups/inject_worker.go create mode 100644 pkg/handlers/eks/mutation/securitygroups/inject_worker_test.go create mode 100644 pkg/handlers/eks/mutation/testutils/request.go create mode 100644 pkg/handlers/eks/workerconfig/variables.go create mode 100644 pkg/handlers/eks/workerconfig/variables_test.go diff --git a/api/v1alpha1/clusterconfig_types.go b/api/v1alpha1/clusterconfig_types.go index 9b995cadd..05d520466 100644 --- a/api/v1alpha1/clusterconfig_types.go +++ b/api/v1alpha1/clusterconfig_types.go @@ -34,6 +34,8 @@ var ( nutanixClusterConfigCRDDefinition []byte //go:embed crds/caren.nutanix.com_genericclusterconfigs.yaml genericClusterConfigCRDDefinition []byte + //go:embed crds/caren.nutanix.com_eksclusterconfigs.yaml + eksClusterConfigCRDDefinition []byte dockerClusterConfigVariableSchema = variables.MustSchemaFromCRDYAML( dockerClusterConfigCRDDefinition, @@ -47,6 +49,9 @@ var ( genericClusterConfigVariableSchema = variables.MustSchemaFromCRDYAML( genericClusterConfigCRDDefinition, ) + eksClusterConfigVariableSchema = variables.MustSchemaFromCRDYAML( + eksClusterConfigCRDDefinition, + ) ) // +kubebuilder:object:root=true @@ -70,7 +75,7 @@ type AWSClusterConfigSpec struct { // +kubebuilder:validation:Optional AWS *AWSSpec `json:"aws,omitempty"` - GenericClusterConfigSpec `json:",inline"` + GenericClusterConfigResource `json:",inline"` // +kubebuilder:validation:Optional Addons *AWSAddons `json:"addons,omitempty"` @@ -105,7 +110,7 @@ type DockerClusterConfigSpec struct { // +kubebuilder:validation:Optional Docker *DockerSpec `json:"docker,omitempty"` - GenericClusterConfigSpec `json:",inline"` + GenericClusterConfigResource `json:",inline"` // +kubebuilder:validation:Optional Addons *DockerAddons `json:"addons,omitempty"` @@ -145,7 +150,7 @@ type NutanixClusterConfigSpec struct { // +kubebuilder:validation:Optional Nutanix *NutanixSpec `json:"nutanix,omitempty"` - GenericClusterConfigSpec `json:",inline"` + GenericClusterConfigResource `json:",inline"` // +kubebuilder:validation:Optional Addons *NutanixAddons `json:"addons,omitempty"` @@ -187,6 +192,13 @@ func (s GenericClusterConfig) VariableSchema() clusterv1.VariableSchema { //noli // GenericClusterConfigSpec defines the desired state of GenericClusterConfig. type GenericClusterConfigSpec struct { + GenericClusterConfigResource `json:",inline"` + + // +kubebuilder:validation:Optional + Addons *GenericAddons `json:"addons,omitempty"` +} + +type GenericClusterConfigResource struct { // Sets the Kubernetes image repository used for the KubeadmControlPlane. // +kubebuilder:validation:Optional // +kubebuilder:validation:Pattern=`^((?:[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*|\[(?:[a-fA-F0-9:]+)\])(:[0-9]+)?/)?[a-z0-9]+((?:[._]|__|[-]+)[a-z0-9]+)*(/[a-z0-9]+((?:[._]|__|[-]+)[a-z0-9]+)*)*$` @@ -222,6 +234,31 @@ type GenericClusterConfigSpec struct { NTP *NTP `json:"ntp,omitempty"` } +// +kubebuilder:object:root=true + +// EKSClusterConfig is the Schema for the eksclusterconfigs API. +type EKSClusterConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +kubebuilder:validation:Optional + Spec EKSClusterConfigSpec `json:"spec,omitempty"` +} + +func (s EKSClusterConfig) VariableSchema() clusterv1.VariableSchema { //nolint:gocritic,lll // Passed by value for no potential side-effect. + return eksClusterConfigVariableSchema +} + +// EKSClusterConfigSpec defines the desired state of ClusterConfig. +type EKSClusterConfigSpec struct { + // EKS cluster configuration. + // +kubebuilder:validation:Optional + EKS *EKSSpec `json:"eks,omitempty"` + + // +kubebuilder:validation:Optional + Addons *AWSAddons `json:"addons,omitempty"` +} + type Image struct { // Repository is used to override the image repository to pull from. // +kubebuilder:validation:Optional diff --git a/api/v1alpha1/constants.go b/api/v1alpha1/constants.go index e208fec80..1093bd273 100644 --- a/api/v1alpha1/constants.go +++ b/api/v1alpha1/constants.go @@ -17,6 +17,8 @@ const ( DockerVariableName = "docker" // NutanixVariableName is the Nutanix config patch variable name. NutanixVariableName = "nutanix" + // AWSVariableName is the EKS config patch variable name. + EKSVariableName = "eks" // CNIVariableName is the CNI external patch variable name. CNIVariableName = "cni" diff --git a/api/v1alpha1/crds/caren.nutanix.com_eksclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_eksclusterconfigs.yaml new file mode 100644 index 000000000..29e81815a --- /dev/null +++ b/api/v1alpha1/crds/caren.nutanix.com_eksclusterconfigs.yaml @@ -0,0 +1,348 @@ +# Copyright 2024 Nutanix. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: eksclusterconfigs.caren.nutanix.com +spec: + group: caren.nutanix.com + names: + kind: EKSClusterConfig + listKind: EKSClusterConfigList + plural: eksclusterconfigs + singular: eksclusterconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: EKSClusterConfig is the Schema for the eksclusterconfigs API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: EKSClusterConfigSpec defines the desired state of ClusterConfig. + properties: + addons: + properties: + ccm: + description: CCM tells us to enable or disable the cloud provider + interface. + properties: + credentials: + description: A reference to the Secret for credential information + for the target Prism Central instance + properties: + secretRef: + description: A reference to the Secret containing the + credentials used by the CCM provider. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + minLength: 1 + type: string + required: + - name + type: object + required: + - secretRef + type: object + strategy: + default: HelmAddon + description: Addon strategy used to deploy the CCM to the + workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + clusterAutoscaler: + description: ClusterAutoscaler tells us to enable or disable the + cluster-autoscaler addon. + properties: + strategy: + default: HelmAddon + description: |- + Addon strategy used to deploy cluster-autoscaler to the management cluster + targeting the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + cni: + description: CNI required for providing CNI configuration. + properties: + provider: + description: CNI provider to deploy. + enum: + - Calico + - Cilium + type: string + strategy: + default: HelmAddon + description: Addon strategy used to deploy the CNI provider + to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + values: + description: Values contains the helm values for the CNI when + HelmAddon is the strategy. + properties: + sourceRef: + description: |- + SourceRef is an object reference to Configmap/Secret inside the same namespace + which contains inline YAML representing the values for the Helm chart. + properties: + kind: + description: Kind is the type of resource being referenced, + valid values are ('ConfigMap'). + enum: + - ConfigMap + type: string + name: + description: Name is the name of resource being referenced. + minLength: 1 + type: string + required: + - kind + - name + type: object + type: object + required: + - provider + type: object + csi: + properties: + defaultStorage: + properties: + provider: + description: Name of the CSI Provider for the default + storage class. + enum: + - aws-ebs + - nutanix + - local-path + type: string + storageClassConfig: + description: Name of the default storage class config + the specified default provider. + minLength: 1 + type: string + required: + - provider + - storageClassConfig + type: object + providers: + properties: + aws-ebs: + properties: + credentials: + description: The reference to any secret used by the + CSI Provider. + properties: + secretRef: + description: A reference to the Secret containing + the credentials used by the CSI provider. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + minLength: 1 + type: string + required: + - name + type: object + required: + - secretRef + type: object + storageClassConfigs: + additionalProperties: + properties: + allowExpansion: + default: false + description: If the storage class should allow + volume expanding + type: boolean + parameters: + additionalProperties: + type: string + description: Parameters passed into the storage + class object. + type: object + reclaimPolicy: + default: Delete + description: PersistentVolumeReclaimPolicy describes + a policy for end-of-life maintenance of persistent + volumes. + enum: + - Delete + - Retain + - Recycle + type: string + volumeBindingMode: + default: WaitForFirstConsumer + description: VolumeBindingMode indicates how + PersistentVolumeClaims should be bound. + enum: + - Immediate + - WaitForFirstConsumer + type: string + type: object + description: StorageClassConfigs is a map of storage + class configurations for this CSI provider. + type: object + strategy: + default: HelmAddon + description: Addon strategy used to deploy the CSI + provider to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + required: + - storageClassConfigs + type: object + required: + - aws-ebs + type: object + snapshotController: + description: Deploy the CSI snapshot controller and associated + CRDs. + properties: + strategy: + default: HelmAddon + description: Addon strategy used to deploy the snapshot + controller to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + required: + - defaultStorage + - providers + type: object + nfd: + description: NFD tells us to enable or disable the node feature + discovery addon. + properties: + strategy: + default: HelmAddon + description: Addon strategy used to deploy Node Feature Discovery + (NFD) to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + registry: + properties: + provider: + default: CNCF Distribution + description: The OCI registry provider to deploy. + enum: + - CNCF Distribution + type: string + required: + - provider + type: object + serviceLoadBalancer: + properties: + configuration: + description: Configuration for the chosen ServiceLoadBalancer + provider. + properties: + addressRanges: + description: |- + AddressRanges is a list of IPv4 address ranges the + provider uses to choose an address for a load balancer. + items: + description: AddressRange defines an IPv4 range. + properties: + end: + format: ipv4 + type: string + start: + format: ipv4 + type: string + required: + - end + - start + type: object + minItems: 1 + type: array + required: + - addressRanges + type: object + provider: + description: |- + The LoadBalancer-type Service provider to deploy. Not required in infrastructures where + the CCM acts as the provider. + enum: + - MetalLB + type: string + required: + - provider + type: object + type: object + eks: + description: EKS cluster configuration. + properties: + network: + description: AWS network configuration. + properties: + subnets: + description: AWS Subnet configuration. + items: + description: SubnetSpec configures an AWS Subnet. + properties: + id: + description: Existing Subnet ID to use for the cluster. + minLength: 1 + type: string + required: + - id + type: object + type: array + vpc: + properties: + id: + description: Existing VPC ID to use for the cluster. + minLength: 1 + type: string + required: + - id + type: object + type: object + region: + description: AWS region to create cluster in. + type: string + type: object + type: object + type: object + served: true + storage: true diff --git a/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml new file mode 100644 index 000000000..2de646a5c --- /dev/null +++ b/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml @@ -0,0 +1,137 @@ +# Copyright 2024 Nutanix. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: eksworkernodeconfigs.caren.nutanix.com +spec: + group: caren.nutanix.com + names: + kind: EKSWorkerNodeConfig + listKind: EKSWorkerNodeConfigList + plural: eksworkernodeconfigs + singular: eksworkernodeconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: EKSWorkerNodeConfig is the Schema for the eksnodeconfigs API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + EKSWorkerNodeConfigSpec defines the desired state of EKSNodeConfig. + Place any configuration that can be applied to individual Nodes here. + Otherwise, it should go into the ClusterConfigSpec. + properties: + eks: + properties: + additionalSecurityGroups: + items: + properties: + id: + description: ID is the id of the security group + type: string + type: object + type: array + ami: + description: |- + AMI or AMI Lookup arguments for machine image of a AWS machine. + If both AMI ID and AMI lookup arguments are provided then AMI ID takes precedence + properties: + id: + description: AMI ID is the reference to the AMI from which + to create the machine instance. + type: string + lookup: + description: Lookup is the lookup arguments for the AMI. + properties: + baseOS: + description: The name of the base os for image lookup + type: string + format: + description: |- + AMI naming format. Supports substitutions for {{.BaseOS}} and {{.K8sVersion}} with the + base OS and kubernetes version. + example: capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-* + type: string + org: + description: The AWS Organization ID to use for image + lookup. + type: string + type: object + type: object + iamInstanceProfile: + default: nodes.cluster-api-provider-aws.sigs.k8s.io + description: The IAM instance profile to use for the cluster Machines. + type: string + instanceType: + default: m5.2xlarge + description: The AWS instance type to use for the cluster Machines. + type: string + placementGroupName: + description: PlacementGroup specifies the placement group in which + to launch the instance. + properties: + name: + description: Name is the name of the placement group. + maxLength: 255 + minLength: 1 + type: string + required: + - name + type: object + type: object + taints: + description: Taints specifies the taints the Node API object should + be registered with. + items: + description: |- + The node this Taint is attached to has the "effect" on + any pod that does not tolerate the Taint. + properties: + effect: + default: NoSchedule + description: |- + The effect of the taint on pods that do not tolerate the taint. + Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + enum: + - NoSchedule + - PreferNoSchedule + - NoExecute + type: string + key: + description: The taint key to be applied to a node. + type: string + value: + description: The taint value corresponding to the taint key. + type: string + required: + - effect + - key + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml index 93abf1486..ddaebdaae 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml @@ -50,6 +50,162 @@ spec: spec: description: GenericClusterConfigSpec defines the desired state of GenericClusterConfig. properties: + addons: + properties: + ccm: + description: CCM tells us to enable or disable the cloud provider + interface. + properties: + credentials: + description: A reference to the Secret for credential information + for the target Prism Central instance + properties: + secretRef: + description: A reference to the Secret containing the + credentials used by the CCM provider. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + minLength: 1 + type: string + required: + - name + type: object + required: + - secretRef + type: object + strategy: + default: HelmAddon + description: Addon strategy used to deploy the CCM to the + workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + clusterAutoscaler: + description: ClusterAutoscaler tells us to enable or disable the + cluster-autoscaler addon. + properties: + strategy: + default: HelmAddon + description: |- + Addon strategy used to deploy cluster-autoscaler to the management cluster + targeting the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + cni: + description: CNI required for providing CNI configuration. + properties: + provider: + description: CNI provider to deploy. + enum: + - Calico + - Cilium + type: string + strategy: + default: HelmAddon + description: Addon strategy used to deploy the CNI provider + to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + values: + description: Values contains the helm values for the CNI when + HelmAddon is the strategy. + properties: + sourceRef: + description: |- + SourceRef is an object reference to Configmap/Secret inside the same namespace + which contains inline YAML representing the values for the Helm chart. + properties: + kind: + description: Kind is the type of resource being referenced, + valid values are ('ConfigMap'). + enum: + - ConfigMap + type: string + name: + description: Name is the name of resource being referenced. + minLength: 1 + type: string + required: + - kind + - name + type: object + type: object + required: + - provider + type: object + nfd: + description: NFD tells us to enable or disable the node feature + discovery addon. + properties: + strategy: + default: HelmAddon + description: Addon strategy used to deploy Node Feature Discovery + (NFD) to the workload cluster. + enum: + - ClusterResourceSet + - HelmAddon + type: string + type: object + registry: + properties: + provider: + default: CNCF Distribution + description: The OCI registry provider to deploy. + enum: + - CNCF Distribution + type: string + required: + - provider + type: object + serviceLoadBalancer: + properties: + configuration: + description: Configuration for the chosen ServiceLoadBalancer + provider. + properties: + addressRanges: + description: |- + AddressRanges is a list of IPv4 address ranges the + provider uses to choose an address for a load balancer. + items: + description: AddressRange defines an IPv4 range. + properties: + end: + format: ipv4 + type: string + start: + format: ipv4 + type: string + required: + - end + - start + type: object + minItems: 1 + type: array + required: + - addressRanges + type: object + provider: + description: |- + The LoadBalancer-type Service provider to deploy. Not required in infrastructures where + the CCM acts as the provider. + enum: + - MetalLB + type: string + required: + - provider + type: object + type: object dns: description: DNS defines the DNS configuration for the cluster. properties: diff --git a/api/v1alpha1/eks_clusterconfig_types.go b/api/v1alpha1/eks_clusterconfig_types.go new file mode 100644 index 000000000..1ba473a2b --- /dev/null +++ b/api/v1alpha1/eks_clusterconfig_types.go @@ -0,0 +1,14 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +type EKSSpec struct { + // AWS region to create cluster in. + // +kubebuilder:validation:Optional + Region *Region `json:"region,omitempty"` + + // AWS network configuration. + // +kubebuilder:validation:Optional + Network *AWSNetwork `json:"network,omitempty"` +} diff --git a/api/v1alpha1/nodeconfig_types.go b/api/v1alpha1/nodeconfig_types.go index 8fff34b84..c678a3244 100644 --- a/api/v1alpha1/nodeconfig_types.go +++ b/api/v1alpha1/nodeconfig_types.go @@ -19,14 +19,17 @@ var ( awsNodeConfigCRDDefinition []byte //go:embed crds/caren.nutanix.com_nutanixworkernodeconfigs.yaml nutanixNodeConfigCRDDefinition []byte + //go:embed crds/caren.nutanix.com_eksworkernodeconfigs.yaml + eksNodeConfigCRDDefinition []byte dockerNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML( dockerNodeConfigCRDDefinition, ) - awsWorkerNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML(awsNodeConfigCRDDefinition) - nutanixNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML( + awsNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML(awsNodeConfigCRDDefinition) + nutanixNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML( nutanixNodeConfigCRDDefinition, ) + eksNodeConfigVariableSchema = variables.MustSchemaFromCRDYAML(eksNodeConfigCRDDefinition) ) // +kubebuilder:object:root=true @@ -41,7 +44,7 @@ type AWSWorkerNodeConfig struct { } func (s AWSWorkerNodeConfig) VariableSchema() clusterv1.VariableSchema { //nolint:gocritic,lll // Passed by value for no potential side-effect. - return awsWorkerNodeConfigVariableSchema + return awsNodeConfigVariableSchema } // AWSWorkerNodeConfigSpec defines the desired state of AWSNodeConfig. @@ -113,6 +116,37 @@ type GenericNodeSpec struct { NodeRegistration *NodeRegistrationOptions `json:"nodeRegistration,omitempty"` } +// +kubebuilder:object:root=true + +// EKSWorkerNodeConfig is the Schema for the eksnodeconfigs API. +type EKSWorkerNodeConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +kubebuilder:validation:Optional + Spec EKSWorkerNodeConfigSpec `json:"spec,omitempty"` +} + +func (s EKSWorkerNodeConfig) VariableSchema() clusterv1.VariableSchema { //nolint:gocritic,lll // Passed by value for no potential side-effect. + return eksNodeConfigVariableSchema +} + +// EKSWorkerNodeConfigSpec defines the desired state of EKSNodeConfig. +// Place any configuration that can be applied to individual Nodes here. +// Otherwise, it should go into the ClusterConfigSpec. +type EKSWorkerNodeConfigSpec struct { + // +kubebuilder:validation:Optional + EKS *AWSWorkerNodeSpec `json:"eks,omitempty"` + + EKSNodeSpec `json:",inline"` +} + +type EKSNodeSpec struct { + // Taints specifies the taints the Node API object should be registered with. + // +kubebuilder:validation:Optional + Taints []Taint `json:"taints,omitempty"` +} + // The node this Taint is attached to has the "effect" on // any pod that does not tolerate the Taint. type Taint struct { @@ -170,5 +204,6 @@ func init() { &AWSWorkerNodeConfig{}, &DockerWorkerNodeConfig{}, &NutanixWorkerNodeConfig{}, + &EKSWorkerNodeConfig{}, ) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 94fe6de30..21871a194 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -153,7 +153,7 @@ func (in *AWSClusterConfigSpec) DeepCopyInto(out *AWSClusterConfigSpec) { *out = new(AWSSpec) (*in).DeepCopyInto(*out) } - in.GenericClusterConfigSpec.DeepCopyInto(&out.GenericClusterConfigSpec) + in.GenericClusterConfigResource.DeepCopyInto(&out.GenericClusterConfigResource) if in.Addons != nil { in, out := &in.Addons, &out.Addons *out = new(AWSAddons) @@ -825,7 +825,7 @@ func (in *DockerClusterConfigSpec) DeepCopyInto(out *DockerClusterConfigSpec) { *out = new(DockerSpec) **out = **in } - in.GenericClusterConfigSpec.DeepCopyInto(&out.GenericClusterConfigSpec) + in.GenericClusterConfigResource.DeepCopyInto(&out.GenericClusterConfigResource) if in.Addons != nil { in, out := &in.Addons, &out.Addons *out = new(DockerAddons) @@ -957,6 +957,149 @@ func (in *DockerWorkerNodeConfigSpec) DeepCopy() *DockerWorkerNodeConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSClusterConfig) DeepCopyInto(out *EKSClusterConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSClusterConfig. +func (in *EKSClusterConfig) DeepCopy() *EKSClusterConfig { + if in == nil { + return nil + } + out := new(EKSClusterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EKSClusterConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSClusterConfigSpec) DeepCopyInto(out *EKSClusterConfigSpec) { + *out = *in + if in.EKS != nil { + in, out := &in.EKS, &out.EKS + *out = new(EKSSpec) + (*in).DeepCopyInto(*out) + } + if in.Addons != nil { + in, out := &in.Addons, &out.Addons + *out = new(AWSAddons) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSClusterConfigSpec. +func (in *EKSClusterConfigSpec) DeepCopy() *EKSClusterConfigSpec { + if in == nil { + return nil + } + out := new(EKSClusterConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSNodeSpec) DeepCopyInto(out *EKSNodeSpec) { + *out = *in + if in.Taints != nil { + in, out := &in.Taints, &out.Taints + *out = make([]Taint, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSNodeSpec. +func (in *EKSNodeSpec) DeepCopy() *EKSNodeSpec { + if in == nil { + return nil + } + out := new(EKSNodeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSSpec) DeepCopyInto(out *EKSSpec) { + *out = *in + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(Region) + **out = **in + } + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(AWSNetwork) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSSpec. +func (in *EKSSpec) DeepCopy() *EKSSpec { + if in == nil { + return nil + } + out := new(EKSSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSWorkerNodeConfig) DeepCopyInto(out *EKSWorkerNodeConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSWorkerNodeConfig. +func (in *EKSWorkerNodeConfig) DeepCopy() *EKSWorkerNodeConfig { + if in == nil { + return nil + } + out := new(EKSWorkerNodeConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EKSWorkerNodeConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EKSWorkerNodeConfigSpec) DeepCopyInto(out *EKSWorkerNodeConfigSpec) { + *out = *in + if in.EKS != nil { + in, out := &in.EKS, &out.EKS + *out = new(AWSWorkerNodeSpec) + (*in).DeepCopyInto(*out) + } + in.EKSNodeSpec.DeepCopyInto(&out.EKSNodeSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSWorkerNodeConfigSpec. +func (in *EKSWorkerNodeConfigSpec) DeepCopy() *EKSWorkerNodeConfigSpec { + if in == nil { + return nil + } + out := new(EKSWorkerNodeConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EncryptionAtRest) DeepCopyInto(out *EncryptionAtRest) { *out = *in @@ -1142,7 +1285,7 @@ func (in *GenericClusterConfig) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GenericClusterConfigSpec) DeepCopyInto(out *GenericClusterConfigSpec) { +func (in *GenericClusterConfigResource) DeepCopyInto(out *GenericClusterConfigResource) { *out = *in if in.KubernetesImageRepository != nil { in, out := &in.KubernetesImageRepository, &out.KubernetesImageRepository @@ -1200,6 +1343,27 @@ func (in *GenericClusterConfigSpec) DeepCopyInto(out *GenericClusterConfigSpec) } } +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterConfigResource. +func (in *GenericClusterConfigResource) DeepCopy() *GenericClusterConfigResource { + if in == nil { + return nil + } + out := new(GenericClusterConfigResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericClusterConfigSpec) DeepCopyInto(out *GenericClusterConfigSpec) { + *out = *in + in.GenericClusterConfigResource.DeepCopyInto(&out.GenericClusterConfigResource) + if in.Addons != nil { + in, out := &in.Addons, &out.Addons + *out = new(GenericAddons) + (*in).DeepCopyInto(*out) + } +} + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterConfigSpec. func (in *GenericClusterConfigSpec) DeepCopy() *GenericClusterConfigSpec { if in == nil { @@ -1529,7 +1693,7 @@ func (in *NutanixClusterConfigSpec) DeepCopyInto(out *NutanixClusterConfigSpec) *out = new(NutanixSpec) (*in).DeepCopyInto(*out) } - in.GenericClusterConfigSpec.DeepCopyInto(&out.GenericClusterConfigSpec) + in.GenericClusterConfigResource.DeepCopyInto(&out.GenericClusterConfigResource) if in.Addons != nil { in, out := &in.Addons, &out.Addons *out = new(NutanixAddons) diff --git a/api/variables/aggregate_types.go b/api/variables/aggregate_types.go index df225e88b..c366e20c2 100644 --- a/api/variables/aggregate_types.go +++ b/api/variables/aggregate_types.go @@ -20,7 +20,9 @@ type ClusterConfigSpec struct { Nutanix *carenv1.NutanixSpec `json:"nutanix,omitempty"` - carenv1.GenericClusterConfigSpec `json:",inline"` + EKS *carenv1.EKSSpec `json:"eks,omitempty"` + + carenv1.GenericClusterConfigResource `json:",inline"` Addons *Addons `json:"addons,omitempty"` @@ -48,6 +50,8 @@ type WorkerNodeConfigSpec struct { Nutanix *carenv1.NutanixWorkerNodeSpec `json:"nutanix,omitempty"` + EKS *carenv1.AWSWorkerNodeSpec `json:"eks,omitempty"` + carenv1.GenericNodeSpec `json:",inline"` } diff --git a/cmd/main.go b/cmd/main.go index 37397d4be..a5da106f9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -39,6 +39,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/feature" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/docker" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix" @@ -124,6 +125,10 @@ func main() { // It allows to specify configuration under a single variable. genericMetaHandlers := generic.New() + // eksMetaHandlers combines all EKS patch and variable handlers under a single handler. + // It allows to specify configuration under a single variable. + eksMetaHandlers := eks.New(globalOptions) + namespacesyncOptions := namespacesync.Options{} enforceClusterAutoscalerLimitsOptions := enforceclusterautoscalerlimits.Options{} failureDomainRolloutOptions := failuredomainrollout.Options{} @@ -173,6 +178,7 @@ func main() { allHandlers = append(allHandlers, dockerMetaHandlers.AllHandlers(mgr)...) allHandlers = append(allHandlers, nutanixMetaHandlers.AllHandlers(mgr)...) allHandlers = append(allHandlers, genericMetaHandlers.AllHandlers(mgr)...) + allHandlers = append(allHandlers, eksMetaHandlers.AllHandlers(mgr)...) runtimeWebhookServer := server.NewServer(runtimeWebhookServerOpts, allHandlers...) diff --git a/common/pkg/capi/clustertopology/patches/selectors/selectors.go b/common/pkg/capi/clustertopology/patches/selectors/selectors.go index cfc6a08db..dcee9fb8c 100644 --- a/common/pkg/capi/clustertopology/patches/selectors/selectors.go +++ b/common/pkg/capi/clustertopology/patches/selectors/selectors.go @@ -34,6 +34,20 @@ func WorkersKubeadmConfigTemplateSelector() clusterv1.PatchSelector { } } +func WorkersConfigTemplateSelector(capiInfrastructureAPIVersion, kind string) clusterv1.PatchSelector { + return clusterv1.PatchSelector{ + APIVersion: capiInfrastructureAPIVersion, + Kind: kind, + MatchResources: clusterv1.PatchSelectorMatch{ + MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ + Names: []string{ + "*", + }, + }, + }, + } +} + // InfrastructureCluster selector matches against infrastructure clusters. // Passing in the API version (not the API group) is required because different providers could support different API // versions. This also allows for a patch to select multiple infrastructure versions for the same provider. diff --git a/go.mod b/go.mod index 002870e94..8457764b6 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.8 // indirect github.com/aws/aws-sdk-go-v2/service/ec2 v1.240.0 // indirect + github.com/aws/aws-sdk-go-v2/service/eks v1.69.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect diff --git a/go.sum b/go.sum index d723b1354..6b997200a 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= github.com/aws/aws-sdk-go-v2/service/ec2 v1.240.0 h1:/NUzag+6BGBNvM7FEHDsDK8itSgWEUVhmC2HDBR8NrM= github.com/aws/aws-sdk-go-v2/service/ec2 v1.240.0/go.mod h1:HDxGArx3/bUnkoFsuvTNIxEj/cR3f+IgsVh1B7Pvay8= +github.com/aws/aws-sdk-go-v2/service/eks v1.69.0 h1:eiZOCsKGl0D7M3FSeSJwJbsikxowCMVz513WDFCe6HY= +github.com/aws/aws-sdk-go-v2/service/eks v1.69.0/go.mod h1:u3CDoNUAkSIGKNiA6LfQtApPmHPGRuAjikx3ObM5XBs= github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/pkg/handlers/aws/mutation/ami/inject.go b/pkg/handlers/aws/mutation/ami/inject.go index bf75d3e07..9a780dbc2 100644 --- a/pkg/handlers/aws/mutation/ami/inject.go +++ b/pkg/handlers/aws/mutation/ami/inject.go @@ -31,7 +31,7 @@ type awsAMISpecPatchHandler struct { patchSelector clusterv1.PatchSelector } -func newAWSAMISpecPatchHandler( +func NewAWSAMISpecPatchHandler( metaVariableName string, variableFieldPath []string, patchSelector clusterv1.PatchSelector, diff --git a/pkg/handlers/aws/mutation/ami/inject_control_plane.go b/pkg/handlers/aws/mutation/ami/inject_control_plane.go index ea4618a72..9edf32ad2 100644 --- a/pkg/handlers/aws/mutation/ami/inject_control_plane.go +++ b/pkg/handlers/aws/mutation/ami/inject_control_plane.go @@ -10,7 +10,7 @@ import ( ) func NewControlPlanePatch() *awsAMISpecPatchHandler { - return newAWSAMISpecPatchHandler( + return NewAWSAMISpecPatchHandler( v1alpha1.ClusterConfigVariableName, []string{ v1alpha1.ControlPlaneConfigVariableName, diff --git a/pkg/handlers/aws/mutation/ami/inject_worker.go b/pkg/handlers/aws/mutation/ami/inject_worker.go index efd7e228a..cd1a55939 100644 --- a/pkg/handlers/aws/mutation/ami/inject_worker.go +++ b/pkg/handlers/aws/mutation/ami/inject_worker.go @@ -9,7 +9,7 @@ import ( ) func NewWorkerPatch() *awsAMISpecPatchHandler { - return newAWSAMISpecPatchHandler( + return NewAWSAMISpecPatchHandler( v1alpha1.WorkerConfigVariableName, []string{ v1alpha1.AWSVariableName, diff --git a/pkg/handlers/aws/mutation/iaminstanceprofile/inject_suite_test.go b/pkg/handlers/aws/mutation/iaminstanceprofile/inject_suite_test.go index 1eb2e8964..5456e3e85 100644 --- a/pkg/handlers/aws/mutation/iaminstanceprofile/inject_suite_test.go +++ b/pkg/handlers/aws/mutation/iaminstanceprofile/inject_suite_test.go @@ -10,7 +10,7 @@ import ( . "github.com/onsi/gomega" ) -func TestIAMInstnaceProfilePatch(t *testing.T) { +func TestIAMInstanceProfilePatch(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "IAMInstanceProfile patches for ControlPlane and Workers suite") } diff --git a/pkg/handlers/aws/mutation/iaminstanceprofile/inject_worker.go b/pkg/handlers/aws/mutation/iaminstanceprofile/inject_worker.go index e07043cec..4c052fb8d 100644 --- a/pkg/handlers/aws/mutation/iaminstanceprofile/inject_worker.go +++ b/pkg/handlers/aws/mutation/iaminstanceprofile/inject_worker.go @@ -26,14 +26,14 @@ type awsIAMInstanceProfileWorkerPatchHandler struct { } func NewWorkerPatch() *awsIAMInstanceProfileWorkerPatchHandler { - return newAWSIAMInstanceProfileWorkerPatchHandler( + return NewAWSIAMInstanceProfileWorkerPatchHandler( v1alpha1.WorkerConfigVariableName, v1alpha1.AWSVariableName, VariableName, ) } -func newAWSIAMInstanceProfileWorkerPatchHandler( +func NewAWSIAMInstanceProfileWorkerPatchHandler( variableName string, variableFieldPath ...string, ) *awsIAMInstanceProfileWorkerPatchHandler { diff --git a/pkg/handlers/aws/mutation/instancetype/inject_worker.go b/pkg/handlers/aws/mutation/instancetype/inject_worker.go index e3203bb44..61609477e 100644 --- a/pkg/handlers/aws/mutation/instancetype/inject_worker.go +++ b/pkg/handlers/aws/mutation/instancetype/inject_worker.go @@ -26,14 +26,14 @@ type awsInstanceTypeWorkerPatchHandler struct { } func NewWorkerPatch() *awsInstanceTypeWorkerPatchHandler { - return newAWSInstanceTypeWorkerPatchHandler( + return NewAWSInstanceTypeWorkerPatchHandler( v1alpha1.WorkerConfigVariableName, v1alpha1.AWSVariableName, VariableName, ) } -func newAWSInstanceTypeWorkerPatchHandler( +func NewAWSInstanceTypeWorkerPatchHandler( variableName string, variableFieldPath ...string, ) *awsInstanceTypeWorkerPatchHandler { diff --git a/pkg/handlers/aws/mutation/placementgroup/inject_worker.go b/pkg/handlers/aws/mutation/placementgroup/inject_worker.go index 4176e82bd..be239c278 100644 --- a/pkg/handlers/aws/mutation/placementgroup/inject_worker.go +++ b/pkg/handlers/aws/mutation/placementgroup/inject_worker.go @@ -26,14 +26,14 @@ type awsPlacementGroupWorkerPatchHandler struct { } func NewWorkerPatch() *awsPlacementGroupWorkerPatchHandler { - return newAWSPlacementGroupWorkerPatchHandler( + return NewAWSPlacementGroupWorkerPatchHandler( v1alpha1.WorkerConfigVariableName, v1alpha1.AWSVariableName, VariableName, ) } -func newAWSPlacementGroupWorkerPatchHandler( +func NewAWSPlacementGroupWorkerPatchHandler( variableName string, variableFieldPath ...string, ) *awsPlacementGroupWorkerPatchHandler { diff --git a/pkg/handlers/aws/mutation/securitygroups/inject.go b/pkg/handlers/aws/mutation/securitygroups/inject.go index ec8588139..5cf1f1373 100644 --- a/pkg/handlers/aws/mutation/securitygroups/inject.go +++ b/pkg/handlers/aws/mutation/securitygroups/inject.go @@ -32,7 +32,7 @@ type awsSecurityGroupSpecPatchHandler struct { patchSelector clusterv1.PatchSelector } -func newAWSSecurityGroupSpecPatchHandler( +func NewAWSSecurityGroupSpecPatchHandler( metaVariableName string, variableFieldPath []string, patchSelector clusterv1.PatchSelector, diff --git a/pkg/handlers/aws/mutation/securitygroups/inject_control_plane.go b/pkg/handlers/aws/mutation/securitygroups/inject_control_plane.go index bca5ec3bd..756739462 100644 --- a/pkg/handlers/aws/mutation/securitygroups/inject_control_plane.go +++ b/pkg/handlers/aws/mutation/securitygroups/inject_control_plane.go @@ -10,7 +10,7 @@ import ( ) func NewControlPlanePatch() *awsSecurityGroupSpecPatchHandler { - return newAWSSecurityGroupSpecPatchHandler( + return NewAWSSecurityGroupSpecPatchHandler( v1alpha1.ClusterConfigVariableName, []string{ v1alpha1.ControlPlaneConfigVariableName, diff --git a/pkg/handlers/aws/mutation/securitygroups/inject_worker.go b/pkg/handlers/aws/mutation/securitygroups/inject_worker.go index bb95d4c9e..ed58fb89f 100644 --- a/pkg/handlers/aws/mutation/securitygroups/inject_worker.go +++ b/pkg/handlers/aws/mutation/securitygroups/inject_worker.go @@ -9,7 +9,7 @@ import ( ) func NewWorkerPatch() *awsSecurityGroupSpecPatchHandler { - return newAWSSecurityGroupSpecPatchHandler( + return NewAWSSecurityGroupSpecPatchHandler( v1alpha1.WorkerConfigVariableName, []string{ v1alpha1.AWSVariableName, diff --git a/pkg/handlers/eks/clusterconfig/variables.go b/pkg/handlers/eks/clusterconfig/variables.go new file mode 100644 index 000000000..507e56d65 --- /dev/null +++ b/pkg/handlers/eks/clusterconfig/variables.go @@ -0,0 +1,48 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package clusterconfig + +import ( + "context" + + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + commonhandlers "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" +) + +var ( + _ commonhandlers.Named = &eksClusterConfigVariableHandler{} + _ mutation.DiscoverVariables = &eksClusterConfigVariableHandler{} +) + +const ( + // HandlerNameVariable is the name of the variable handler. + HandlerNameVariable = "EKSClusterConfigVars" +) + +func NewVariable() *eksClusterConfigVariableHandler { + return &eksClusterConfigVariableHandler{} +} + +type eksClusterConfigVariableHandler struct{} + +func (h *eksClusterConfigVariableHandler) Name() string { + return HandlerNameVariable +} + +func (h *eksClusterConfigVariableHandler) DiscoverVariables( + ctx context.Context, + _ *runtimehooksv1.DiscoverVariablesRequest, + resp *runtimehooksv1.DiscoverVariablesResponse, +) { + resp.Variables = append(resp.Variables, clusterv1.ClusterClassVariable{ + Name: v1alpha1.ClusterConfigVariableName, + Required: true, + Schema: v1alpha1.EKSClusterConfig{}.VariableSchema(), + }) + resp.SetStatus(runtimehooksv1.ResponseStatusSuccess) +} diff --git a/pkg/handlers/eks/handlers.go b/pkg/handlers/eks/handlers.go new file mode 100644 index 000000000..d0b5ac720 --- /dev/null +++ b/pkg/handlers/eks/handlers.go @@ -0,0 +1,34 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package eks + +import ( + "github.com/spf13/pflag" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" + eksclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/clusterconfig" + eksmutation "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation" + eksworkerconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/workerconfig" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options" +) + +type Handlers struct{} + +func New( + _ *options.GlobalOptions, +) *Handlers { + return &Handlers{} +} + +func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { + return []handlers.Named{ + eksclusterconfig.NewVariable(), + eksworkerconfig.NewVariable(), + eksmutation.MetaPatchHandler(mgr), + eksmutation.MetaWorkerPatchHandler(mgr), + } +} + +func (h *Handlers) AddFlags(_ *pflag.FlagSet) {} diff --git a/pkg/handlers/eks/mutation/ami/inject_suite_test.go b/pkg/handlers/eks/mutation/ami/inject_suite_test.go new file mode 100644 index 000000000..5652607d2 --- /dev/null +++ b/pkg/handlers/eks/mutation/ami/inject_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package ami + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAMIPatch(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "AMI patches for Workers suite") +} diff --git a/pkg/handlers/eks/mutation/ami/inject_worker.go b/pkg/handlers/eks/mutation/ami/inject_worker.go new file mode 100644 index 000000000..34599c0d0 --- /dev/null +++ b/pkg/handlers/eks/mutation/ami/inject_worker.go @@ -0,0 +1,25 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +package ami + +import ( + capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/ami" +) + +func NewWorkerPatch() mutation.MetaMutator { + return ami.NewAWSAMISpecPatchHandler( + v1alpha1.WorkerConfigVariableName, + []string{ + v1alpha1.EKSVariableName, + ami.VariableName, + }, + selectors.InfrastructureWorkerMachineTemplates( + capav1.GroupVersion.Version, + "AWSMachineTemplate", + ), + ) +} diff --git a/pkg/handlers/eks/mutation/ami/inject_worker_test.go b/pkg/handlers/eks/mutation/ami/inject_worker_test.go new file mode 100644 index 000000000..4eb88d48b --- /dev/null +++ b/pkg/handlers/eks/mutation/ami/inject_worker_test.go @@ -0,0 +1,111 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package ami + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/ami" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +var _ = Describe("Generate AMI patches for Worker", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewWorkerPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "AMI set for workers", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + v1alpha1.AMISpec{ID: "ami-controlplane"}, + v1alpha1.EKSVariableName, + ami.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "add", + Path: "/spec/template/spec/ami/id", + ValueMatcher: gomega.Equal("ami-controlplane"), + }, + }, + }, + { + Name: "AMI lookup format set for worker", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + v1alpha1.AMISpec{ + Lookup: &v1alpha1.AMILookup{ + Format: "test-{{.kubernetesVersion}}-format", + Org: "12345", + BaseOS: "testOS", + }, + }, + v1alpha1.EKSVariableName, + ami.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "add", + Path: "/spec/template/spec/imageLookupFormat", + ValueMatcher: gomega.Equal("test-{{.kubernetesVersion}}-format"), + }, + { + Operation: "add", + Path: "/spec/template/spec/imageLookupOrg", + ValueMatcher: gomega.Equal("12345"), + }, + { + Operation: "add", + Path: "/spec/template/spec/imageLookupBaseOS", + ValueMatcher: gomega.Equal("testOS"), + }, + }, + UnexpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "add", + Path: "/spec/template/spec/ami/id", + ValueMatcher: gomega.Equal(""), + }, + }, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/handlers.go b/pkg/handlers/eks/mutation/handlers.go new file mode 100644 index 000000000..ae67e0196 --- /dev/null +++ b/pkg/handlers/eks/mutation/handlers.go @@ -0,0 +1,27 @@ +// Copyright 2025 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package mutation + +import ( + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/kubeproxymode" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/ntp" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/taints" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/mutation/users" +) + +// metaMutators returns all EKS applicable patch handlers. +func metaMutators() []mutation.MetaMutator { + return []mutation.MetaMutator{ + users.NewPatch(), + kubeproxymode.NewPatch(), + ntp.NewPatch(), + } +} + +func workerMetaMutators() []mutation.MetaMutator { + return []mutation.MetaMutator{ + taints.NewWorkerPatch(), + } +} diff --git a/pkg/handlers/eks/mutation/iaminstanceprofile/inject_suite_test.go b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_suite_test.go new file mode 100644 index 000000000..e04715cfd --- /dev/null +++ b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package iaminstanceprofile + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestIAMInstanceProfilePatch(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "IAMInstanceProfile patches for Workers suite") +} diff --git a/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker.go b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker.go new file mode 100644 index 000000000..9726cb483 --- /dev/null +++ b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker.go @@ -0,0 +1,18 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package iaminstanceprofile + +import ( + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/iaminstanceprofile" +) + +func NewWorkerPatch() mutation.MetaMutator { + return iaminstanceprofile.NewAWSIAMInstanceProfileWorkerPatchHandler( + v1alpha1.WorkerConfigVariableName, + v1alpha1.EKSVariableName, + iaminstanceprofile.VariableName, + ) +} diff --git a/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker_test.go b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker_test.go new file mode 100644 index 000000000..cac35327e --- /dev/null +++ b/pkg/handlers/eks/mutation/iaminstanceprofile/inject_worker_test.go @@ -0,0 +1,64 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package iaminstanceprofile + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/iaminstanceprofile" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +var _ = Describe("Generate IAMInstanceProfile patches for Worker", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewWorkerPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "iamInstanceProfile for worker set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + "nodes.cluster-api-provider-aws.sigs.k8s.io", + v1alpha1.EKSVariableName, + iaminstanceprofile.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/iamInstanceProfile", + ValueMatcher: gomega.Equal("nodes.cluster-api-provider-aws.sigs.k8s.io"), + }}, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/instancetype/inject_suite_test.go b/pkg/handlers/eks/mutation/instancetype/inject_suite_test.go new file mode 100644 index 000000000..eb5dbea48 --- /dev/null +++ b/pkg/handlers/eks/mutation/instancetype/inject_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package instancetype + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestInstanceTypePatch(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "InstanceType patches for Workers suite") +} diff --git a/pkg/handlers/eks/mutation/instancetype/inject_worker.go b/pkg/handlers/eks/mutation/instancetype/inject_worker.go new file mode 100644 index 000000000..affd2f36a --- /dev/null +++ b/pkg/handlers/eks/mutation/instancetype/inject_worker.go @@ -0,0 +1,18 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package instancetype + +import ( + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + awsinstancetype "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/instancetype" +) + +func NewWorkerPatch() mutation.MetaMutator { + return awsinstancetype.NewAWSInstanceTypeWorkerPatchHandler( + v1alpha1.WorkerConfigVariableName, + v1alpha1.EKSVariableName, + awsinstancetype.VariableName, + ) +} diff --git a/pkg/handlers/eks/mutation/instancetype/inject_worker_test.go b/pkg/handlers/eks/mutation/instancetype/inject_worker_test.go new file mode 100644 index 000000000..db6fad09b --- /dev/null +++ b/pkg/handlers/eks/mutation/instancetype/inject_worker_test.go @@ -0,0 +1,64 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package instancetype + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request" + awsinstancetype "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/instancetype" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +var _ = Describe("Generate InstanceType patches for Worker", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewWorkerPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "instanceType for workers set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + "m5.2xlarge", + v1alpha1.EKSVariableName, + awsinstancetype.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "replace", + Path: "/spec/template/spec/instanceType", + ValueMatcher: gomega.Equal("m5.2xlarge"), + }}, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/metapatch_handler.go b/pkg/handlers/eks/mutation/metapatch_handler.go new file mode 100644 index 000000000..c8d42d3e4 --- /dev/null +++ b/pkg/handlers/eks/mutation/metapatch_handler.go @@ -0,0 +1,51 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package mutation + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/ami" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/iaminstanceprofile" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/instancetype" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/network" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/placementgroup" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/region" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/securitygroups" +) + +// MetaPatchHandler returns a meta patch handler for mutating CAPA clusters. +func MetaPatchHandler(mgr manager.Manager) handlers.Named { + patchHandlers := []mutation.MetaMutator{ + region.NewPatch(), + network.NewPatch(), + } + patchHandlers = append(patchHandlers, metaMutators()...) + + return mutation.NewMetaGeneratePatchesHandler( + "eksClusterV1ConfigPatch", + mgr.GetClient(), + patchHandlers..., + ) +} + +// MetaWorkerPatchHandler returns a meta patch handler for mutating CAPA workers. +func MetaWorkerPatchHandler(mgr manager.Manager) handlers.Named { + patchHandlers := []mutation.MetaMutator{ + iaminstanceprofile.NewWorkerPatch(), + instancetype.NewWorkerPatch(), + ami.NewWorkerPatch(), + securitygroups.NewWorkerPatch(), + placementgroup.NewWorkerPatch(), + } + patchHandlers = append(patchHandlers, workerMetaMutators()...) + + return mutation.NewMetaGeneratePatchesHandler( + "eksWorkerV1ConfigPatch", + mgr.GetClient(), + patchHandlers..., + ) +} diff --git a/pkg/handlers/eks/mutation/network/inject.go b/pkg/handlers/eks/mutation/network/inject.go new file mode 100644 index 000000000..5ad105535 --- /dev/null +++ b/pkg/handlers/eks/mutation/network/inject.go @@ -0,0 +1,127 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package network + +import ( + "context" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + 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" + "sigs.k8s.io/controller-runtime/pkg/client" + + capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + eksv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" +) + +const ( + // VariableName is the external patch variable name. + VariableName = "network" +) + +type eksNetworkPatchHandler struct { + variableName string + variableFieldPath []string +} + +func NewPatch() *eksNetworkPatchHandler { + return newEKSPatchPatchHandler( + v1alpha1.ClusterConfigVariableName, + v1alpha1.EKSVariableName, + VariableName, + ) +} + +func newEKSPatchPatchHandler( + variableName string, + variableFieldPath ...string, +) *eksNetworkPatchHandler { + return &eksNetworkPatchHandler{ + variableName: variableName, + variableFieldPath: variableFieldPath, + } +} + +func (h *eksNetworkPatchHandler) Mutate( + ctx context.Context, + obj *unstructured.Unstructured, + vars map[string]apiextensionsv1.JSON, + holderRef runtimehooksv1.HolderReference, + _ client.ObjectKey, + _ mutation.ClusterGetter, +) error { + log := ctrl.LoggerFrom(ctx).WithValues( + "holderRef", holderRef, + ) + + networkVar, err := variables.Get[v1alpha1.AWSNetwork]( + vars, + h.variableName, + h.variableFieldPath..., + ) + if err != nil { + if variables.IsNotFoundError(err) { + log.V(5).Info("AWS Network variable not defined") + return nil + } + return err + } + + log = log.WithValues( + "variableName", + h.variableName, + "variableFieldPath", + h.variableFieldPath, + "variableValue", + networkVar, + ) + + return patches.MutateIfApplicable( + obj, + vars, + &holderRef, + clusterv1.PatchSelector{ + APIVersion: eksv1.GroupVersion.String(), + Kind: "AWSManagedControlPlaneTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + ControlPlane: true, + }, + }, + log, + func(obj *eksv1.AWSManagedControlPlaneTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", client.ObjectKeyFromObject(obj), + ).Info("setting Network in AWSManagedControlPlane spec") + + if networkVar.VPC != nil && + networkVar.VPC.ID != "" { + obj.Spec.Template.Spec.NetworkSpec.VPC = capav1.VPCSpec{ + ID: networkVar.VPC.ID, + } + } + + if len(networkVar.Subnets) > 0 { + subnets := make([]capav1.SubnetSpec, 0) + for _, subnet := range networkVar.Subnets { + if subnet.ID == "" { + continue + } + subnets = append(subnets, capav1.SubnetSpec{ + ID: subnet.ID, + }) + } + obj.Spec.Template.Spec.NetworkSpec.Subnets = subnets + } + + return nil + }, + ) +} diff --git a/pkg/handlers/eks/mutation/network/inject_test.go b/pkg/handlers/eks/mutation/network/inject_test.go new file mode 100644 index 000000000..caaf7bb7d --- /dev/null +++ b/pkg/handlers/eks/mutation/network/inject_test.go @@ -0,0 +1,120 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package network + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +func TestNetworkPatch(t *testing.T) { + gomega.RegisterFailHandler(Fail) + RunSpecs(t, "EKS Network mutator suite") +} + +var _ = Describe("Generate EKS Network patches", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "VPC ID set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.AWSNetwork{ + VPC: &v1alpha1.VPC{ + ID: "vpc-1234", + }, + }, + v1alpha1.EKSVariableName, + VariableName, + ), + }, + RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/network/vpc/id", + ValueMatcher: gomega.Equal("vpc-1234"), + }}, + }, + { + Name: "Subnet IDs set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.AWSNetwork{ + Subnets: v1alpha1.Subnets{ + {ID: "subnet-1"}, + {ID: "subnet-2"}, + {ID: "subnet-3"}, + }, + }, + v1alpha1.EKSVariableName, + VariableName, + ), + }, + RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/network/subnets", + ValueMatcher: gomega.HaveLen(3), + }}, + }, + { + Name: "both VPC ID and Subnet IDs set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.AWSNetwork{ + VPC: &v1alpha1.VPC{ + ID: "vpc-1234", + }, + Subnets: v1alpha1.Subnets{ + {ID: "subnet-1"}, + {ID: "subnet-2"}, + {ID: "subnet-3"}, + }, + }, + v1alpha1.EKSVariableName, + VariableName, + ), + }, + RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/network/vpc/id", + ValueMatcher: gomega.Equal("vpc-1234"), + }, { + Operation: "add", + Path: "/spec/template/spec/network/subnets", + ValueMatcher: gomega.HaveLen(3), + }}, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/network/variables_test.go b/pkg/handlers/eks/mutation/network/variables_test.go new file mode 100644 index 000000000..3512430d5 --- /dev/null +++ b/pkg/handlers/eks/mutation/network/variables_test.go @@ -0,0 +1,67 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package network + +import ( + "testing" + + "k8s.io/utils/ptr" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + eksclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/clusterconfig" +) + +func TestVariableValidation(t *testing.T) { + capitest.ValidateDiscoverVariables( + t, + v1alpha1.ClusterConfigVariableName, + ptr.To(v1alpha1.EKSClusterConfig{}.VariableSchema()), + true, + eksclusterconfig.NewVariable, + capitest.VariableTestDef{ + Name: "specified VPC ID", + Vals: v1alpha1.EKSClusterConfigSpec{ + EKS: &v1alpha1.EKSSpec{ + Network: &v1alpha1.AWSNetwork{ + VPC: &v1alpha1.VPC{ + ID: "vpc-1234", + }, + }, + }, + }, + }, + capitest.VariableTestDef{ + Name: "specified subnet IDs", + Vals: v1alpha1.EKSClusterConfigSpec{ + EKS: &v1alpha1.EKSSpec{ + Network: &v1alpha1.AWSNetwork{ + Subnets: v1alpha1.Subnets{ + {ID: "subnet-1"}, + {ID: "subnet-2"}, + {ID: "subnet-3"}, + }, + }, + }, + }, + }, + capitest.VariableTestDef{ + Name: "specified both VPC ID and subnet IDs", + Vals: v1alpha1.EKSClusterConfigSpec{ + EKS: &v1alpha1.EKSSpec{ + Network: &v1alpha1.AWSNetwork{ + VPC: &v1alpha1.VPC{ + ID: "vpc-1234", + }, + Subnets: v1alpha1.Subnets{ + {ID: "subnet-1"}, + {ID: "subnet-2"}, + {ID: "subnet-3"}, + }, + }, + }, + }, + }, + ) +} diff --git a/pkg/handlers/eks/mutation/placementgroup/inject_suite_test.go b/pkg/handlers/eks/mutation/placementgroup/inject_suite_test.go new file mode 100644 index 000000000..1549bc6fa --- /dev/null +++ b/pkg/handlers/eks/mutation/placementgroup/inject_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package placementgroup + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestInstanceTypePatch(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "PlacementGroup patches for Workers suite") +} diff --git a/pkg/handlers/eks/mutation/placementgroup/inject_worker.go b/pkg/handlers/eks/mutation/placementgroup/inject_worker.go new file mode 100644 index 000000000..e2c0a998d --- /dev/null +++ b/pkg/handlers/eks/mutation/placementgroup/inject_worker.go @@ -0,0 +1,18 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package placementgroup + +import ( + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/placementgroup" +) + +func NewWorkerPatch() mutation.MetaMutator { + return placementgroup.NewAWSPlacementGroupWorkerPatchHandler( + v1alpha1.WorkerConfigVariableName, + v1alpha1.EKSVariableName, + placementgroup.VariableName, + ) +} diff --git a/pkg/handlers/eks/mutation/placementgroup/inject_worker_test.go b/pkg/handlers/eks/mutation/placementgroup/inject_worker_test.go new file mode 100644 index 000000000..d92e31a55 --- /dev/null +++ b/pkg/handlers/eks/mutation/placementgroup/inject_worker_test.go @@ -0,0 +1,64 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package placementgroup + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/placementgroup" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +var _ = Describe("Generate PlacementGroup patches for Worker", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewWorkerPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "placementGroup for workers set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + v1alpha1.PlacementGroup{Name: "pg-1234"}, + v1alpha1.EKSVariableName, + placementgroup.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/placementGroupName", + ValueMatcher: gomega.Equal("pg-1234"), + }}, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/region/inject.go b/pkg/handlers/eks/mutation/region/inject.go new file mode 100644 index 000000000..aa13a3bc1 --- /dev/null +++ b/pkg/handlers/eks/mutation/region/inject.go @@ -0,0 +1,108 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package region + +import ( + "context" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + 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" + "sigs.k8s.io/controller-runtime/pkg/client" + + eksv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" +) + +const ( + // VariableName is the external patch variable name. + VariableName = "region" +) + +type eksRegionPatchHandler struct { + variableName string + variableFieldPath []string +} + +func NewPatch() *eksRegionPatchHandler { + return newEKSRegionPatchHandler( + v1alpha1.ClusterConfigVariableName, + v1alpha1.EKSVariableName, + VariableName, + ) +} + +func newEKSRegionPatchHandler( + variableName string, + variableFieldPath ...string, +) *eksRegionPatchHandler { + return &eksRegionPatchHandler{ + variableName: variableName, + variableFieldPath: variableFieldPath, + } +} + +func (h *eksRegionPatchHandler) Mutate( + ctx context.Context, + obj *unstructured.Unstructured, + vars map[string]apiextensionsv1.JSON, + holderRef runtimehooksv1.HolderReference, + _ client.ObjectKey, + _ mutation.ClusterGetter, +) error { + log := ctrl.LoggerFrom(ctx).WithValues( + "holderRef", holderRef, + ) + + regionVar, err := variables.Get[v1alpha1.Region]( + vars, + h.variableName, + h.variableFieldPath..., + ) + if err != nil { + if variables.IsNotFoundError(err) { + log.V(5).Info("EKS region variable not defined") + return nil + } + return err + } + + log = log.WithValues( + "variableName", + h.variableName, + "variableFieldPath", + h.variableFieldPath, + "variableValue", + regionVar, + ) + + return patches.MutateIfApplicable( + obj, + vars, + &holderRef, + clusterv1.PatchSelector{ + APIVersion: eksv1.GroupVersion.String(), + Kind: "AWSManagedControlPlaneTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + ControlPlane: true, + }, + }, + log, + func(obj *eksv1.AWSManagedControlPlaneTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", client.ObjectKeyFromObject(obj), + ).Info("setting region in AWSManagedControlPlaneTemplate spec") + + obj.Spec.Template.Spec.Region = string(regionVar) + + return nil + }, + ) +} diff --git a/pkg/handlers/eks/mutation/region/inject_test.go b/pkg/handlers/eks/mutation/region/inject_test.go new file mode 100644 index 000000000..4d015e26b --- /dev/null +++ b/pkg/handlers/eks/mutation/region/inject_test.go @@ -0,0 +1,64 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package region + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +func TestRegionPatch(t *testing.T) { + gomega.RegisterFailHandler(Fail) + RunSpecs(t, "EKS Region mutator suite") +} + +var _ = Describe("Generate EKS Region patches", func() { + // only add eks region patch + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "region set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + "a-specific-region", + v1alpha1.EKSVariableName, + VariableName, + ), + }, + RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/region", + ValueMatcher: gomega.Equal("a-specific-region"), + }}, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/region/variables_test.go b/pkg/handlers/eks/mutation/region/variables_test.go new file mode 100644 index 000000000..bb1b060de --- /dev/null +++ b/pkg/handlers/eks/mutation/region/variables_test.go @@ -0,0 +1,32 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package region + +import ( + "testing" + + "k8s.io/utils/ptr" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + eksclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/clusterconfig" +) + +func TestVariableValidation(t *testing.T) { + capitest.ValidateDiscoverVariables( + t, + v1alpha1.ClusterConfigVariableName, + ptr.To(v1alpha1.EKSClusterConfig{}.VariableSchema()), + true, + eksclusterconfig.NewVariable, + capitest.VariableTestDef{ + Name: "specified region", + Vals: v1alpha1.EKSClusterConfigSpec{ + EKS: &v1alpha1.EKSSpec{ + Region: ptr.To(v1alpha1.Region("a-specified-region")), + }, + }, + }, + ) +} diff --git a/pkg/handlers/eks/mutation/securitygroups/inject_suite_test.go b/pkg/handlers/eks/mutation/securitygroups/inject_suite_test.go new file mode 100644 index 000000000..b72cb8490 --- /dev/null +++ b/pkg/handlers/eks/mutation/securitygroups/inject_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package securitygroups + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestSecurityGroupsPatch(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "EKS security groups patches for Workers suite") +} diff --git a/pkg/handlers/eks/mutation/securitygroups/inject_worker.go b/pkg/handlers/eks/mutation/securitygroups/inject_worker.go new file mode 100644 index 000000000..2969c765b --- /dev/null +++ b/pkg/handlers/eks/mutation/securitygroups/inject_worker.go @@ -0,0 +1,25 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +package securitygroups + +import ( + capav1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/securitygroups" +) + +func NewWorkerPatch() mutation.MetaMutator { + return securitygroups.NewAWSSecurityGroupSpecPatchHandler( + v1alpha1.WorkerConfigVariableName, + []string{ + v1alpha1.EKSVariableName, + securitygroups.VariableName, + }, + selectors.InfrastructureWorkerMachineTemplates( + capav1.GroupVersion.Version, + "AWSMachineTemplate", + ), + ) +} diff --git a/pkg/handlers/eks/mutation/securitygroups/inject_worker_test.go b/pkg/handlers/eks/mutation/securitygroups/inject_worker_test.go new file mode 100644 index 000000000..ea850f9fd --- /dev/null +++ b/pkg/handlers/eks/mutation/securitygroups/inject_worker_test.go @@ -0,0 +1,80 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package securitygroups + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/aws/mutation/securitygroups" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" +) + +var _ = Describe("Generate AWS SecurityGroups patches for Worker", func() { + patchGenerator := func() mutation.GeneratePatches { + return mutation.NewMetaGeneratePatchesHandler("", helpers.TestEnv.Client, NewWorkerPatch()).(mutation.GeneratePatches) + } + + testDefs := []capitest.PatchTestDef{ + { + Name: "unset variable", + }, + { + Name: "SecurityGroups for workers set", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + v1alpha1.AdditionalSecurityGroup{ + {ID: "sg-1"}, + {ID: "sg-2"}, + {ID: "sg-3"}, + }, + v1alpha1.EKSVariableName, + securitygroups.VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: request.NewWorkerAWSMachineTemplateRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "add", + Path: "/spec/template/spec/additionalSecurityGroups", + ValueMatcher: gomega.HaveLen(3), + }, + // TODO(shalinpatel): add matcher to check if all SG are set + // { + // Operation: "add", + // Path: "/spec/template/spec/additionalSecurityGroups", + // ValueMatcher: gomega.ContainElements( + // gomega.HaveKeyWithValue("id", "sg-1"), + // gomega.HaveKeyWithValue("id", "sg-2"), + // gomega.HaveKeyWithValue("id", "sg-3"), + // ), + // }, + }, + }, + } + + // create test node for each case + for _, tt := range testDefs { + It(tt.Name, func() { + capitest.AssertGeneratePatches( + GinkgoT(), + patchGenerator, + &tt, + ) + }) + } +}) diff --git a/pkg/handlers/eks/mutation/testutils/request.go b/pkg/handlers/eks/mutation/testutils/request.go new file mode 100644 index 000000000..7dce21c79 --- /dev/null +++ b/pkg/handlers/eks/mutation/testutils/request.go @@ -0,0 +1,78 @@ +// Copyright 2025 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package testutils + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + eksbootstrapv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" + eksv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" +) + +func NewEKSControlPlaneRequestItem( + uid types.UID, + existingSpec ...eksv1.AWSManagedControlPlaneTemplateSpec, +) runtimehooksv1.GeneratePatchesRequestItem { + eksClusterTemplate := &eksv1.AWSManagedControlPlaneTemplate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: eksv1.GroupVersion.String(), + Kind: "AWSManagedControlPlaneTemplate", + }, + } + + switch len(existingSpec) { + case 0: + // Do nothing. + case 1: + eksClusterTemplate.Spec = existingSpec[0] + default: + panic("can only take at most one existing spec") + } + + return request.NewRequestItem( + eksClusterTemplate, + &runtimehooksv1.HolderReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "Cluster", + FieldPath: "spec.controlPlaneRef", + Name: request.ClusterName, + Namespace: request.Namespace, + }, + uid, + ) +} + +func NewEKSConfigTemplateRequestItem( + uid types.UID, + existingSpec ...eksbootstrapv1.EKSConfigTemplateSpec, +) runtimehooksv1.GeneratePatchesRequestItem { + eksConfigTemplate := &eksbootstrapv1.EKSConfigTemplate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: eksbootstrapv1.GroupVersion.String(), + Kind: "EKSConfigTemplate", + }, + } + + switch len(existingSpec) { + case 0: + // Do nothing. + case 1: + eksConfigTemplate.Spec = existingSpec[0] + default: + panic("can only take at most one existing spec") + } + + return request.NewRequestItem( + eksConfigTemplate, + &runtimehooksv1.HolderReference{ + Kind: "MachineDeployment", + FieldPath: "spec.template.spec.bootstrap.configRef", + }, + uid, + ) +} diff --git a/pkg/handlers/eks/workerconfig/variables.go b/pkg/handlers/eks/workerconfig/variables.go new file mode 100644 index 000000000..5a98171ed --- /dev/null +++ b/pkg/handlers/eks/workerconfig/variables.go @@ -0,0 +1,48 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package workerconfig + +import ( + "context" + + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + commonhandlers "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" +) + +var ( + _ commonhandlers.Named = &eksWorkerConfigVariableHandler{} + _ mutation.DiscoverVariables = &eksWorkerConfigVariableHandler{} +) + +const ( + // HandlerNameVariable is the name of the variable handler. + HandlerNameVariable = "EKSWorkerConfigVars" +) + +func NewVariable() *eksWorkerConfigVariableHandler { + return &eksWorkerConfigVariableHandler{} +} + +type eksWorkerConfigVariableHandler struct{} + +func (h *eksWorkerConfigVariableHandler) Name() string { + return HandlerNameVariable +} + +func (h *eksWorkerConfigVariableHandler) DiscoverVariables( + ctx context.Context, + _ *runtimehooksv1.DiscoverVariablesRequest, + resp *runtimehooksv1.DiscoverVariablesResponse, +) { + resp.Variables = append(resp.Variables, clusterv1.ClusterClassVariable{ + Name: v1alpha1.WorkerConfigVariableName, + Required: false, + Schema: v1alpha1.EKSWorkerNodeConfig{}.VariableSchema(), + }) + resp.SetStatus(runtimehooksv1.ResponseStatusSuccess) +} diff --git a/pkg/handlers/eks/workerconfig/variables_test.go b/pkg/handlers/eks/workerconfig/variables_test.go new file mode 100644 index 000000000..06a3843ca --- /dev/null +++ b/pkg/handlers/eks/workerconfig/variables_test.go @@ -0,0 +1,39 @@ +// Copyright 2023 Nutanix. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package workerconfig + +import ( + "testing" + + "k8s.io/utils/ptr" + + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" +) + +func TestVariableValidation(t *testing.T) { + capitest.ValidateDiscoverVariables( + t, + v1alpha1.WorkerConfigVariableName, + ptr.To(v1alpha1.EKSWorkerNodeConfig{}.VariableSchema()), + false, + NewVariable, + capitest.VariableTestDef{ + Name: "specified IAM instance profile", + Vals: v1alpha1.EKSWorkerNodeConfigSpec{ + EKS: &v1alpha1.AWSWorkerNodeSpec{ + IAMInstanceProfile: "nodes.cluster-api-provider-aws.sigs.k8s.io", + }, + }, + }, + capitest.VariableTestDef{ + Name: "specified instance type", + Vals: v1alpha1.EKSWorkerNodeConfigSpec{ + EKS: &v1alpha1.AWSWorkerNodeSpec{ + InstanceType: "m5.small", + }, + }, + }, + ) +} diff --git a/pkg/handlers/generic/mutation/coredns/variables_test.go b/pkg/handlers/generic/mutation/coredns/variables_test.go index 32c3e7138..025a29492 100644 --- a/pkg/handlers/generic/mutation/coredns/variables_test.go +++ b/pkg/handlers/generic/mutation/coredns/variables_test.go @@ -18,16 +18,20 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "unset", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{}, + }, }, }, { Name: "set with valid image values", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{ - CoreDNS: &v1alpha1.CoreDNS{ - Image: &v1alpha1.Image{ - Repository: "my-registry.io/my-org/my-repo", - Tag: "v1.11.3_custom.0", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + Image: &v1alpha1.Image{ + Repository: "my-registry.io/my-org/my-repo", + Tag: "v1.11.3_custom.0", + }, }, }, }, @@ -35,10 +39,12 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "set with valid image repository", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{ - CoreDNS: &v1alpha1.CoreDNS{ - Image: &v1alpha1.Image{ - Repository: "my-registry.io/my-org/my-repo", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + Image: &v1alpha1.Image{ + Repository: "my-registry.io/my-org/my-repo", + }, }, }, }, @@ -46,10 +52,12 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "set with valid image tag", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{ - CoreDNS: &v1alpha1.CoreDNS{ - Image: &v1alpha1.Image{ - Tag: "v1.11.3_custom.0", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + Image: &v1alpha1.Image{ + Tag: "v1.11.3_custom.0", + }, }, }, }, @@ -57,10 +65,12 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "set with invalid image repository", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{ - CoreDNS: &v1alpha1.CoreDNS{ - Image: &v1alpha1.Image{ - Tag: "https://this.should.not.have.a.scheme", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + Image: &v1alpha1.Image{ + Tag: "https://this.should.not.have.a.scheme", + }, }, }, }, @@ -69,10 +79,12 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "set with invalid image tag", Vals: v1alpha1.GenericClusterConfigSpec{ - DNS: &v1alpha1.DNS{ - CoreDNS: &v1alpha1.CoreDNS{ - Image: &v1alpha1.Image{ - Tag: "this:is:not:a:valid:tag", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + Image: &v1alpha1.Image{ + Tag: "this:is:not:a:valid:tag", + }, }, }, }, diff --git a/pkg/handlers/generic/mutation/etcd/variables_test.go b/pkg/handlers/generic/mutation/etcd/variables_test.go index 206882aec..899c9f708 100644 --- a/pkg/handlers/generic/mutation/etcd/variables_test.go +++ b/pkg/handlers/generic/mutation/etcd/variables_test.go @@ -18,24 +18,30 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "unset", Vals: v1alpha1.GenericClusterConfigSpec{ - Etcd: &v1alpha1.Etcd{}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Etcd: &v1alpha1.Etcd{}, + }, }, }, { Name: "set with valid image values", Vals: v1alpha1.GenericClusterConfigSpec{ - Etcd: &v1alpha1.Etcd{ - Image: &v1alpha1.Image{ - Repository: "my-registry.io/my-org/my-repo", - Tag: "v3.5.99_custom.0", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Etcd: &v1alpha1.Etcd{ + Image: &v1alpha1.Image{ + Repository: "my-registry.io/my-org/my-repo", + Tag: "v3.5.99_custom.0", + }, }, }, }, }, { Name: "set with invalid image repository", Vals: v1alpha1.GenericClusterConfigSpec{ - Etcd: &v1alpha1.Etcd{ - Image: &v1alpha1.Image{ - Repository: "https://this.should.not.have.a.scheme", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Etcd: &v1alpha1.Etcd{ + Image: &v1alpha1.Image{ + Repository: "https://this.should.not.have.a.scheme", + }, }, }, }, @@ -43,9 +49,11 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "set with invalid image tag", Vals: v1alpha1.GenericClusterConfigSpec{ - Etcd: &v1alpha1.Etcd{ - Image: &v1alpha1.Image{ - Tag: "this:is:not:a:valid:tag", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Etcd: &v1alpha1.Etcd{ + Image: &v1alpha1.Image{ + Tag: "this:is:not:a:valid:tag", + }, }, }, }, diff --git a/pkg/handlers/generic/mutation/httpproxy/variables_test.go b/pkg/handlers/generic/mutation/httpproxy/variables_test.go index 55227da56..c8ce46abb 100644 --- a/pkg/handlers/generic/mutation/httpproxy/variables_test.go +++ b/pkg/handlers/generic/mutation/httpproxy/variables_test.go @@ -18,10 +18,12 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "valid proxy config", Vals: v1alpha1.GenericClusterConfigSpec{ - Proxy: &v1alpha1.HTTPProxy{ - HTTP: "http://a.b.c.example.com", - HTTPS: "https://a.b.c.example.com", - AdditionalNo: []string{"d.e.f.example.com"}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Proxy: &v1alpha1.HTTPProxy{ + HTTP: "http://a.b.c.example.com", + HTTPS: "https://a.b.c.example.com", + AdditionalNo: []string{"d.e.f.example.com"}, + }, }, }, }} diff --git a/pkg/handlers/generic/mutation/imageregistries/credentials/variables_test.go b/pkg/handlers/generic/mutation/imageregistries/credentials/variables_test.go index 6942b0ac6..3a9709a29 100644 --- a/pkg/handlers/generic/mutation/imageregistries/credentials/variables_test.go +++ b/pkg/handlers/generic/mutation/imageregistries/credentials/variables_test.go @@ -19,21 +19,25 @@ var testDefs = []capitest.VariableTestDef{ { Name: "without a credentials secret", Vals: v1alpha1.GenericClusterConfigSpec{ - ImageRegistries: []v1alpha1.ImageRegistry{ - { - URL: "http://a.b.c.example.com", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + ImageRegistries: []v1alpha1.ImageRegistry{ + { + URL: "http://a.b.c.example.com", + }, }, }, }, }, { Name: "with a credentials secret", Vals: v1alpha1.GenericClusterConfigSpec{ - ImageRegistries: []v1alpha1.ImageRegistry{ - { - URL: "https://a.b.c.example.com/a/b/c", - Credentials: &v1alpha1.RegistryCredentials{ - SecretRef: &v1alpha1.LocalObjectReference{ - Name: "a.b.c.example.com-creds", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + ImageRegistries: []v1alpha1.ImageRegistry{ + { + URL: "https://a.b.c.example.com/a/b/c", + Credentials: &v1alpha1.RegistryCredentials{ + SecretRef: &v1alpha1.LocalObjectReference{ + Name: "a.b.c.example.com-creds", + }, }, }, }, @@ -42,21 +46,25 @@ var testDefs = []capitest.VariableTestDef{ }, { Name: "support for multiple image registries", Vals: v1alpha1.GenericClusterConfigSpec{ - ImageRegistries: []v1alpha1.ImageRegistry{ - { - URL: "http://first-image-registry.example.com", - }, - { - URL: "http://second-image-registry.example.com", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + ImageRegistries: []v1alpha1.ImageRegistry{ + { + URL: "http://first-image-registry.example.com", + }, + { + URL: "http://second-image-registry.example.com", + }, }, }, }, }, { Name: "invalid registry URL", Vals: v1alpha1.GenericClusterConfigSpec{ - ImageRegistries: []v1alpha1.ImageRegistry{ - { - URL: "unsupportedformat://a.b.c.example.com", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + ImageRegistries: []v1alpha1.ImageRegistry{ + { + URL: "unsupportedformat://a.b.c.example.com", + }, }, }, }, @@ -64,9 +72,11 @@ var testDefs = []capitest.VariableTestDef{ }, { Name: "registry URL without format", Vals: v1alpha1.GenericClusterConfigSpec{ - ImageRegistries: []v1alpha1.ImageRegistry{ - { - URL: "a.b.c.example.com/a/b/c", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + ImageRegistries: []v1alpha1.ImageRegistry{ + { + URL: "a.b.c.example.com/a/b/c", + }, }, }, }, diff --git a/pkg/handlers/generic/mutation/kubeproxymode/inject.go b/pkg/handlers/generic/mutation/kubeproxymode/inject.go index 37bd7f264..e87150cef 100644 --- a/pkg/handlers/generic/mutation/kubeproxymode/inject.go +++ b/pkg/handlers/generic/mutation/kubeproxymode/inject.go @@ -13,12 +13,14 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + eksv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" @@ -115,7 +117,7 @@ func (h *kubeProxyMode) Mutate( return nil } - return patches.MutateIfApplicable( + if err := patches.MutateIfApplicable( obj, vars, &holderRef, @@ -153,7 +155,44 @@ func (h *kubeProxyMode) Mutate( return fmt.Errorf("unknown kube proxy mode %q", kubeProxyMode) } }, - ) + ); err != nil { + return err + } + + if err := patches.MutateIfApplicable( + obj, + vars, + &holderRef, + clusterv1.PatchSelector{ + APIVersion: eksv1.GroupVersion.String(), + Kind: "AWSManagedControlPlaneTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + ControlPlane: true, + }, + }, + log, + func(obj *eksv1.AWSManagedControlPlaneTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", client.ObjectKeyFromObject(obj), + ).Info("adding kube proxy mode to AWSManagedControlPlaneTemplate spec") + + if isSkipProxy { + log.Info( + "cluster controlplane contains controlplane.cluster.x-k8s.io/skip-kube-proxy annotation, " + + "skipping kube-proxy addon", + ) + + obj.Spec.Template.Spec.KubeProxy.Disable = true + } + + return nil + }, + ); err != nil { + return err + } + + return nil } // addKubeProxyConfigFileAndCommand adds the kube-proxy configuration file and command to the KCPTemplate. diff --git a/pkg/handlers/generic/mutation/kubeproxymode/inject_test.go b/pkg/handlers/generic/mutation/kubeproxymode/inject_test.go index fcd6a3a7e..25a6d3360 100644 --- a/pkg/handlers/generic/mutation/kubeproxymode/inject_test.go +++ b/pkg/handlers/generic/mutation/kubeproxymode/inject_test.go @@ -21,6 +21,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" ) @@ -165,7 +166,7 @@ var _ = Describe("Generate kube proxy mode patches", func() { capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.AWSClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeIPTables, }, @@ -214,7 +215,7 @@ mode: iptables capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.AWSClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeIPTables, }, @@ -263,7 +264,7 @@ mode: iptables capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.DockerClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeIPTables, }, @@ -312,7 +313,7 @@ mode: iptables capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.NutanixClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeNFTables, }, @@ -361,7 +362,7 @@ mode: nftables capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.AWSClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeNFTables, }, @@ -410,7 +411,7 @@ mode: nftables capitest.VariableWithValue( v1alpha1.ClusterConfigVariableName, v1alpha1.DockerClusterConfigSpec{ - GenericClusterConfigSpec: v1alpha1.GenericClusterConfigSpec{ + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ KubeProxy: &v1alpha1.KubeProxy{ Mode: v1alpha1.KubeProxyModeNFTables, }, @@ -452,6 +453,44 @@ mode: nftables }, }, }, + }, { + patchTest: capitest.PatchTestDef{ + Name: "disable kube proxy with EKS", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.EKSClusterConfigSpec{}, + ), + }, + RequestItem: testutils.NewEKSControlPlaneRequestItem("1234"), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/kubeProxy/disable", + ValueMatcher: gomega.Equal(true), + }}, + }, + cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: request.Namespace, + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "eks", + }, + }, + Spec: clusterv1.ClusterSpec{ + Topology: &clusterv1.Topology{ + Version: "dummy-version", + Class: "dummy-class", + ControlPlane: clusterv1.ControlPlaneTopology{ + Metadata: clusterv1.ObjectMeta{ + Annotations: map[string]string{ + controlplanev1.SkipKubeProxyAnnotation: "", + }, + }, + }, + }, + }, + }, }} // create test node for each case diff --git a/pkg/handlers/generic/mutation/kubernetesimagerepository/variables_test.go b/pkg/handlers/generic/mutation/kubernetesimagerepository/variables_test.go index 46433ffba..f8c04317c 100644 --- a/pkg/handlers/generic/mutation/kubernetesimagerepository/variables_test.go +++ b/pkg/handlers/generic/mutation/kubernetesimagerepository/variables_test.go @@ -18,9 +18,11 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "set", Vals: v1alpha1.GenericClusterConfigSpec{ - KubernetesImageRepository: ptr.To( - "my-registry.io/my-org/my-repo", - ), + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + KubernetesImageRepository: ptr.To( + "my-registry.io/my-org/my-repo", + ), + }, }, }} diff --git a/pkg/handlers/generic/mutation/mirrors/variables_test.go b/pkg/handlers/generic/mutation/mirrors/variables_test.go index d25235fd8..50643f6fe 100644 --- a/pkg/handlers/generic/mutation/mirrors/variables_test.go +++ b/pkg/handlers/generic/mutation/mirrors/variables_test.go @@ -18,18 +18,22 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "without a credentials secret", Vals: v1alpha1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ - URL: "http://a.b.c.example.com", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ + URL: "http://a.b.c.example.com", + }, }, }, }, { Name: "with a credentials CA secret", Vals: v1alpha1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ - URL: "http://a.b.c.example.com", - Credentials: &v1alpha1.RegistryCredentials{ - SecretRef: &v1alpha1.LocalObjectReference{ - Name: "a.b.c.example.com-ca-cert-creds", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ + URL: "http://a.b.c.example.com", + Credentials: &v1alpha1.RegistryCredentials{ + SecretRef: &v1alpha1.LocalObjectReference{ + Name: "a.b.c.example.com-ca-cert-creds", + }, }, }, }, @@ -37,16 +41,20 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "invalid mirror registry URL", Vals: v1alpha1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ - URL: "unsupportedformat://a.b.c.example.com", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ + URL: "unsupportedformat://a.b.c.example.com", + }, }, }, ExpectError: true, }, { Name: "mirror URL without format", Vals: v1alpha1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ - URL: "a.b.c.example.com/a/b/c", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &v1alpha1.GlobalImageRegistryMirror{ + URL: "a.b.c.example.com/a/b/c", + }, }, }, ExpectError: true, diff --git a/pkg/handlers/generic/mutation/ntp/inject.go b/pkg/handlers/generic/mutation/ntp/inject.go index a7d6697c4..47ec3058f 100644 --- a/pkg/handlers/generic/mutation/ntp/inject.go +++ b/pkg/handlers/generic/mutation/ntp/inject.go @@ -15,6 +15,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + eksbootstrapv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" @@ -121,5 +122,22 @@ func (h *ntpPatchHandler) Mutate( return err } + if err := patches.MutateIfApplicable( + obj, vars, &holderRef, + selectors.WorkersConfigTemplateSelector(eksbootstrapv1.GroupVersion.String(), "EKSConfigTemplate"), log, + func(obj *eksbootstrapv1.EKSConfigTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", client.ObjectKeyFromObject(obj), + ).Info("setting users in worker node EKS config template") + obj.Spec.Template.Spec.NTP = &eksbootstrapv1.NTP{ + Enabled: ptr.To(true), + Servers: ntp.Servers, + } + return nil + }); err != nil { + return err + } + return nil } diff --git a/pkg/handlers/generic/mutation/ntp/inject_test.go b/pkg/handlers/generic/mutation/ntp/inject_test.go index 9dae4bcd0..068286657 100644 --- a/pkg/handlers/generic/mutation/ntp/inject_test.go +++ b/pkg/handlers/generic/mutation/ntp/inject_test.go @@ -14,6 +14,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" ) @@ -158,6 +159,37 @@ var _ = Describe("Generate NTP patches", func() { ), }, }, + { + Name: "NTP configuration is set for worker nodes with single server for EKSConfigTemplate", + RequestItem: testutils.NewEKSConfigTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{ + { + Operation: "add", + Path: "/spec/template/spec/ntp", + ValueMatcher: gomega.SatisfyAll( + gomega.HaveKeyWithValue("enabled", gomega.BeTrue()), + gomega.HaveKeyWithValue("servers", gomega.ConsistOf("pool.ntp.org")), + ), + }, + }, + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.NTP{ + Servers: []string{"pool.ntp.org"}, + }, + VariableName, + ), + capitest.VariableWithValue( + "builtin", + map[string]any{ + "machineDeployment": map[string]any{ + "class": "worker-class", + }, + }, + ), + }, + }, } // create test node for each case diff --git a/pkg/handlers/generic/mutation/ntp/variables_test.go b/pkg/handlers/generic/mutation/ntp/variables_test.go index aa44f9f24..cc59318a0 100644 --- a/pkg/handlers/generic/mutation/ntp/variables_test.go +++ b/pkg/handlers/generic/mutation/ntp/variables_test.go @@ -21,22 +21,28 @@ var testDefs = []capitest.VariableTestDef{{ }, { Name: "valid single NTP server", Vals: v1alpha1.GenericClusterConfigSpec{ - NTP: &v1alpha1.NTP{ - Servers: []string{"pool.ntp.org"}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + NTP: &v1alpha1.NTP{ + Servers: []string{"pool.ntp.org"}, + }, }, }, }, { Name: "valid multiple NTP servers", Vals: v1alpha1.GenericClusterConfigSpec{ - NTP: &v1alpha1.NTP{ - Servers: []string{"time.aws.com", "time.google.com", "pool.ntp.org"}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + NTP: &v1alpha1.NTP{ + Servers: []string{"time.aws.com", "time.google.com", "pool.ntp.org"}, + }, }, }, }, { Name: "empty servers array", Vals: v1alpha1.GenericClusterConfigSpec{ - NTP: &v1alpha1.NTP{ - Servers: []string{}, + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + NTP: &v1alpha1.NTP{ + Servers: []string{}, + }, }, }, ExpectError: true, diff --git a/pkg/handlers/generic/mutation/taints/inject_worker.go b/pkg/handlers/generic/mutation/taints/inject_worker.go index 263f72bf0..22509a618 100644 --- a/pkg/handlers/generic/mutation/taints/inject_worker.go +++ b/pkg/handlers/generic/mutation/taints/inject_worker.go @@ -5,6 +5,7 @@ package taints import ( "context" + "strings" "github.com/samber/lo" v1 "k8s.io/api/core/v1" @@ -15,6 +16,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + eksbootstrapv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" @@ -80,7 +82,7 @@ func (h *taintsWorkerPatchHandler) Mutate( taintsVar, ) - return patches.MutateIfApplicable( + if err := patches.MutateIfApplicable( obj, vars, &holderRef, selectors.WorkersKubeadmConfigTemplateSelector(), log, func(obj *bootstrapv1.KubeadmConfigTemplate) error { log.WithValues( @@ -95,7 +97,37 @@ func (h *taintsWorkerPatchHandler) Mutate( taintsVar, ) return nil - }) + }); err != nil { + return err + } + + if err := patches.MutateIfApplicable( + obj, vars, &holderRef, + selectors.WorkersConfigTemplateSelector(eksbootstrapv1.GroupVersion.String(), "EKSConfigTemplate"), log, + func(obj *eksbootstrapv1.EKSConfigTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", ctrlclient.ObjectKeyFromObject(obj), + ).Info("adding taints to worker node EKS config template") + if obj.Spec.Template.Spec.KubeletExtraArgs == nil { + obj.Spec.Template.Spec.KubeletExtraArgs = make(map[string]string, 1) + } + + existingTaintsFlagValue := obj.Spec.Template.Spec.KubeletExtraArgs["register-with-taints"] + + newTaintsFlagValue := toEKSConfigTaints(taintsVar) + + if existingTaintsFlagValue != "" { + newTaintsFlagValue = existingTaintsFlagValue + "," + newTaintsFlagValue + } + + obj.Spec.Template.Spec.KubeletExtraArgs["register-with-taints"] = newTaintsFlagValue + return nil + }); err != nil { + return err + } + + return nil } func toCoreTaints(existingTaints []v1.Taint, newTaints []v1alpha1.Taint) []v1.Taint { @@ -124,3 +156,16 @@ func toCoreTaints(existingTaints []v1.Taint, newTaints []v1alpha1.Taint) []v1.Ta return append(existingTaints, newCoreTaints...) } } + +func toEKSConfigTaints(newTaints []v1alpha1.Taint) string { + taintValues := lo.Map(newTaints, func(t v1alpha1.Taint, _ int) string { + taint := t.Key + if t.Value != "" { + taint += "=" + t.Value + } + taint += ":" + string(t.Effect) + return taint + }) + + return strings.Join(taintValues, ",") +} diff --git a/pkg/handlers/generic/mutation/taints/inject_worker_test.go b/pkg/handlers/generic/mutation/taints/inject_worker_test.go index ec6a19495..548d3cf4f 100644 --- a/pkg/handlers/generic/mutation/taints/inject_worker_test.go +++ b/pkg/handlers/generic/mutation/taints/inject_worker_test.go @@ -17,6 +17,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" ) @@ -57,6 +58,34 @@ var _ = Describe("Generate taints patches for Worker", func() { ), }}, }, + { + Name: "taints for workers set for EKSConfigTemplate", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.WorkerConfigVariableName, + []v1alpha1.Taint{{ + Key: "key", + Effect: v1alpha1.TaintEffectNoExecute, + Value: "value", + }}, + VariableName, + ), + capitest.VariableWithValue( + "builtin", + apiextensionsv1.JSON{ + Raw: []byte(`{"machineDeployment": {"class": "a-worker"}}`), + }, + ), + }, + RequestItem: testutils.NewEKSConfigTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/kubeletExtraArgs", + ValueMatcher: gomega.HaveKeyWithValue( + "register-with-taints", "key=value:NoExecute", + ), + }}, + }, } // create test node for each case @@ -115,3 +144,40 @@ func Test_toCoreTaints(t *testing.T) { }) } } + +func Test_toEKSConfigTaints(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + newTaints []v1alpha1.Taint + want string + }{{ + name: "nil taints", + want: "", + }, { + name: "new taints", + newTaints: []v1alpha1.Taint{ + {Key: "key", Effect: v1alpha1.TaintEffectNoExecute, Value: "value"}, + }, + want: "key=value:NoExecute", + }, { + name: "multiple new taints", + newTaints: []v1alpha1.Taint{ + {Key: "key", Effect: v1alpha1.TaintEffectNoExecute, Value: "value"}, + {Key: "key2", Effect: v1alpha1.TaintEffectNoExecute, Value: "value2"}, + }, + want: "key=value:NoExecute,key2=value2:NoExecute", + }, { + name: "empty but non-nil new taints", + newTaints: []v1alpha1.Taint{}, + want: "", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.want, toEKSConfigTaints(tt.newTaints)) + }) + } +} diff --git a/pkg/handlers/generic/mutation/users/inject.go b/pkg/handlers/generic/mutation/users/inject.go index bd9e2d9a8..11ed5dae6 100644 --- a/pkg/handlers/generic/mutation/users/inject.go +++ b/pkg/handlers/generic/mutation/users/inject.go @@ -15,6 +15,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + eksbootstrapv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" @@ -80,6 +81,11 @@ func (h *usersPatchHandler) Mutate( usersVariable, ) + bootstrapUsers := []bootstrapv1.User{} + for _, userFromVariable := range usersVariable { + bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable)) + } + if err := patches.MutateIfApplicable( obj, vars, &holderRef, selectors.ControlPlane(), log, func(obj *controlplanev1.KubeadmControlPlaneTemplate) error { @@ -87,10 +93,7 @@ func (h *usersPatchHandler) Mutate( "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), "patchedObjectName", ctrlclient.ObjectKeyFromObject(obj), ).Info("setting users in control plane kubeadm config template") - bootstrapUsers := []bootstrapv1.User{} - for _, userFromVariable := range usersVariable { - bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable)) - } + obj.Spec.Template.Spec.KubeadmConfigSpec.Users = bootstrapUsers return nil }); err != nil { @@ -104,16 +107,53 @@ func (h *usersPatchHandler) Mutate( "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), "patchedObjectName", ctrlclient.ObjectKeyFromObject(obj), ).Info("setting users in worker node kubeadm config template") - bootstrapUsers := []bootstrapv1.User{} - for _, userFromVariable := range usersVariable { - bootstrapUsers = append(bootstrapUsers, generateBootstrapUser(userFromVariable)) - } obj.Spec.Template.Spec.Users = bootstrapUsers return nil }); err != nil { return err } + if err := patches.MutateIfApplicable( + obj, vars, &holderRef, + selectors.WorkersConfigTemplateSelector(eksbootstrapv1.GroupVersion.String(), "EKSConfigTemplate"), log, + func(obj *eksbootstrapv1.EKSConfigTemplate) error { + log.WithValues( + "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), + "patchedObjectName", ctrlclient.ObjectKeyFromObject(obj), + ).Info("setting users in worker node EKS config template") + eksBootstrapUsers := make([]eksbootstrapv1.User, 0, len(bootstrapUsers)) + for _, user := range bootstrapUsers { + var passwdFrom *eksbootstrapv1.PasswdSource + if user.PasswdFrom != nil { + passwdFrom = &eksbootstrapv1.PasswdSource{ + Secret: eksbootstrapv1.SecretPasswdSource{ + Name: user.PasswdFrom.Secret.Name, + Key: user.PasswdFrom.Secret.Key, + }, + } + } + eksBootstrapUsers = append(eksBootstrapUsers, eksbootstrapv1.User{ + Name: user.Name, + Gecos: user.Gecos, + Groups: user.Groups, + HomeDir: user.HomeDir, + Inactive: user.Inactive, + Shell: user.Shell, + Passwd: user.Passwd, + PasswdFrom: passwdFrom, + PrimaryGroup: user.PrimaryGroup, + LockPassword: user.LockPassword, + Sudo: user.Sudo, + SSHAuthorizedKeys: user.SSHAuthorizedKeys, + }) + } + + obj.Spec.Template.Spec.Users = eksBootstrapUsers + return nil + }); err != nil { + return err + } + return nil } diff --git a/pkg/handlers/generic/mutation/users/inject_test.go b/pkg/handlers/generic/mutation/users/inject_test.go index 61711cd4f..cbee5a747 100644 --- a/pkg/handlers/generic/mutation/users/inject_test.go +++ b/pkg/handlers/generic/mutation/users/inject_test.go @@ -18,6 +18,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/testutils/capitest/request" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/eks/mutation/testutils" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/test/helpers" ) @@ -192,6 +193,30 @@ var _ = Describe("Generate Users patches", func() { ValueMatcher: gomega.HaveLen(2), }}, }, + { + Name: "users set for EKSConfigTemplate generic worker", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + []v1alpha1.User{testUser1, testUser2}, + VariableName, + ), + capitest.VariableWithValue( + "builtin", + map[string]any{ + "machineDeployment": map[string]any{ + "class": names.SimpleNameGenerator.GenerateName("worker-"), + }, + }, + ), + }, + RequestItem: testutils.NewEKSConfigTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/users", + ValueMatcher: gomega.HaveLen(2), + }}, + }, } // create test node for each case diff --git a/pkg/handlers/generic/mutation/users/variables_test.go b/pkg/handlers/generic/mutation/users/variables_test.go index c196f1dc6..2803b35c3 100644 --- a/pkg/handlers/generic/mutation/users/variables_test.go +++ b/pkg/handlers/generic/mutation/users/variables_test.go @@ -18,18 +18,20 @@ import ( var testDefs = []capitest.VariableTestDef{{ Name: "valid users", Vals: v1alpha1.GenericClusterConfigSpec{ - Users: []v1alpha1.User{ - { - Name: "complete", - HashedPassword: "password", - SSHAuthorizedKeys: []string{ - "key1", - "key2", + GenericClusterConfigResource: v1alpha1.GenericClusterConfigResource{ + Users: []v1alpha1.User{ + { + Name: "complete", + HashedPassword: "password", + SSHAuthorizedKeys: []string{ + "key1", + "key2", + }, + Sudo: "ALL=(ALL) NOPASSWD:ALL", + }, + { + Name: "onlyname", }, - Sudo: "ALL=(ALL) NOPASSWD:ALL", - }, - { - Name: "onlyname", }, }, }, diff --git a/pkg/webhook/addons/registry/webhook_test.go b/pkg/webhook/addons/registry/webhook_test.go index 61f47d05d..48b13e57e 100644 --- a/pkg/webhook/addons/registry/webhook_test.go +++ b/pkg/webhook/addons/registry/webhook_test.go @@ -188,7 +188,7 @@ func TestDefaultingShouldBeSkippedWhenGlobalImageRegistryMirrorIsSet(t *testing. t, nil, &carenv1.DockerClusterConfigSpec{ - GenericClusterConfigSpec: carenv1.GenericClusterConfigSpec{ + GenericClusterConfigResource: carenv1.GenericClusterConfigResource{ GlobalImageRegistryMirror: &carenv1.GlobalImageRegistryMirror{ URL: "mirror.com", }, diff --git a/pkg/webhook/preflight/generic/registry_test.go b/pkg/webhook/preflight/generic/registry_test.go index a4e70b228..e1e4d051f 100644 --- a/pkg/webhook/preflight/generic/registry_test.go +++ b/pkg/webhook/preflight/generic/registry_test.go @@ -402,8 +402,10 @@ func TestNewRegistryCheck(t *testing.T) { { name: "only registry mirror configuration", genericClusterConfigSpec: &carenv1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &carenv1.GlobalImageRegistryMirror{ - URL: testRegistryURL, + GenericClusterConfigResource: carenv1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &carenv1.GlobalImageRegistryMirror{ + URL: testRegistryURL, + }, }, }, expectedChecks: 1, @@ -411,12 +413,14 @@ func TestNewRegistryCheck(t *testing.T) { { name: "only image registries configuration", genericClusterConfigSpec: &carenv1.GenericClusterConfigSpec{ - ImageRegistries: []carenv1.ImageRegistry{ - { - URL: "https://registry1.example.com", - }, - { - URL: "https://registry2.example.com", + GenericClusterConfigResource: carenv1.GenericClusterConfigResource{ + ImageRegistries: []carenv1.ImageRegistry{ + { + URL: "https://registry1.example.com", + }, + { + URL: "https://registry2.example.com", + }, }, }, }, @@ -425,12 +429,14 @@ func TestNewRegistryCheck(t *testing.T) { { name: "both registry mirror and image registries configuration", genericClusterConfigSpec: &carenv1.GenericClusterConfigSpec{ - GlobalImageRegistryMirror: &carenv1.GlobalImageRegistryMirror{ - URL: testRegistryURL, - }, - ImageRegistries: []carenv1.ImageRegistry{ - { - URL: "https://registry1.example.com", + GenericClusterConfigResource: carenv1.GenericClusterConfigResource{ + GlobalImageRegistryMirror: &carenv1.GlobalImageRegistryMirror{ + URL: testRegistryURL, + }, + ImageRegistries: []carenv1.ImageRegistry{ + { + URL: "https://registry1.example.com", + }, }, }, }, From bcf913dad845e9638cbdf133c3dfd28022907af2 Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Tue, 19 Aug 2025 12:52:29 +0100 Subject: [PATCH 2/3] fixup! fix: Remove control plane affinity from EBS controller --- .../addons/csi/aws-ebs/values-template.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/charts/cluster-api-runtime-extensions-nutanix/addons/csi/aws-ebs/values-template.yaml b/charts/cluster-api-runtime-extensions-nutanix/addons/csi/aws-ebs/values-template.yaml index f390f36e7..20e33d412 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/addons/csi/aws-ebs/values-template.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/addons/csi/aws-ebs/values-template.yaml @@ -1,11 +1,4 @@ controller: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists tolerations: - key: CriticalAddonsOnly operator: Exists From c5dd8df620de0f15e149d3d9b59d8dbb574e100b Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Tue, 19 Aug 2025 13:52:16 +0100 Subject: [PATCH 3/3] fixup! fix: Regenerate API types and addons --- .../crds/caren.nutanix.com_eksworkernodeconfigs.yaml | 2 +- .../csi/aws-ebs/manifests/aws-ebs-csi-configmap.yaml | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml index 2de646a5c..5d1f1dfc9 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_eksworkernodeconfigs.yaml @@ -89,7 +89,7 @@ spec: default: m5.2xlarge description: The AWS instance type to use for the cluster Machines. type: string - placementGroupName: + placementGroup: description: PlacementGroup specifies the placement group in which to launch the instance. properties: diff --git a/charts/cluster-api-runtime-extensions-nutanix/templates/csi/aws-ebs/manifests/aws-ebs-csi-configmap.yaml b/charts/cluster-api-runtime-extensions-nutanix/templates/csi/aws-ebs/manifests/aws-ebs-csi-configmap.yaml index e1bec1d79..8cf4e690c 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/templates/csi/aws-ebs/manifests/aws-ebs-csi-configmap.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/templates/csi/aws-ebs/manifests/aws-ebs-csi-configmap.yaml @@ -538,11 +538,6 @@ data: - auto - hybrid weight: 1 - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: