Skip to content

Commit 6240baa

Browse files
robinlioretRobin LIORET
authored andcommitted
feat: helm v2-alpha: add nodeSelector, toleration and affinity to the chart
1 parent a97ba96 commit 6240baa

File tree

13 files changed

+368
-33
lines changed

13 files changed

+368
-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
@@ -40,6 +40,15 @@ manager:
4040
cpu: 10m
4141
memory: 64Mi
4242

43+
# Manager pod's affinity
44+
affinity: {}
45+
46+
# Manager pod's node selector
47+
nodeSelector: {}
48+
49+
# Manager pod's tolerations
50+
tolerations: []
51+
4352
# Essential RBAC permissions (required for controller operation)
4453
# These include ServiceAccount, controller permissions, leader election, and metrics access
4554
# 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
@@ -40,6 +40,15 @@ manager:
4040
cpu: 10m
4141
memory: 64Mi
4242

43+
# Manager pod's affinity
44+
affinity: {}
45+
46+
# Manager pod's node selector
47+
nodeSelector: {}
48+
49+
# Manager pod's tolerations
50+
tolerations: []
51+
4352
# Essential RBAC permissions (required for controller operation)
4453
# These include ServiceAccount, controller permissions, leader election, and metrics access
4554
# 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
@@ -40,6 +40,15 @@ manager:
4040
cpu: 10m
4141
memory: 64Mi
4242

43+
# Manager pod's affinity
44+
affinity: {}
45+
46+
# Manager pod's node selector
47+
nodeSelector: {}
48+
49+
# Manager pod's tolerations
50+
tolerations: []
51+
4352
# Essential RBAC permissions (required for controller operation)
4453
# These include ServiceAccount, controller permissions, leader election, and metrics access
4554
# Note: Essential RBAC is always enabled as it's required for the controller to function

docs/book/src/plugins/available/helm-v2-alpha.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,35 @@ controllerManager:
188188
cpu: 10m
189189
memory: 64Mi
190190

191+
# Manager pod's affinity
192+
affinity:
193+
nodeAffinity:
194+
requiredDuringSchedulingIgnoredDuringExecution:
195+
nodeSelectorTerms:
196+
- matchExpressions:
197+
- key: kubernetes.io/arch
198+
operator: In
199+
values:
200+
- amd64
201+
- arm64
202+
- ppc64le
203+
- s390x
204+
- key: kubernetes.io/os
205+
operator: In
206+
values:
207+
- linux
208+
209+
# Manager pod's node selector
210+
nodeSelector:
211+
kubernetes.io/os: linux
212+
213+
# Manager pod's tolerations
214+
tolerations:
215+
- key: "node.kubernetes.io/unreachable"
216+
operator: "Exists"
217+
effect: "NoExecute"
218+
tolerationSeconds: 6000
219+
191220
# Essential RBAC permissions (required for controller operation)
192221
# These include ServiceAccount, controller permissions, leader election, and metrics access
193222
# 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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ func (c *ChartConverter) ExtractDeploymentConfig() map[string]any {
106106

