Skip to content

Commit b561401

Browse files
committed
enhance userdata for ack
1 parent f36c340 commit b561401

File tree

3 files changed

+414
-47
lines changed

3 files changed

+414
-47
lines changed

pkg/providers/cluster/ackmanaged.go

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,40 @@ func (a *ACKManaged) UserData(ctx context.Context,
143143
taints []corev1.Taint,
144144
kubeletCfg *v1alpha1.KubeletConfiguration,
145145
userData *string) (string, error) {
146+
147+
attach, err := a.getClusterAttachScripts(ctx)
148+
if err != nil {
149+
return "", err
150+
}
151+
ackScript := a.ackBootstrap(attach, labels, taints, kubeletCfg)
152+
cloudInit := NewCloudInit()
153+
154+
if err := cloudInit.Merge(&ackScript); err != nil {
155+
return "", err
156+
}
157+
if err := cloudInit.Merge(userData); err != nil {
158+
return "", err
159+
}
160+
161+
return cloudInit.Script()
162+
}
163+
164+
func (a *ACKManaged) FeatureFlags() FeatureFlags {
165+
if cni, err := a.GetClusterCNI(context.TODO()); err == nil && cni == ClusterCNITypeFlannel {
166+
return FeatureFlags{
167+
PodsPerCoreEnabled: false,
168+
SupportsENILimitedPodDensity: false,
169+
}
170+
}
171+
return FeatureFlags{
172+
PodsPerCoreEnabled: true,
173+
SupportsENILimitedPodDensity: true,
174+
}
175+
}
176+
177+
func (a *ACKManaged) getClusterAttachScripts(ctx context.Context) (string, error) {
146178
if cachedScript, ok := a.cache.Get(a.clusterID); ok {
147-
return a.resolveUserData(cachedScript.(string), labels, taints, kubeletCfg, userData), nil
179+
return cachedScript.(string), nil
148180
}
149181

150182
reqPara := &ackclient.DescribeClusterAttachScriptsRequest{
@@ -174,20 +206,7 @@ func (a *ACKManaged) UserData(ctx context.Context,
174206
}
175207

176208
a.cache.SetDefault(a.clusterID, respStr)
177-
return a.resolveUserData(respStr, labels, taints, kubeletCfg, userData), nil
178-
}
179-
180-
func (a *ACKManaged) FeatureFlags() FeatureFlags {
181-
if cni, err := a.GetClusterCNI(context.TODO()); err == nil && cni == ClusterCNITypeFlannel {
182-
return FeatureFlags{
183-
PodsPerCoreEnabled: false,
184-
SupportsENILimitedPodDensity: false,
185-
}
186-
}
187-
return FeatureFlags{
188-
PodsPerCoreEnabled: true,
189-
SupportsENILimitedPodDensity: true,
190-
}
209+
return respStr, nil
191210
}
192211

193212
// We need to manually retrieve the runtime configuration of the nodepool, with the default node pool prioritized.
@@ -239,21 +258,13 @@ func (a *ACKManaged) getClusterAttachRuntimeConfiguration(ctx context.Context) (
239258
tea.StringValue(targetNodepool.KubernetesConfig.RuntimeVersion), nil
240259
}
241260

242-
func (a *ACKManaged) resolveUserData(respStr string, labels map[string]string, taints []corev1.Taint,
243-
kubeletCfg *v1alpha1.KubeletConfiguration, userData *string) string {
244-
preUserData, postUserData := parseCustomUserData(userData)
261+
func (a *ACKManaged) ackBootstrap(respStr string, labels map[string]string, taints []corev1.Taint,
262+
kubeletCfg *v1alpha1.KubeletConfiguration) string {
245263

246264
var script bytes.Buffer
247265
// Add bash script header
248266
script.WriteString("#!/bin/bash\n\n")
249267

250-
// Insert preUserData if available
251-
if preUserData != "" {
252-
// Pre-userData: scripts to be executed before node registration
253-
script.WriteString("echo \"Executing preUserData...\"\n")
254-
script.WriteString(preUserData + "\n\n")
255-
}
256-
257268
// Clean up the input string
258269
script.WriteString(respStr + " ")
259270
// Add labels
@@ -264,15 +275,7 @@ func (a *ACKManaged) resolveUserData(respStr string, labels map[string]string, t
264275
// Add taints
265276
script.WriteString(fmt.Sprintf("--taints %s\n\n", a.formatTaints(taints)))
266277

267-
// Insert postUserData if available
268-
if postUserData != "" {
269-
// Post-userData: scripts to be executed after node registration
270-
script.WriteString("echo \"Executing postUserData...\"\n")
271-
script.WriteString(postUserData + "\n")
272-
}
273-
274-
// Encode to base64
275-
return base64.StdEncoding.EncodeToString(script.Bytes())
278+
return script.String()
276279
}
277280

278281
func (a *ACKManaged) formatLabels(labels map[string]string) string {
@@ -310,16 +313,3 @@ func convertNodeClassKubeletConfigToACKNodeConfig(kubeletCfg *v1alpha1.KubeletCo
310313
}
311314
return base64.StdEncoding.EncodeToString(data)
312315
}
313-
314-
const userDataSeparator = "#===USERDATA_SEPARATOR==="
315-
316-
// By default, the UserData is executed after the node registration is completed.
317-
// If a user requires tasks to be executed both before and after node registration,
318-
// they must split the userdata into preUserData and postUserData using a SEPARATOR.
319-
func parseCustomUserData(userData *string) (string, string) {
320-
parts := strings.Split(tea.StringValue(userData), userDataSeparator)
321-
if len(parts) == 2 {
322-
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
323-
}
324-
return "", tea.StringValue(userData)
325-
}

pkg/providers/cluster/cloudinit.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cluster
2+
3+
import (
4+
"mime"
5+
"strings"
6+
7+
awsmime "github.com/aws/karpenter-provider-aws/pkg/providers/amifamily/bootstrap/mime"
8+
"github.com/samber/lo"
9+
)
10+
11+
const (
12+
contentTypeStage string = `stage`
13+
contentTypeShellScriptMediaType string = `text/x-shellscript`
14+
)
15+
16+
type CloudInit struct {
17+
entries []awsmime.Entry
18+
}
19+
20+
func NewCloudInit() *CloudInit {
21+
return &CloudInit{
22+
entries: make([]awsmime.Entry, 0),
23+
}
24+
}
25+
26+
func (c *CloudInit) Script() (string, error) {
27+
c.sort()
28+
mimeArchive := awsmime.Archive(c.entries)
29+
userData, err := mimeArchive.Serialize()
30+
if err != nil {
31+
return "", err
32+
}
33+
return userData, nil
34+
}
35+
36+
func (c *CloudInit) Merge(userdata *string) error {
37+
userData := lo.FromPtr(userdata)
38+
if userData == "" {
39+
return nil
40+
}
41+
if strings.HasPrefix(strings.TrimSpace(userData), "MIME-Version:") ||
42+
strings.HasPrefix(strings.TrimSpace(userData), "Content-Type:") {
43+
archive, err := awsmime.NewArchive(userData)
44+
if err != nil {
45+
return err
46+
}
47+
c.entries = append(c.entries, archive...)
48+
return nil
49+
}
50+
// Fallback to YAML or shall script if UserData is not in MIME format. Determine the content type for the
51+
// generated MIME header depending on the type of the custom UserData.
52+
c.entries = append(c.entries, awsmime.Entry{
53+
ContentType: awsmime.ContentTypeShellScript,
54+
Content: userData,
55+
})
56+
return nil
57+
}
58+
59+
func (c *CloudInit) sort() {
60+
var pre, non []awsmime.Entry
61+
for _, entry := range c.entries {
62+
mediaType, params, err := mime.ParseMediaType(string(entry.ContentType))
63+
if err != nil {
64+
non = append(non, entry)
65+
continue
66+
}
67+
if stage, ok := params[contentTypeStage]; ok && mediaType == contentTypeShellScriptMediaType && stage == "pre" {
68+
pre = append(pre, entry)
69+
} else {
70+
non = append(non, entry)
71+
}
72+
}
73+
c.entries = append(pre, non...)
74+
}

0 commit comments

Comments
 (0)