@@ -20,8 +20,12 @@ package controllers
2020import (
2121 "bytes"
2222 "context"
23+ "fmt"
2324 "time"
2425
26+ "github.com/aws/aws-sdk-go/aws"
27+ "github.com/aws/aws-sdk-go/aws/session"
28+ "github.com/aws/aws-sdk-go/service/eks"
2529 "github.com/pkg/errors"
2630 corev1 "k8s.io/api/core/v1"
2731 apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -39,6 +43,7 @@ import (
3943 eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2"
4044 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/internal/userdata"
4145 ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
46+ expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
4247 "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
4348 "sigs.k8s.io/cluster-api-provider-aws/v2/util/paused"
4449 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -221,9 +226,19 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
221226 return nil
222227 }
223228
229+ // Get the AWSManagedControlPlane
224230 controlPlane := & ekscontrolplanev1.AWSManagedControlPlane {}
225231 if err := r .Get (ctx , client.ObjectKey {Name : cluster .Spec .ControlPlaneRef .Name , Namespace : cluster .Spec .ControlPlaneRef .Namespace }, controlPlane ); err != nil {
226- return err
232+ return errors .Wrap (err , "failed to get control plane" )
233+ }
234+
235+ // Check if control plane is ready
236+ if ! conditions .IsTrue (controlPlane , ekscontrolplanev1 .EKSControlPlaneReadyCondition ) {
237+ log .Info ("Control plane is not ready yet, waiting..." )
238+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition ,
239+ eksbootstrapv1 .DataSecretGenerationFailedReason ,
240+ clusterv1 .ConditionSeverityInfo , "Control plane is not ready yet" )
241+ return nil
227242 }
228243
229244 log .Info ("Generating userdata" )
@@ -234,61 +249,174 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
234249 return err
235250 }
236251
237- nodeInput := & userdata.NodeInput {
238- // AWSManagedControlPlane webhooks default and validate EKSClusterName
239- ClusterName : controlPlane .Spec .EKSClusterName ,
240- KubeletExtraArgs : config .Spec .KubeletExtraArgs ,
241- ContainerRuntime : config .Spec .ContainerRuntime ,
242- DNSClusterIP : config .Spec .DNSClusterIP ,
243- DockerConfigJSON : config .Spec .DockerConfigJSON ,
244- APIRetryAttempts : config .Spec .APIRetryAttempts ,
245- UseMaxPods : config .Spec .UseMaxPods ,
246- PreBootstrapCommands : config .Spec .PreBootstrapCommands ,
247- PostBootstrapCommands : config .Spec .PostBootstrapCommands ,
248- BootstrapCommandOverride : config .Spec .BootstrapCommandOverride ,
249- NTP : config .Spec .NTP ,
250- Users : config .Spec .Users ,
251- DiskSetup : config .Spec .DiskSetup ,
252- Mounts : config .Spec .Mounts ,
253- Files : files ,
254- }
255- if config .Spec .PauseContainer != nil {
256- nodeInput .PauseContainerAccount = & config .Spec .PauseContainer .AccountNumber
257- nodeInput .PauseContainerVersion = & config .Spec .PauseContainer .Version
258- }
259-
260- // Check if IPv6 was provided to the user configuration first
261- // If not, we also check if the cluster is ipv6 based.
262- if config .Spec .ServiceIPV6Cidr != nil && * config .Spec .ServiceIPV6Cidr != "" {
263- nodeInput .ServiceIPV6Cidr = config .Spec .ServiceIPV6Cidr
264- nodeInput .IPFamily = ptr.To [string ]("ipv6" )
265- }
266-
267- // we don't want to override any manually set configuration options.
268- if config .Spec .ServiceIPV6Cidr == nil && controlPlane .Spec .NetworkSpec .VPC .IsIPv6Enabled () {
269- log .Info ("Adding ipv6 data to userdata...." )
270- nodeInput .ServiceIPV6Cidr = ptr.To [string ](controlPlane .Spec .NetworkSpec .VPC .IPv6 .CidrBlock )
271- nodeInput .IPFamily = ptr.To [string ]("ipv6" )
272- }
273-
274- // generate userdata
275- userDataScript , err := userdata .NewNode (nodeInput )
276- if err != nil {
277- log .Error (err , "Failed to create a worker join configuration" )
278- conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition , eksbootstrapv1 .DataSecretGenerationFailedReason , clusterv1 .ConditionSeverityWarning , "" )
279- return err
252+ // Generate userdata based on node type
253+ var userDataScript []byte
254+
255+ if config .Spec .NodeType == "al2023" {
256+ // Use the ControlPlaneEndpoint from the AWSManagedControlPlane spec
257+ apiServerEndpoint := controlPlane .Spec .ControlPlaneEndpoint .Host
258+
259+ log .Info ("Generating AL2023 userdata" ,
260+ "cluster" , controlPlane .Spec .EKSClusterName ,
261+ "endpoint" , apiServerEndpoint )
262+
263+ // Fetch CA cert directly from EKS API
264+ sess , err := session .NewSession (& aws.Config {Region : aws .String (controlPlane .Spec .Region )})
265+ if err != nil {
266+ log .Error (err , "Failed to create AWS session for EKS API" )
267+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition ,
268+ eksbootstrapv1 .DataSecretGenerationFailedReason ,
269+ clusterv1 .ConditionSeverityWarning ,
270+ "Failed to create AWS session: %v" , err )
271+ return err
272+ }
273+ eksClient := eks .New (sess )
274+ describeInput := & eks.DescribeClusterInput {Name : aws .String (controlPlane .Spec .EKSClusterName )}
275+ clusterOut , err := eksClient .DescribeCluster (describeInput )
276+ if err != nil {
277+ log .Error (err , "Failed to describe EKS cluster for CA cert fetch" )
278+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition ,
279+ eksbootstrapv1 .DataSecretGenerationFailedReason ,
280+ clusterv1 .ConditionSeverityWarning ,
281+ "Failed to describe EKS cluster: %v" , err )
282+ return err
283+ }
284+
285+ caCert := ""
286+ if clusterOut .Cluster != nil && clusterOut .Cluster .CertificateAuthority != nil && clusterOut .Cluster .CertificateAuthority .Data != nil {
287+ caCert = * clusterOut .Cluster .CertificateAuthority .Data
288+ } else {
289+ log .Error (nil , "CA certificate not found in EKS cluster response" )
290+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition ,
291+ eksbootstrapv1 .DataSecretGenerationFailedReason ,
292+ clusterv1 .ConditionSeverityWarning ,
293+ "CA certificate not found in EKS cluster response" )
294+ return fmt .Errorf ("CA certificate not found in EKS cluster response" )
295+ }
296+
297+ // Get AMI ID from AWSManagedMachinePool's launch template if specified
298+ var amiID string
299+ if configOwner .GetKind () == "MachinePool" {
300+ amp := & expinfrav1.AWSManagedMachinePool {}
301+ if err := r .Get (ctx , client.ObjectKey {Namespace : config .Namespace , Name : configOwner .GetName ()}, amp ); err == nil {
302+ if amp .Spec .AWSLaunchTemplate != nil && amp .Spec .AWSLaunchTemplate .AMI .ID != nil {
303+ amiID = * amp .Spec .AWSLaunchTemplate .AMI .ID
304+ }
305+ }
306+ }
307+
308+ input := & userdata.AL2023UserDataInput {
309+ ClusterName : controlPlane .Spec .EKSClusterName ,
310+ APIServerEndpoint : apiServerEndpoint ,
311+ CACert : caCert ,
312+ NodeGroupName : config .Name , // Use the config name as nodegroup name
313+ MaxPods : getMaxPods (config ), // Get from config or use default
314+ ClusterDNS : getClusterDNS (config ), // Get from config or use default
315+ AMIImageID : amiID , // Use launch template AMI if specified
316+ CapacityType : getCapacityType (config ), // Get from config or use default
317+ }
318+
319+ // Try to generate userdata with retries
320+ var userDataErr error
321+ for i := 0 ; i < 3 ; i ++ { // Retry up to 3 times
322+ userDataScript , userDataErr = userdata .GenerateAL2023UserData (input )
323+ if userDataErr == nil {
324+ break
325+ }
326+ log .Error (userDataErr , "Failed to generate AL2023 userdata, retrying" ,
327+ "attempt" , i + 1 ,
328+ "cluster" , input .ClusterName )
329+ time .Sleep (time .Second * time .Duration (i + 1 )) // Exponential backoff
330+ }
331+
332+ if userDataErr != nil {
333+ log .Error (userDataErr , "Failed to generate AL2023 userdata after retries" )
334+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition ,
335+ eksbootstrapv1 .DataSecretGenerationFailedReason ,
336+ clusterv1 .ConditionSeverityWarning ,
337+ "Failed to generate AL2023 userdata: %v" , userDataErr )
338+ return userDataErr
339+ }
340+ } else {
341+ log .Info ("Generating standard userdata for node type" , "type" , config .Spec .NodeType )
342+ nodeInput := & userdata.NodeInput {
343+ // AWSManagedControlPlane webhooks default and validate EKSClusterName
344+ ClusterName : controlPlane .Spec .EKSClusterName ,
345+ KubeletExtraArgs : config .Spec .KubeletExtraArgs ,
346+ ContainerRuntime : config .Spec .ContainerRuntime ,
347+ DNSClusterIP : config .Spec .DNSClusterIP ,
348+ DockerConfigJSON : config .Spec .DockerConfigJSON ,
349+ APIRetryAttempts : config .Spec .APIRetryAttempts ,
350+ UseMaxPods : config .Spec .UseMaxPods ,
351+ PreBootstrapCommands : config .Spec .PreBootstrapCommands ,
352+ PostBootstrapCommands : config .Spec .PostBootstrapCommands ,
353+ BootstrapCommandOverride : config .Spec .BootstrapCommandOverride ,
354+ NTP : config .Spec .NTP ,
355+ Users : config .Spec .Users ,
356+ DiskSetup : config .Spec .DiskSetup ,
357+ Mounts : config .Spec .Mounts ,
358+ Files : files ,
359+ }
360+
361+ if config .Spec .PauseContainer != nil {
362+ nodeInput .PauseContainerAccount = & config .Spec .PauseContainer .AccountNumber
363+ nodeInput .PauseContainerVersion = & config .Spec .PauseContainer .Version
364+ }
365+
366+ // Check if IPv6 was provided to the user configuration first
367+ // If not, we also check if the cluster is ipv6 based.
368+ if config .Spec .ServiceIPV6Cidr != nil && * config .Spec .ServiceIPV6Cidr != "" {
369+ nodeInput .ServiceIPV6Cidr = config .Spec .ServiceIPV6Cidr
370+ nodeInput .IPFamily = ptr.To [string ]("ipv6" )
371+ }
372+
373+ // we don't want to override any manually set configuration options.
374+ if config .Spec .ServiceIPV6Cidr == nil && controlPlane .Spec .NetworkSpec .VPC .IsIPv6Enabled () {
375+ log .Info ("Adding ipv6 data to userdata...." )
376+ nodeInput .ServiceIPV6Cidr = ptr.To [string ](controlPlane .Spec .NetworkSpec .VPC .IPv6 .CidrBlock )
377+ nodeInput .IPFamily = ptr.To [string ]("ipv6" )
378+ }
379+
380+ userDataScript , err = userdata .NewNode (nodeInput )
381+ if err != nil {
382+ log .Error (err , "Failed to create a worker join configuration" )
383+ conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition , eksbootstrapv1 .DataSecretGenerationFailedReason , clusterv1 .ConditionSeverityWarning , "" )
384+ return err
385+ }
280386 }
281387
282- // store userdata as secret
388+ // Store the userdata in a secret
283389 if err := r .storeBootstrapData (ctx , cluster , config , userDataScript ); err != nil {
284390 log .Error (err , "Failed to store bootstrap data" )
285391 conditions .MarkFalse (config , eksbootstrapv1 .DataSecretAvailableCondition , eksbootstrapv1 .DataSecretGenerationFailedReason , clusterv1 .ConditionSeverityWarning , "" )
286392 return err
287393 }
288394
395+ conditions .MarkTrue (config , eksbootstrapv1 .DataSecretAvailableCondition )
289396 return nil
290397}
291398
399+ // Helper functions to get dynamic values
400+ func getMaxPods (config * eksbootstrapv1.EKSConfig ) int {
401+ if config .Spec .UseMaxPods != nil && * config .Spec .UseMaxPods {
402+ return 58 // Default value when UseMaxPods is true
403+ }
404+ return 110 // Default value when UseMaxPods is false
405+ }
406+
407+ func getClusterDNS (config * eksbootstrapv1.EKSConfig ) string {
408+ if config .Spec .DNSClusterIP != nil && * config .Spec .DNSClusterIP != "" {
409+ return * config .Spec .DNSClusterIP
410+ }
411+ return "10.96.0.10" // Default value
412+ }
413+
414+ func getCapacityType (config * eksbootstrapv1.EKSConfig ) string {
415+ // TODO: Get from AWSManagedMachinePool spec if available
416+ // For now, return default
417+ return "ON_DEMAND"
418+ }
419+
292420func (r * EKSConfigReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager , option controller.Options ) error {
293421 b := ctrl .NewControllerManagedBy (mgr ).
294422 For (& eksbootstrapv1.EKSConfig {}).
0 commit comments