Skip to content

Commit f6d1a11

Browse files
committed
Add imagePullSecrets parsing in helm v2-alpha plugin
1 parent 82ab3ee commit f6d1a11

File tree

10 files changed

+219
-1
lines changed

10 files changed

+219
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ manager:
1414
# Environment variables
1515
env: []
1616

17+
# Image pull secrets
18+
imagePullSecrets: []
19+
1720
# Pod-level security settings
1821
podSecurityContext:
1922
runAsNonRoot: true

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ manager:
1414
# Environment variables
1515
env: []
1616

17+
# Image pull secrets
18+
imagePullSecrets: []
19+
1720
# Pod-level security settings
1821
podSecurityContext:
1922
runAsNonRoot: true

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ manager:
1414
# Environment variables
1515
env: []
1616

17+
# Image pull secrets
18+
imagePullSecrets: []
19+
1720
# Pod-level security settings
1821
podSecurityContext:
1922
runAsNonRoot: true

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (c *ChartConverter) ExtractDeploymentConfig() map[string]any {
105105
}
106106

107107
extractPodSecurityContext(specMap, config)
108-
108+
extractImagePullSecrets(specMap, config)
109109
container := firstManagerContainer(specMap)
110110
if container == nil {
111111
return config
@@ -135,6 +135,20 @@ func extractDeploymentSpec(deployment *unstructured.Unstructured) map[string]any
135135
return specMap
136136
}
137137

