Skip to content

Commit 8a3d353

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 b5a66b0 commit 8a3d353

File tree

5 files changed

+195
-33
lines changed

5 files changed

+195
-33
lines changed

pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ spec:
9696
# operator: In
9797
# values:
9898
# - linux
99+
# TODO(user): Uncomment the following code to configure the nodeSelector.
100+
# nodeSelector:
101+
# kubernetes.io/os: linux
102+
# TODO(user): Uncomment the following code to configure the tolerations.
103+
# tolerations:
104+
# - key: "key1"
105+
# operator: "Exists"
106+
# effect: "NoSchedule"
99107
securityContext:
100108
# Projects are configured by default to adhere to the "restricted" Pod Security Standards.
101109
# This ensures that deployments meet the highest security requirements for Kubernetes.

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ func (c *ChartConverter) ExtractDeploymentConfig() map[string]interface{} {
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 {
@@ -149,6 +152,48 @@ func extractPodSecurityContext(specMap map[string]interface{}, config map[string
149152
config["podSecurityContext"] = podSecurityContext
150153
}
151154

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

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ 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(yamlContent, "nodeSelector", "spec.template.spec", ".Values.manager.nodeSelector")
194+
yamlContent = t.templateBasicWithStatement(yamlContent, "affinity", "spec.template.spec", ".Values.manager.affinity")
195+
yamlContent = t.templateBasicWithStatement(yamlContent, "tolerations", "spec.template.spec", ".Values.manager.tolerations")
193196

194197
return yamlContent
195198
}
@@ -620,6 +623,91 @@ func (t *HelmTemplater) templateImageReference(yamlContent string) string {
620623
return yamlContent
621624
}
622625

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ var _ = Describe("HelmValuesBasic", func() {
159159
Expect(content).To(ContainSubstring("resources:"))
160160
Expect(content).To(ContainSubstring("cpu: 100m"))
161161
Expect(content).To(ContainSubstring("memory: 128Mi"))
162+
Expect(content).To(ContainSubstring("affinity: {}"))
163+
Expect(content).To(ContainSubstring("nodeSelector: {}"))
164+
Expect(content).To(ContainSubstring("tolerations: []"))
162165
})
163166
})
164167

0 commit comments

Comments
 (0)