@@ -23,7 +23,8 @@ import (
23
23
"text/template"
24
24
25
25
"github.com/alessio/shellescape"
26
-
26
+ "k8s.io/klog/v2"
27
+ "k8s.io/utils/ptr"
27
28
eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/bootstrap/eks/api/v1beta2"
28
29
"sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
29
30
)
@@ -50,10 +51,32 @@ runcmd:
50
51
{{- template "mounts" .Mounts}}
51
52
`
52
53
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
55
56
Content-Type: multipart/mixed; boundary="{{.Boundary}}"
56
57
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 = `
57
80
--{{.Boundary}}
58
81
Content-Type: application/node.eks.aws
59
82
@@ -62,26 +85,24 @@ apiVersion: node.eks.aws/v1alpha1
62
85
kind: NodeConfig
63
86
spec:
64
87
cluster:
88
+ name: {{.ClusterName}}
65
89
apiServerEndpoint: {{.APIServerEndpoint}}
66
90
certificateAuthority: {{.CACert}}
67
- cidr: 10.96.0.0/12
68
- name: {{.ClusterName}}
91
+ cidr: {{if .ClusterCIDR}}{{.ClusterCIDR}}{{else}}10.96.0.0/12{{end}}
69
92
kubelet:
70
93
config:
71
94
maxPods: {{.MaxPods}}
72
95
clusterDNS:
73
- - {{.ClusterDNS }}
96
+ - {{.DNSClusterIP }}
74
97
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}}"
76
99
77
100
--{{.Boundary}}--`
78
101
79
102
// AL2023-specific templates.
80
103
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}}"
85
106
{{- end -}}
86
107
{{- end -}}`
87
108
@@ -177,7 +198,12 @@ spec:
177
198
{{- end -}}`
178
199
)
179
200
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.
181
207
type NodeInput struct {
182
208
ClusterName string
183
209
KubeletExtraArgs map [string ]string
@@ -210,6 +236,10 @@ type NodeInput struct {
210
236
NodeGroupName string
211
237
AMIImageID string
212
238
CapacityType * v1beta2.ManagedMachinePoolCapacityType
239
+ MaxPods * int32
240
+ Boundary string
241
+ ClusterDNS string
242
+ ClusterCIDR string // CIDR range for the cluster
213
243
}
214
244
215
245
// PauseContainerInfo holds pause container information for templates.
@@ -302,129 +332,85 @@ func generateStandardUserData(input *NodeInput) ([]byte, error) {
302
332
303
333
// generateAL2023UserData generates userdata for Amazon Linux 2023 nodes.
304
334
func 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
317
337
}
318
338
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
324
340
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 )
329
344
}
330
345
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 )
341
354
}
342
355
}
343
356
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 )
348
361
}
349
362
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
+ }
353
365
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"
361
370
}
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 ))
369
378
}
379
+ }
370
380
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" )
386
394
}
387
395
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 )
391
401
}
392
402
}
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" )
398
405
}
406
+ input .ClusterDNS = * input .DNSClusterIP
399
407
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
421
410
}
422
411
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 ())
428
414
429
- return out . Bytes (), nil
415
+ return nil
430
416
}
0 commit comments