138+
func extractImagePullSecrets(specMap map[string]any, config map[string]any) {
139+
imagePullSecrets, found, err := unstructured.NestedFieldNoCopy(specMap, "imagePullSecrets")
140+
if !found || err != nil {
141+
return
142+
}
143+
144+
imagePullSecretsList, ok := imagePullSecrets.([]any)
145+
if !ok || len(imagePullSecretsList) == 0 {
146+
return
147+
}
148+
149+
config["imagePullSecrets"] = imagePullSecretsList
150+
}
151+
138152
func extractPodSecurityContext(specMap map[string]any, config map[string]any) {
139153
podSecurityContext, found, err := unstructured.NestedFieldNoCopy(specMap, "securityContext")
140154
if !found || err != nil {

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,39 @@ var _ = Describe("ChartConverter", func() {
274274
Expect(config["webhookPort"]).To(Equal(9444))
275275
})
276276

277+
It("should extract imagePullSecrets", func() {
278+
// Set up deployment with image pull secrets
279+
containers := []any{
280+
map[string]any{
281+
"name": "manager",
282+
"image": "controller:latest",
283+
},
284+
}
285+
imagePullSecrets := []any{
286+
map[string]any{
287+
"name": "test-secret",
288+
},
289+
}
290+
// Set the image pull secrets
291+
err := unstructured.SetNestedSlice(
292+
resources.Deployment.Object,
293+
imagePullSecrets,
294+
"spec", "template", "spec", "imagePullSecrets",
295+
)
296+
Expect(err).NotTo(HaveOccurred())
297+
// Set the containers
298+
err = unstructured.SetNestedSlice(
299+
resources.Deployment.Object,
300+
containers,
301+
"spec", "template", "spec", "containers",
302+
)
303+
Expect(err).NotTo(HaveOccurred())
304+
305+
config := converter.ExtractDeploymentConfig()
306+
Expect(config).To(HaveKey("imagePullSecrets"))
307+
Expect(config["imagePullSecrets"]).To(Equal(imagePullSecrets))
308+
})
309+
277310
It("should handle deployment without containers", func() {
278311
config := converter.ExtractDeploymentConfig()
279312
Expect(config).To(BeEmpty())

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func (t *HelmTemplater) templateDeploymentFields(yamlContent string) string {
183183
// Template configuration fields
184184
yamlContent = t.templateImageReference(yamlContent)
185185
yamlContent = t.templateEnvironmentVariables(yamlContent)
186+
yamlContent = t.templateImagePullSecrets(yamlContent)
186187
yamlContent = t.templatePodSecurityContext(yamlContent)
187188
yamlContent = t.templateContainerSecurityContext(yamlContent)
188189
yamlContent = t.templateResources(yamlContent)
@@ -325,6 +326,57 @@ func (t *HelmTemplater) templateVolumes(yamlContent string) string {
325326
return yamlContent
326327
}
327328

329+
// templateImagePullSecrets exposes imagePullSecrets via values.yaml
330+
func (t *HelmTemplater) templateImagePullSecrets(yamlContent string) string {
331+
if !strings.Contains(yamlContent, "imagePullSecrets:") {
332+
return yamlContent
333+
}
334+
335+
lines := strings.Split(yamlContent, "\n")
336+
for i := range lines {
337+
// Use prefix to allow `imagePullSecrets: []` to be preserved
338+
if !strings.HasPrefix(strings.TrimSpace(lines[i]), "imagePullSecrets:") {
339+
continue
340+
}
341+
indentStr, indentLen := leadingWhitespace(lines[i])
342+
end := i + 1
343+
for ; end < len(lines); end++ {
344+
trimmed := strings.TrimSpace(lines[end])
345+
if trimmed == "" {
346+
break
347+
}
348+
lineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], " \t"))
349+
if lineIndent < indentLen {
350+
break
351+
}
352+
if lineIndent == indentLen && !strings.HasPrefix(trimmed, "-") {
353+
break
354+
}
355+
}
356+
357+
if i+1 < len(lines) && strings.Contains(lines[i+1], ".Values.manager.imagePullSecrets") {
358+
return yamlContent
359+
}
360+
361+
childIndent := indentStr + " "
362+
childIndentWidth := strconv.Itoa(len(childIndent))
363+
364+
block := []string{
365+
indentStr + "{{- if .Values.manager.imagePullSecrets }}",
366+
indentStr + "imagePullSecrets:",
367+
childIndent + "{{- toYaml .Values.manager.imagePullSecrets | nindent " + childIndentWidth + " }}",
368+
indentStr + "{{- end }}",
369+
}
370+
371+
newLines := append([]string{}, lines[:i]...)
372+
newLines = append(newLines, block...)
373+
newLines = append(newLines, lines[end:]...)
374+
return strings.Join(newLines, "\n")
375+
}
376+
377+
return yamlContent
378+
}
379+
328380
// templatePodSecurityContext exposes podSecurityContext via values.yaml
329381
func (t *HelmTemplater) templatePodSecurityContext(yamlContent string) string {
330382
if !strings.Contains(yamlContent, "securityContext:") {

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,58 @@ metadata:
230230
Expect(result).NotTo(ContainSubstring(`{{- include "chart.labels"`))
231231
Expect(result).NotTo(ContainSubstring(`{{- include "chart.annotations"`))
232232
})
233+
234+
It("should template imagePullSecrets", func() {
235+
deploymentResource := &unstructured.Unstructured{}
236+
deploymentResource.SetAPIVersion("apps/v1")
237+
deploymentResource.SetKind("Deployment")
238+
deploymentResource.SetName("test-project-controller-manager")
239+
240+
content := `apiVersion: apps/v1
241+
kind: Deployment
242+
spec:
243+
template:
244+
spec:
245+
imagePullSecrets:
246+
- name: test-secret
247+
containers:
248+
- args:
249+
- --metrics-bind-address=:8443
250+
- --health-probe-bind-address=:8081
251+
- --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs/tls.crt
252+
- --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs/tls.crt
253+
- --leader-elect`
254+
255+
result := templater.ApplyHelmSubstitutions(content, deploymentResource)
256+
257+
Expect(result).To(ContainSubstring("imagePullSecrets:"))
258+
Expect(result).NotTo(ContainSubstring("test-secret"))
259+
})
260+
261+
It("should template empty imagePullSecrets", func() {
262+
deploymentResource := &unstructured.Unstructured{}
263+
deploymentResource.SetAPIVersion("apps/v1")
264+
deploymentResource.SetKind("Deployment")
265+
deploymentResource.SetName("test-project-controller-manager")
266+
267+
content := `apiVersion: apps/v1
268+
kind: Deployment
269+
spec:
270+
template:
271+
spec:
272+
imagePullSecrets: []
273+
containers:
274+
- args:
275+
- --metrics-bind-address=:8443
276+
- --health-probe-bind-address=:8081
277+
- --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs/tls.crt
278+
- --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs/tls.crt
279+
- --leader-elect`
280+
281+
result := templater.ApplyHelmSubstitutions(content, deploymentResource)
282+
283+
Expect(result).To(ContainSubstring("imagePullSecrets:"))
284+
})
233285
})
234286