107107
extractPodSecurityContext(specMap, config)
108108
extractImagePullSecrets(specMap, config)
109+
extractPodNodeSelector(specMap, config)
110+
extractPodTolerations(specMap, config)
111+
extractPodAffinity(specMap, config)
112+
109113
container := firstManagerContainer(specMap)
110114
if container == nil {
111115
return config
@@ -163,6 +167,48 @@ func extractPodSecurityContext(specMap map[string]any, config map[string]any) {
163167
config["podSecurityContext"] = podSecurityContext
164168
}
165169

170+
func extractPodNodeSelector(specMap map[string]any, config map[string]any) {
171+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "nodeSelector")
172+
if !found || err != nil {
173+
return
174+
}
175+
176+
result, ok := raw.(map[string]any)
177+
if !ok || len(result) == 0 {
178+
return
179+
}
180+
181+
config["podNodeSelector"] = result
182+
}
183+
184+
func extractPodTolerations(specMap map[string]any, config map[string]any) {
185+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "tolerations")
186+
if !found || err != nil {
187+
return
188+
}
189+
190+
result, ok := raw.([]any)
191+
if !ok || len(result) == 0 {
192+
return
193+
}
194+
195+
config["podTolerations"] = result
196+
}
197+
198+
func extractPodAffinity(specMap map[string]any, config map[string]any) {
199+
raw, found, err := unstructured.NestedFieldNoCopy(specMap, "affinity")
200+
if !found || err != nil {
201+
return
202+
}
203+
204+
result, ok := raw.(map[string]any)
205+
if !ok || len(result) == 0 {
206+
return
207+
}
208+
209+
config["podAffinity"] = result
210+
}
211+
166212
func firstManagerContainer(specMap map[string]any) map[string]any {
167213
containers, found, err := unstructured.NestedFieldNoCopy(specMap, "containers")
168214
if !found || err != nil {

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
@@ -191,6 +191,24 @@ func (t *HelmTemplater) templateDeploymentFields(yamlContent string) string {
191191
yamlContent = t.templateVolumeMounts(yamlContent)
192192
yamlContent = t.templateVolumes(yamlContent)
193193
yamlContent = t.templateControllerManagerArgs(yamlContent)
194+
yamlContent = t.templateBasicWithStatement(
195+
yamlContent,
196+
"nodeSelector",
197+
"spec.template.spec",
198+
".Values.manager.nodeSelector",
199+
)
200+
yamlContent = t.templateBasicWithStatement(
201+
yamlContent,
202+
"affinity",
203+
"spec.template.spec",
204+
".Values.manager.affinity",
205+
)
206+
yamlContent = t.templateBasicWithStatement(
207+
yamlContent,
208+
"tolerations",
209+
"spec.template.spec",
210+
".Values.manager.tolerations",
211+
)
194212

195213
return yamlContent
196214
}
@@ -669,6 +687,88 @@ func (t *HelmTemplater) templateImageReference(yamlContent string) string {
669687
return yamlContent
670688
}
671689

690+
func (t *HelmTemplater) templateBasicWithStatement(
691+
yamlContent string,
692+
key string,
693+
parentKey string,
694+
valuePath string,
695+
) string {
696+
lines := strings.Split(yamlContent, "\n")
697+
yamlKey := fmt.Sprintf("%s:", key)
698+
699+
var start, end int
700+
var indentLen int
701+
if !strings.Contains(yamlContent, yamlKey) {
702+
// Find parent block start if the key is missing
703+
pKeyParts := strings.Split(parentKey, ".")
704+
pKeyIdx := 0
705+
pKeyInit := false
706+
currIndent := 0
707+
for i := range len(lines) {
708+
_, lineIndent := leadingWhitespace(lines[i])
709+
if pKeyInit && lineIndent <= currIndent {
710+
return yamlContent
711+
}
712+
if !strings.HasPrefix(strings.TrimSpace(lines[i]), pKeyParts[pKeyIdx]) {
713+
continue
714+
}
715+
716+
// Parent key part found
717+
pKeyIdx++
718+
pKeyInit = true
719+
if pKeyIdx >= len(pKeyParts) {
720+
start = i + 1
721+
end = start
722+
break
723+
}
724+
}
725+
_, indentLen = leadingWhitespace(lines[start])
726+
} else {
727+
// Find the existing block
728+
for i := range len(lines) {
729+
if !strings.HasPrefix(strings.TrimSpace(lines[i]), key) {
730+
continue
731+
}
732+
start = i
733+
end = i + 1
734+
trimmed := strings.TrimSpace(lines[i])
735+
if len(trimmed) == len(yamlKey) {
736+
_, indentLenSearch := leadingWhitespace(lines[i])
737+
for j := end; j < len(lines); j++ {
738+
_, indentLenLine := leadingWhitespace(lines[j])
739+
if indentLenLine <= indentLenSearch {
740+
end = j
741+
break
742+
}
743+
}
744+
}
745+
}
746+
_, indentLen = leadingWhitespace(lines[start])
747+
}
748+
749+
indentStr := strings.Repeat(" ", indentLen)
750+
751+
var builder strings.Builder
752+
builder.WriteString(indentStr)
753+
builder.WriteString("{{- with ")
754+
builder.WriteString(valuePath)
755+
builder.WriteString(" }}\n")
756+
builder.WriteString(indentStr)
757+
builder.WriteString(yamlKey)
758+
builder.WriteString(" {{ toYaml . | nindent ")
759+
builder.WriteString(strconv.Itoa(indentLen + 4))
760+
builder.WriteString(" }}\n")
761+
builder.WriteString(indentStr)
762+
builder.WriteString("{{- end }}\n")
763+
764+
newBlock := strings.TrimRight(builder.String(), "\n")
765+
766+
newLines := append([]string{}, lines[:start]...)
767+
newLines = append(newLines, strings.Split(newBlock, "\n")...)
768+
newLines = append(newLines, lines[end:]...)
769+
return strings.Join(newLines, "\n")
770+
}
771+
672772
// makeWebhookAnnotationsConditional makes only cert-manager annotations conditional, not the entire webhook
673773
func (t *HelmTemplater) makeWebhookAnnotationsConditional(yamlContent string) string {
674774
// Find cert-manager.io/inject-ca-from annotation and make it conditional

0 commit comments

Comments
 (0)