@@ -23,7 +23,8 @@ import (
2323 "text/template"
2424
2525 "github.com/alessio/shellescape"
26-
26+ "k8s.io/klog/v2"
27+ "k8s.io/utils/ptr"
2728 eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2"
2829 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
2930)
@@ -50,10 +51,32 @@ runcmd:
5051{{- template "mounts" .Mounts}}
5152`
5253
53- // Multipart MIME template for AL2023.
54- al2023UserDataTemplate = `MIME-Version: 1.0
54+ // Common MIME header and boundary template
55+ mimeHeaderTemplate = `MIME-Version: 1.0
5556Content-Type: multipart/mixed; boundary="{{.Boundary}}"
5657
58+ `
59+
60+ // Shell script part template for AL2023
61+ shellScriptPartTemplate = `--{{.Boundary}}
62+ Content-Type: text/x-shellscript; charset="us-ascii"
63+
64+ #!/bin/bash
65+ set -o errexit
66+ set -o pipefail
67+ set -o nounset
68+ {{- if or .PreBootstrapCommands .PostBootstrapCommands }}
69+
70+ {{- range .PreBootstrapCommands}}
71+ {{.}}
72+ {{- end}}
73+ {{- range .PostBootstrapCommands}}
74+ {{.}}
75+ {{- end}}
76+ {{- end}}`
77+
78+ // Node config part template for AL2023
79+ nodeConfigPartTemplate = `
5780--{{.Boundary}}
5881Content-Type: application/node.eks.aws
5982
@@ -62,26 +85,24 @@ apiVersion: node.eks.aws/v1alpha1
6285kind: NodeConfig
6386spec:
6487 cluster:
88+ name: {{.ClusterName}}
6589 apiServerEndpoint: {{.APIServerEndpoint}}
6690 certificateAuthority: {{.CACert}}
67- cidr: 10.96.0.0/12
68- name: {{.ClusterName}}
91+ cidr: {{if .ClusterCIDR}}{{.ClusterCIDR}}{{else}}10.96.0.0/12{{end}}
6992 kubelet:
7093 config:
7194 maxPods: {{.MaxPods}}
7295 clusterDNS:
73- - {{.ClusterDNS }}
96+ - {{.DNSClusterIP }}
7497 flags:
75- - "--node-labels=eks.amazonaws.com/nodegroup-image={{.AMIImageID}},eks.amazonaws.com/capacityType={{.CapacityType}}, eks.amazonaws.com/nodegroup={{.NodeGroupName}}" {{.PreCommands }}{{.PostCommands}}{{template "al2023KubeletExtraArgs" .KubeletExtraArgs}}{{template "al2023ContainerRuntime" .ContainerRuntime}}{{template "al2023DockerConfig" .DockerConfigJSONEscaped }}{{template "al2023APIRetryAttempts" .APIRetryAttempts }}{{template "al2023PauseContainer" .PauseContainerInfo}}{{template "al2023Files" .Files}}{{template "al2023DiskSetup" .DiskSetup}}{{template "al2023Mounts" .Mounts}}{{template "al2023Users" .Users }}{{template "al2023NTP" .NTP}}
98+ - "--node-labels={{if and .KubeletExtraArgs (index .KubeletExtraArgs "node-labels")}}{{index .KubeletExtraArgs "node-labels"}}{{else}} eks.amazonaws.com/nodegroup-image ={{if .AMIImageID}} {{.AMIImageID }}{{end}},eks.amazonaws.com/capacityType={{if .CapacityType }}{{.CapacityType }}{{else}}ON_DEMAND{{end}},eks.amazonaws.com/nodegroup={{.NodeGroupName }}{{end}}"
7699
77100--{{.Boundary}}--`
78101
79102 // AL2023-specific templates.
80103 al2023KubeletExtraArgsTemplate = `{{- define "al2023KubeletExtraArgs" -}}
81- {{- if . -}}
82- {{- range $k, $v := . -}}
83- - "--{{$k}}={{$v}}"
84- {{- end -}}
104+ {{- if . }}
105+ - "--node-labels={{range $k, $v := .}}{{$k}}={{$v}}{{end}}"
85106{{- end -}}
86107{{- end -}}`
87108
@@ -177,7 +198,12 @@ spec:
177198{{- end -}}`
178199)
179200
180- // NodeInput defines the context to generate a node user data.
201+ // NodeUserData is responsible for generating userdata for EKS nodes.
202+ type NodeUserData struct {
203+ input * NodeInput
204+ }
205+
206+ // NodeInput contains all the information required to generate user data for a node.
181207type NodeInput struct {
182208 ClusterName string
183209 KubeletExtraArgs map [string ]string
@@ -210,6 +236,10 @@ type NodeInput struct {
210236 NodeGroupName string
211237 AMIImageID string
212238 CapacityType * v1beta2.ManagedMachinePoolCapacityType
239+ MaxPods * int32
240+ Boundary string
241+ ClusterDNS string
242+ ClusterCIDR string // CIDR range for the cluster
213243}
214244
215245// PauseContainerInfo holds pause container information for templates.
@@ -302,129 +332,85 @@ func generateStandardUserData(input *NodeInput) ([]byte, error) {
302332
303333// generateAL2023UserData generates userdata for Amazon Linux 2023 nodes.
304334func generateAL2023UserData (input * NodeInput ) ([]byte , error ) {
305- // Validate required AL2023 fields
306- if input .APIServerEndpoint == "" {
307- return nil , fmt .Errorf ("API server endpoint is required for AL2023" )
308- }
309- if input .CACert == "" {
310- return nil , fmt .Errorf ("CA certificate is required for AL2023" )
311- }
312- if input .ClusterName == "" {
313- return nil , fmt .Errorf ("cluster name is required for AL2023" )
314- }
315- if input .NodeGroupName == "" {
316- return nil , fmt .Errorf ("node group name is required for AL2023" )
335+ if err := validateAL2023Input (input ); err != nil {
336+ return nil , err
317337 }
318338
319- // Calculate maxPods based on UseMaxPods setting
320- maxPods := 110 // Default when UseMaxPods is false
321- if input .UseMaxPods != nil && * input .UseMaxPods {
322- maxPods = 58 // Default when UseMaxPods is true
323- }
339+ var buf bytes.Buffer
324340
325- // Get cluster DNS
326- clusterDNS := "10.96.0.10" // Default value
327- if input .DNSClusterIP != nil && * input .DNSClusterIP != "" {
328- clusterDNS = * input .DNSClusterIP
341+ // Write MIME header
342+ if _ , err := buf .WriteString (fmt .Sprintf ("MIME-Version: 1.0\n Content-Type: multipart/mixed; boundary=\" %s\" \n \n " , input .Boundary )); err != nil {
343+ return nil , fmt .Errorf ("failed to write MIME header: %v" , err )
329344 }
330345
331- // Get capacity type as string
332- capacityType := "ON_DEMAND" // Default value
333- if input .CapacityType != nil {
334- switch * input .CapacityType {
335- case v1beta2 .ManagedMachinePoolCapacityTypeSpot :
336- capacityType = "SPOT"
337- case v1beta2 .ManagedMachinePoolCapacityTypeOnDemand :
338- capacityType = "ON_DEMAND"
339- default :
340- capacityType = strings .ToUpper (string (* input .CapacityType ))
346+ // Write shell script part if needed
347+ if len (input .PreBootstrapCommands ) > 0 || len (input .PostBootstrapCommands ) > 0 {
348+ shellScriptTemplate := template .Must (template .New ("shell" ).Parse (shellScriptPartTemplate ))
349+ if err := shellScriptTemplate .Execute (& buf , input ); err != nil {
350+ return nil , fmt .Errorf ("failed to execute shell script template: %v" , err )
351+ }
352+ if _ , err := buf .WriteString ("\n " ); err != nil {
353+ return nil , fmt .Errorf ("failed to write newline: %v" , err )
341354 }
342355 }
343356
344- // Get AMI ID - use empty string if not specified
345- amiID := ""
346- if input . AMIImageID != "" {
347- amiID = input . AMIImageID
357+ // Write node config part
358+ nodeConfigTemplate := template . Must ( template . New ( "node" ). Parse ( nodeConfigPartTemplate ))
359+ if err := nodeConfigTemplate . Execute ( & buf , input ); err != nil {
360+ return nil , fmt . Errorf ( "failed to execute node config template: %v" , err )
348361 }
349362
350- // Debug logging
351- fmt .Printf ("DEBUG: AL2023 Userdata Generation - maxPods: %d, clusterDNS: %s, amiID: %s, capacityType: %s\n " ,
352- maxPods , clusterDNS , amiID , capacityType )
363+ return buf .Bytes (), nil
364+ }
353365
354- // Generate pre/post commands sections
355- preCommands := ""
356- if len (input .PreBootstrapCommands ) > 0 {
357- preCommands = "\n preKubeadmCommands:"
358- for _ , cmd := range input .PreBootstrapCommands {
359- preCommands += fmt .Sprintf ("\n - %s" , cmd )
360- }
366+ // getCapacityTypeString returns the string representation of the capacity type
367+ func (ni * NodeInput ) getCapacityTypeString () string {
368+ if ni .CapacityType == nil {
369+ return "ON_DEMAND"
361370 }
362-
363- postCommands := ""
364- if len ( input . PostBootstrapCommands ) > 0 {
365- postCommands = " \n postKubeadmCommands:"
366- for _ , cmd := range input . PostBootstrapCommands {
367- postCommands += fmt . Sprintf ( " \n - %s" , cmd )
368- }
371+ switch * ni . CapacityType {
372+ case v1beta2 . ManagedMachinePoolCapacityTypeSpot :
373+ return "SPOT"
374+ case v1beta2 . ManagedMachinePoolCapacityTypeOnDemand :
375+ return "ON_DEMAND"
376+ default :
377+ return strings . ToUpper ( string ( * ni . CapacityType ))
369378 }
379+ }
370380
371- // Create template with all AL2023 templates
372- tm := template .New ("AL2023Node" ).Funcs (defaultTemplateFuncMap )
373-
374- // Parse all AL2023-specific templates
375- templates := []string {
376- al2023KubeletExtraArgsTemplate ,
377- al2023ContainerRuntimeTemplate ,
378- al2023DockerConfigTemplate ,
379- al2023APIRetryAttemptsTemplate ,
380- al2023PauseContainerTemplate ,
381- al2023FilesTemplate ,
382- al2023DiskSetupTemplate ,
383- al2023MountsTemplate ,
384- al2023UsersTemplate ,
385- al2023NTPTemplate ,
381+ // validateAL2023Input validates the input for AL2023 user data generation
382+ func validateAL2023Input (input * NodeInput ) error {
383+ if input .APIServerEndpoint == "" {
384+ return fmt .Errorf ("API server endpoint is required for AL2023" )
385+ }
386+ if input .CACert == "" {
387+ return fmt .Errorf ("CA certificate is required for AL2023" )
388+ }
389+ if input .ClusterName == "" {
390+ return fmt .Errorf ("cluster name is required for AL2023" )
391+ }
392+ if input .NodeGroupName == "" {
393+ return fmt .Errorf ("node group name is required for AL2023" )
386394 }
387395
388- for _ , tpl := range templates {
389- if _ , err := tm .Parse (tpl ); err != nil {
390- return nil , fmt .Errorf ("failed to parse AL2023 template: %w" , err )
396+ if input .MaxPods == nil {
397+ if input .UseMaxPods != nil && * input .UseMaxPods {
398+ input .MaxPods = ptr.To [int32 ](58 )
399+ } else {
400+ input .MaxPods = ptr.To [int32 ](110 )
391401 }
392402 }
393-
394- // Parse the main AL2023 template
395- t , err := tm .Parse (al2023UserDataTemplate )
396- if err != nil {
397- return nil , fmt .Errorf ("failed to parse AL2023 userdata template: %w" , err )
403+ if input .DNSClusterIP == nil {
404+ input .DNSClusterIP = ptr.To [string ]("10.96.0.10" )
398405 }
406+ input .ClusterDNS = * input .DNSClusterIP
399407
400- // Create template data with all fields
401- templateData := struct {
402- * NodeInput
403- Boundary string
404- MaxPods int
405- ClusterDNS string
406- CapacityType string
407- AMIImageID string
408- PreCommands string
409- PostCommands string
410- PauseContainerInfo PauseContainerInfo
411- }{
412- NodeInput : input ,
413- Boundary : boundary ,
414- MaxPods : maxPods ,
415- ClusterDNS : clusterDNS ,
416- CapacityType : capacityType ,
417- AMIImageID : amiID ,
418- PreCommands : preCommands ,
419- PostCommands : postCommands ,
420- PauseContainerInfo : PauseContainerInfo {AccountNumber : input .PauseContainerAccount , Version : input .PauseContainerVersion },
408+ if input .Boundary == "" {
409+ input .Boundary = boundary
421410 }
422411
423- // Execute template
424- var out bytes.Buffer
425- if err := t .Execute (& out , templateData ); err != nil {
426- return nil , fmt .Errorf ("failed to generate AL2023 userdata: %w" , err )
427- }
412+ klog .V (2 ).Infof ("AL2023 Userdata Generation - maxPods: %d, clusterDNS: %s, amiID: %s, capacityType: %s" ,
413+ * input .MaxPods , * input .DNSClusterIP , input .AMIImageID , input .getCapacityTypeString ())
428414
429- return out . Bytes (), nil
415+ return nil
430416}
0 commit comments