Skip to content

Commit ae78ce5

Browse files
robinlioretRobin LIORET
authored andcommitted
feat: helm v2-alpha: add nodeSelector, toleration and affinity to the chart
Signed-off-by: Robin LIORET <robin.lioret@darylsocialsoftware.com>
1 parent a0708da commit ae78ce5

File tree

12 files changed

+337
-33
lines changed

12 files changed

+337
-33
lines changed

docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/manager/manager.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ spec:
2121
app.kubernetes.io/name: project
2222
control-plane: controller-manager
2323
spec:
24+
{{- with .Values.manager.tolerations }}
25+
tolerations: {{ toYaml . | nindent 16 }}
26+
{{- end }}
27+
{{- with .Values.manager.affinity }}
28+
affinity: {{ toYaml . | nindent 16 }}
29+
{{- end }}
30+
{{- with .Values.manager.nodeSelector }}
31+
nodeSelector: {{ toYaml . | nindent 16 }}
32+
{{- end }}
2433
containers:
2534
- args:
2635
{{- if .Values.metrics.enable }}

docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ manager:
3737
cpu: 10m
3838
memory: 64Mi
3939

40+
# Pod's affinity
41+
affinity: {}
42+
43+
# Pod's node selector
44+
nodeSelector: {}
45+
46+
# Pod's tolerations
47+
tolerations: []
48+
4049
# Essential RBAC permissions (required for controller operation)
4150
# These include ServiceAccount, controller permissions, leader election, and metrics access
4251
# Note: Essential RBAC is always enabled as it's required for the controller to function

docs/book/src/getting-started/testdata/project/dist/chart/templates/manager/manager.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ spec:
2121
app.kubernetes.io/name: project
2222
control-plane: controller-manager
2323
spec:
24+
{{- with .Values.manager.tolerations }}
25+
tolerations: {{ toYaml . | nindent 16 }}
26+
{{- end }}
27+
{{- with .Values.manager.affinity }}
28+
affinity: {{ toYaml . | nindent 16 }}
29+
{{- end }}
30+
{{- with .Values.manager.nodeSelector }}
31+
nodeSelector: {{ toYaml . | nindent 16 }}
32+
{{- end }}
2433
containers:
2534
- args:
2635
{{- if .Values.metrics.enable }}

docs/book/src/getting-started/testdata/project/dist/chart/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ manager:
3737
cpu: 10m
3838
memory: 64Mi
3939

40+
# Pod's affinity
41+
affinity: {}
42+
43+
# Pod's node selector
44+
nodeSelector: {}
45+
46+
# Pod's tolerations
47+
tolerations: []
48+
4049
# Essential RBAC permissions (required for controller operation)
4150
# These include ServiceAccount, controller permissions, leader election, and metrics access
4251
# Note: Essential RBAC is always enabled as it's required for the controller to function

docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/manager/manager.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ spec:
2121
app.kubernetes.io/name: project
2222
control-plane: controller-manager
2323
spec:
24+
{{- with .Values.manager.tolerations }}
25+
tolerations: {{ toYaml . | nindent 16 }}
26+
{{- end }}
27+
{{- with .Values.manager.affinity }}
28+
affinity: {{ toYaml . | nindent 16 }}
29+
{{- end }}
30+
{{- with .Values.manager.nodeSelector }}
31+
nodeSelector: {{ toYaml . | nindent 16 }}
32+
{{- end }}
2433
containers:
2534
- args:
2635
{{- if .Values.metrics.enable }}

docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ manager:
3737
cpu: 10m
3838
memory: 64Mi
3939

40+
# Pod's affinity
41+
affinity: {}
42+
43+
# Pod's node selector
44+
nodeSelector: {}
45+
46+
# Pod's tolerations
47+
tolerations: []
48+
4049
# Essential RBAC permissions (required for controller operation)
4150
# These include ServiceAccount, controller permissions, leader election, and metrics access
4251
# Note: Essential RBAC is always enabled as it's required for the controller to function

pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ func (c *ChartConverter) ExtractDeploymentConfig() map[string]any {
105105
}
106106

107107
extractPodSecurityContext(specMap, config)
108+
extractPodNodeSelector(specMap, config)
109+
extractPodTolerations(specMap, config)
110+
extractPodAffinity(specMap, config)
108111

109112
container := firstManagerContainer(specMap)
110113
if container == nil {
@@ -148,6 +151,47 @@ func extractPodSecurityContext(specMap map[string]any, config map[string]any) {
148151

149152
config["podSecurityContext"] = podSecurityContext
150153
}
154+
func extractPodNodeSelector(specMap map[string]any, config map[string]any) {
155+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "nodeSelector")
156+
if !found || err != nil {
157+
return
158+
}
159+
160+
result, ok := raw.(map[string]any)
161+
if !ok || len(result) == 0 {
162+
return
163+
}
164+
165+
config["podNodeSelector"] = result
166+
}
167+
168+
func extractPodTolerations(specMap map[string]any, config map[string]any) {
169+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "tolerations")
170+
if !found || err != nil {
171+
return
172+
}
173+
174+
result, ok := raw.([]any)
175+
if !ok || len(result) == 0 {
176+
return
177+
}
178+
179+
config["podTolerations"] = result
180+
}
181+
182+
func extractPodAffinity(specMap map[string]any, config map[string]any) {
183+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "affinity")
184+
if !found || err != nil {
185+
return
186+
}
187+
188+
result, ok := raw.(map[string]any)
189+
if !ok || len(result) == 0 {
190+
return
191+
}
192+
193+
config["podAffinity"] = result
194+
}
151195