235287
Context("conditional wrapping", func() {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,25 @@ func (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {
228228
buf.WriteString(" env: []\n\n")
229229
}
230230

231+
// Add image pull secrets
232+
if imagePullSecrets, exists := f.DeploymentConfig["imagePullSecrets"]; exists && imagePullSecrets != nil {
233+
buf.WriteString(" # Image pull secrets\n")
234+
buf.WriteString(" imagePullSecrets:\n")
235+
if imagePullSecretsYaml, err := yaml.Marshal(imagePullSecrets); err == nil {
236+
lines := bytes.SplitSeq(imagePullSecretsYaml, []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+
}
244+
}
245+
buf.WriteString("\n")
246+
} else {
247+
f.addDefaultImagePullSecrets(buf)
248+
}
249+
231250
// Add podSecurityContext
232251
if podSecCtx, exists := f.DeploymentConfig["podSecurityContext"]; exists && podSecCtx != nil {
233252
buf.WriteString(" # Pod-level security settings\n")
@@ -291,6 +310,7 @@ func (f *HelmValuesBasic) addDefaultDeploymentSections(buf *bytes.Buffer) {
291310
buf.WriteString(" # Environment variables\n")
292311
buf.WriteString(" env: []\n\n")
293312

313+
f.addDefaultImagePullSecrets(buf)
294314
f.addDefaultPodSecurityContext(buf)
295315
f.addDefaultSecurityContext(buf)
296316
f.addDefaultResources(buf)
@@ -323,6 +343,12 @@ func (f *HelmValuesBasic) addArgsSection(buf *bytes.Buffer) {
323343
buf.WriteString(" args: []\n\n")
324344
}
325345

346+
// addDefaultImagePullSecrets adds default imagePullSecrets section
347+
func (f *HelmValuesBasic) addDefaultImagePullSecrets(buf *bytes.Buffer) {
348+
buf.WriteString(" # Image pull secrets\n")
349+
buf.WriteString(" imagePullSecrets: []\n\n")
350+
}
351+
326352
// addDefaultPodSecurityContext adds default podSecurityContext section
327353
func (f *HelmValuesBasic) addDefaultPodSecurityContext(buf *bytes.Buffer) {
328354
buf.WriteString(" # Pod-level security settings\n")

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var _ = Describe("HelmValuesBasic", func() {
5555
Expect(content).To(ContainSubstring("metrics:"))
5656
Expect(content).To(ContainSubstring("prometheus:"))
5757
Expect(content).To(ContainSubstring("rbacHelpers:"))
58+
Expect(content).To(ContainSubstring("imagePullSecrets: []"))
5859
})
5960
})
6061

@@ -84,6 +85,7 @@ var _ = Describe("HelmValuesBasic", func() {
8485
Expect(content).To(ContainSubstring("metrics:"))
8586
Expect(content).To(ContainSubstring("prometheus:"))
8687
Expect(content).To(ContainSubstring("rbacHelpers:"))
88+
Expect(content).To(ContainSubstring("imagePullSecrets: []"))
8789
})
8890
})
8991

@@ -162,6 +164,33 @@ var _ = Describe("HelmValuesBasic", func() {
162164
})
163165
})
164166

167+
Context("with multiple imagePullSecrets", func() {
168+
BeforeEach(func() {
169+
valuesTemplate = &HelmValuesBasic{
170+
DeploymentConfig: map[string]any{
171+
"imagePullSecrets": []any{
172+
map[string]any{
173+
"name": "test-secret",
174+
},
175+
map[string]any{
176+
"name": "test-secret2",
177+
},
178+
},
179+
},
180+
}
181+
valuesTemplate.InjectProjectName("test-private-project")
182+
err := valuesTemplate.SetTemplateDefaults()
183+
Expect(err).NotTo(HaveOccurred())
184+
})
185+
186+
It("should render multiple imagePullSecrets", func() {
187+
content := valuesTemplate.GetBody()
188+
Expect(content).To(ContainSubstring("imagePullSecrets:"))
189+
Expect(content).To(ContainSubstring("- name: test-secret"))
190+
Expect(content).To(ContainSubstring("- name: test-secret2"))
191+
})
192+
})
193+
165194
Context("with complex env variables", func() {
166195
BeforeEach(func() {
167196
valuesTemplate = &HelmValuesBasic{

testdata/project-v4-with-plugins/dist/chart/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ manager:
1818
- name: MEMCACHED_IMAGE
1919
value: memcached:1.6.26-alpine3.19
2020

21+
# Image pull secrets
22+
imagePullSecrets: []
23+
2124
# Pod-level security settings
2225
podSecurityContext:
2326
runAsNonRoot: true

0 commit comments

Comments
 (0)