152196
func firstManagerContainer(specMap map[string]any) map[string]any {
153197
containers, found, err := unstructured.NestedFieldNoCopy(specMap, "containers")

pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ func (t *HelmTemplater) templateDeploymentFields(yamlContent string) string {
190190
yamlContent = t.templateVolumeMounts(yamlContent)
191191
yamlContent = t.templateVolumes(yamlContent)
192192
yamlContent = t.templateControllerManagerArgs(yamlContent)
193+
yamlContent = t.templateBasicWithStatement(
194+
yamlContent,
195+
"nodeSelector",
196+
"spec.template.spec",
197+
".Values.manager.nodeSelector",
198+
)
199+
yamlContent = t.templateBasicWithStatement(
200+
yamlContent,
201+
"affinity",
202+
"spec.template.spec",
203+
".Values.manager.affinity",
204+
)
205+
yamlContent = t.templateBasicWithStatement(
206+
yamlContent,
207+
"tolerations",
208+
"spec.template.spec",
209+
".Values.manager.tolerations",
210+
)
193211

194212
return yamlContent
195213
}
@@ -617,6 +635,88 @@ func (t *HelmTemplater) templateImageReference(yamlContent string) string {
617635
return yamlContent
618636
}
619637

638+
func (t *HelmTemplater) templateBasicWithStatement(
639+
yamlContent string,
640+
key string,
641+
parentKey string,
642+
valuePath string,
643+
) string {
644+
lines := strings.Split(yamlContent, "\n")
645+
yamlKey := fmt.Sprintf("%s:", key)
646+
647+
var start, end int
648+
var indentLen int
649+
if !strings.Contains(yamlContent, yamlKey) {
650+
// Find parent block start if the key is missing
651+
pKeyParts := strings.Split(parentKey, ".")
652+
pKeyIdx := 0
653+
pKeyInit := false
654+
currIndent := 0
655+
for i := 0; i < len(lines); i++ {
656+
_, lineIndent := leadingWhitespace(lines[i])
657+
if pKeyInit && lineIndent <= currIndent {
658+
return yamlContent
659+
}
660+
if !strings.HasPrefix(strings.TrimSpace(lines[i]), pKeyParts[pKeyIdx]) {
661+
continue
662+
}
663+
664+
// Parent key part found
665+
pKeyIdx++
666+
pKeyInit = true
667+
if pKeyIdx >= len(pKeyParts) {
668+
start = i + 1
669+
end = start
670+
break
671+
}
672+
}
673+
_, indentLen = leadingWhitespace(lines[start])
674+
} else {
675+
// Find the existing block
676+
for i := 0; i < len(lines); i++ {
677+
if !strings.HasPrefix(strings.TrimSpace(lines[i]), key) {
678+
continue
679+
}
680+
start = i
681+
end = i + 1
682+
trimmed := strings.TrimSpace(lines[i])
683+
if len(trimmed) == len(yamlKey) {
684+
_, indentLenSearch := leadingWhitespace(lines[i])
685+
for j := end; j < len(lines); j++ {
686+
_, indentLenLine := leadingWhitespace(lines[j])
687+
if indentLenLine <= indentLenSearch {
688+
end = j
689+
break
690+
}
691+
}
692+
}
693+
}
694+
_, indentLen = leadingWhitespace(lines[start])
695+
}
696+
697+
indentStr := strings.Repeat(" ", indentLen)
698+
699+
var builder strings.Builder
700+
builder.WriteString(indentStr)
701+
builder.WriteString("{{- with ")
702+
builder.WriteString(valuePath)
703+
builder.WriteString(" }}\n")
704+
builder.WriteString(indentStr)
705+
builder.WriteString(yamlKey)
706+
builder.WriteString(" {{ toYaml . | nindent ")
707+
builder.WriteString(strconv.Itoa(indentLen + 4))
708+
builder.WriteString(" }}\n")
709+
builder.WriteString(indentStr)
710+
builder.WriteString("{{- end }}\n")
711+
712+
newBlock := strings.TrimRight(builder.String(), "\n")
713+
714+
newLines := append([]string{}, lines[:start]...)
715+
newLines = append(newLines, strings.Split(newBlock, "\n")...)
716+
newLines = append(newLines, lines[end:]...)
717+
return strings.Join(newLines, "\n")
718+
}
719+
620720
// makeWebhookAnnotationsConditional makes only cert-manager annotations conditional, not the entire webhook
621721
func (t *HelmTemplater) makeWebhookAnnotationsConditional(yamlContent string) string {
622722
// Find cert-manager.io/inject-ca-from annotation and make it conditional

pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,7 @@ func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
210210
buf.WriteString(" # Environment variables\n")
211211
buf.WriteString(" env:\n")
212212
if envYaml, err := yaml.Marshal(env); err == nil {
213-
// Indent the YAML properly
214-
lines := bytes.SplitSeq(envYaml, []byte("\n"))
215-
for line := range lines {
216-
if len(line) > 0 {
217-
buf.WriteString(" ")
218-
buf.Write(line)
219-
buf.WriteString("\n")
220-
}
221-
}
213+
f.IndentYamlProperly(buf, envYaml)
222214
} else {
223215
buf.WriteString(" []\n")
224216
}
@@ -233,14 +225,7 @@ func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
233225
buf.WriteString(" # Pod-level security settings\n")
234226
buf.WriteString(" podSecurityContext:\n")
235227
if secYaml, err := yaml.Marshal(podSecCtx); err == nil {
236-
lines := bytes.SplitSeq(secYaml, []byte("\n"))
237-
for line := range lines {
238-
if len(line) > 0 {
239-
buf.WriteString(" ")
240-
buf.Write(line)
241-
buf.WriteString("\n")
242-
}
243-
}
228+
f.IndentYamlProperly(buf, secYaml)
244229
}
245230
buf.WriteString("\n")
246231
} else {
@@ -252,14 +237,7 @@ func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
252237
buf.WriteString(" # Container-level security settings\n")
253238
buf.WriteString(" securityContext:\n")
254239
if secYaml, err := yaml.Marshal(secCtx); err == nil {
255-
lines := bytes.SplitSeq(secYaml, []byte("\n"))
256-
for line := range lines {
257-
if len(line) > 0 {
258-
buf.WriteString(" ")
259-
buf.Write(line)
260-
buf.WriteString("\n")
261-
}
262-
}
240+
f.IndentYamlProperly(buf, secYaml)
263241
}
264242
buf.WriteString("\n")
265243
} else {
@@ -271,19 +249,59 @@ func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
271249
buf.WriteString(" # Resource limits and requests\n")
272250
buf.WriteString(" resources:\n")
273251
if resYaml, err := yaml.Marshal(resources); err == nil {
274-
lines := bytes.SplitSeq(resYaml, []byte("\n"))
275-
for line := range lines {
276-
if len(line) > 0 {
277-
buf.WriteString(" ")
278-
buf.Write(line)
279-
buf.WriteString("\n")
280-
}
281-
}
252+
f.IndentYamlProperly(buf, resYaml)
282253
}
283254
buf.WriteString("\n")
284255
} else {
285256
f.addDefaultResources(buf)
286257
}
258+
259+
buf.WriteString(" # Pod's affinity\n")
260+
if affinity, exists := f.DeploymentConfig["podAffinity"]; exists && affinity != nil {
261+
buf.WriteString(" affinity:\n")
262+
if affYaml, err := yaml.Marshal(affinity); err == nil {
263+
f.IndentYamlProperly(buf, affYaml)
264+
}
265+
buf.WriteString("\n")
266+
} else {
267+
buf.WriteString(" affinity: {}\n")
268+
buf.WriteString("\n")
269+
}
270+
271+
buf.WriteString(" # Pod's node selector\n")
272+
if nodeSelector, exists := f.DeploymentConfig["podNodeSelector"]; exists && nodeSelector != nil {
273+
buf.WriteString(" nodeSelector:\n")
274+
if nodYaml, err := yaml.Marshal(nodeSelector); err == nil {
275+
f.IndentYamlProperly(buf, nodYaml)
276+
}
277+
buf.WriteString("\n")
278+
} else {
279+
buf.WriteString(" nodeSelector: {}\n")
280+
buf.WriteString("\n")
281+
}
282+
283+
buf.WriteString(" # Pod's tolerations\n")
284+
if tolerations, exists := f.DeploymentConfig["podTolerations"]; exists && tolerations != nil {
285+
buf.WriteString(" tolerations:\n")
286+
if tolYaml, err := yaml.Marshal(tolerations); err == nil {
287+
f.IndentYamlProperly(buf, tolYaml)
288+
}
289+
buf.WriteString("\n")
290+
} else {
291+
buf.WriteString(" tolerations: []\n")
292+
buf.WriteString("\n")
293+
}
294+
}
295+
296+
func (f *HelmValuesBasic) IndentYamlProperly(buf *bytes.Buffer, envYaml []byte) {
297+
lines := bytes.SplitSeq(envYaml, []byte("\n"))
298+
for line := range lines {
299+
if len(line) > 0 {
300+
buf.WriteString(" ")
301+
buf.Write(line)
302+
buf.WriteString("\n")
303+
}
304+
}
287305
}
288306

289307
// addDefaultDeploymentSections adds default sections when no deployment config is available

0 commit comments

Comments
 (0)