Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ spec:
{}
{{- end }}
volumeMounts:
{{- if .Values.manager.extraVolumeMounts }}
{{- toYaml .Values.manager.extraVolumeMounts | nindent 10 }}
{{- end }}
{{- if and .Values.certManager.enable .Values.metrics.enable }}
- mountPath: /tmp/k8s-metrics-server/metrics-certs
name: metrics-certs
Expand All @@ -106,6 +109,9 @@ spec:
serviceAccountName: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }}
terminationGracePeriodSeconds: 10
volumes:
{{- if .Values.manager.extraVolumes }}
{{- toYaml .Values.manager.extraVolumes | nindent 8 }}
{{- end }}
{{- if and .Values.certManager.enable .Values.metrics.enable }}
- name: metrics-certs
secret:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ spec:
{{- else }}
{}
{{- end }}
volumeMounts: []
volumeMounts:
{{- if .Values.manager.extraVolumeMounts }}
{{- toYaml .Values.manager.extraVolumeMounts | nindent 10 }}
{{- else }}
[]
{{- end }}
securityContext:
{{- if .Values.manager.podSecurityContext }}
{{- toYaml .Values.manager.podSecurityContext | nindent 8 }}
Expand All @@ -86,4 +91,9 @@ spec:
{{- end }}
serviceAccountName: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }}
terminationGracePeriodSeconds: 10
volumes: []
volumes:
{{- if .Values.manager.extraVolumes }}
{{- toYaml .Values.manager.extraVolumes | nindent 8 }}
{{- else }}
[]
{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ spec:
{}
{{- end }}
volumeMounts:
{{- if .Values.manager.extraVolumeMounts }}
{{- toYaml .Values.manager.extraVolumeMounts | nindent 10 }}
{{- end }}
{{- if and .Values.certManager.enable .Values.metrics.enable }}
- mountPath: /tmp/k8s-metrics-server/metrics-certs
name: metrics-certs
Expand All @@ -106,6 +109,9 @@ spec:
serviceAccountName: {{ include "project.resourceName" (dict "suffix" "controller-manager" "context" $) }}
terminationGracePeriodSeconds: 10
volumes:
{{- if .Values.manager.extraVolumes }}
{{- toYaml .Values.manager.extraVolumes | nindent 8 }}
{{- end }}
{{- if and .Values.certManager.enable .Values.metrics.enable }}
- name: metrics-certs
secret:
Expand Down
9 changes: 9 additions & 0 deletions docs/book/src/plugins/available/helm-v2-alpha.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,15 @@ prometheus:
enable: false
```

### Extra volumes

The chart supports additional volumes and volume mounts for the manager (e.g. secrets, config files), alongside the built-in webhook and metrics cert volumes.

- **Config volumes**: Volumes in the manager deployment (e.g. `config/manager/manager.yaml` or kustomize patches) are written into the chart template. Re-running `kubebuilder edit --plugins=helm/v2-alpha` updates the template from config; `values.yaml` is not overwritten.
- **Values**: When the manager deployment has extra volumes (other than webhook/metrics), `values.yaml` gets `manager.extraVolumes` and `manager.extraVolumeMounts`. Use them to add more entries; the template appends them after the config volumes. Same structure as in a Pod spec; mount names must match volume names.

Webhook and metrics (`webhook-certs`, `metrics-certs`) are not in `extraVolumes`. They are conditional on `certManager.enable` and `metrics.enable`, like the rest of the chart.

### Installation

The first time you run the plugin, it adds convenient Helm deployment targets to your `Makefile`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ func (c *ChartConverter) ExtractDeploymentConfig() map[string]any {
extractContainerResources(container, config)
extractContainerSecurityContext(container, config)

extractExtraVolumes(specMap, config)
extractExtraVolumeMounts(container, config)

return config
}

Expand Down Expand Up @@ -416,3 +419,66 @@ func extractContainerSecurityContext(container map[string]any, config map[string

config["securityContext"] = securityContext
}

// defaultWebhookMetricsVolumeNames are volume names from the Kustomize scaffold;
// they are never added to extraVolumes (conditional in chart).
var defaultWebhookMetricsVolumeNames = map[string]struct{}{
"webhook-certs": {},
"metrics-certs": {},
}

// extractExtraVolumes copies pod volumes into config["extraVolumes"], excluding
// webhook-certs and metrics-certs. Only set when there is at least one extra volume.
func extractExtraVolumes(specMap map[string]any, config map[string]any) {
volumes, found, err := unstructured.NestedFieldNoCopy(specMap, "volumes")
if !found || err != nil {
return
}
volumesList, ok := volumes.([]any)
if !ok || len(volumesList) == 0 {
return
}
extra := make([]any, 0, len(volumesList))
for _, v := range volumesList {
vm, ok := v.(map[string]any)
if !ok {
continue
}
name, _ := vm["name"].(string)
if _, isDefault := defaultWebhookMetricsVolumeNames[name]; isDefault {
continue
}
extra = append(extra, v)
}
if len(extra) > 0 {
config["extraVolumes"] = extra
}
}

// extractExtraVolumeMounts copies container volumeMounts into config["extraVolumeMounts"],
// excluding webhook-certs and metrics-certs. Only set when there is at least one extra.
func extractExtraVolumeMounts(container map[string]any, config map[string]any) {
mounts, found, err := unstructured.NestedFieldNoCopy(container, "volumeMounts")
if !found || err != nil {
return
}
mountsList, ok := mounts.([]any)
if !ok || len(mountsList) == 0 {
return
}
extra := make([]any, 0, len(mountsList))
for _, m := range mountsList {
mm, ok := m.(map[string]any)
if !ok {
continue
}
name, _ := mm["name"].(string)
if _, isDefault := defaultWebhookMetricsVolumeNames[name]; isDefault {
continue
}
extra = append(extra, m)
}
if len(extra) > 0 {
config["extraVolumeMounts"] = extra
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,95 @@ var _ = Describe("ChartConverter", func() {
config := converter.ExtractDeploymentConfig()
Expect(config).To(BeEmpty())
})

It("should extract extraVolumes and extraVolumeMounts excluding webhook and metrics", func() {
volumes := []any{
map[string]any{
"name": "webhook-certs",
"secret": map[string]any{"secretName": "webhook-server-cert"},
},
map[string]any{
"name": "custom-volume",
"secret": map[string]any{"secretName": "my-secret"},
},
}
volumeMounts := []any{
map[string]any{
"name": "webhook-certs",
"mountPath": "/tmp/k8s-webhook-server/serving-certs",
"readOnly": true,
},
map[string]any{
"name": "custom-volume",
"mountPath": "/etc/my-secrets",
"readOnly": true,
},
}
containers := []any{
map[string]any{
"name": "manager",
"image": "controller:latest",
"volumeMounts": volumeMounts,
},
}
err := unstructured.SetNestedSlice(
resources.Deployment.Object,
volumes,
"spec", "template", "spec", "volumes",
)
Expect(err).NotTo(HaveOccurred())
err = unstructured.SetNestedSlice(
resources.Deployment.Object,
containers,
"spec", "template", "spec", "containers",
)
Expect(err).NotTo(HaveOccurred())

config := converter.ExtractDeploymentConfig()

Expect(config).To(HaveKey("extraVolumes"))
extraVols, ok := config["extraVolumes"].([]any)
Expect(ok).To(BeTrue())
Expect(extraVols).To(HaveLen(1))
Expect(extraVols[0]).To(HaveKeyWithValue("name", "custom-volume"))

Expect(config).To(HaveKey("extraVolumeMounts"))
extraMounts, ok := config["extraVolumeMounts"].([]any)
Expect(ok).To(BeTrue())
Expect(extraMounts).To(HaveLen(1))
Expect(extraMounts[0]).To(HaveKeyWithValue("mountPath", "/etc/my-secrets"))
})

It("should not add webhook-certs or metrics-certs to extraVolumes or extraVolumeMounts", func() {
volumes := []any{
map[string]any{"name": "webhook-certs", "secret": map[string]any{"secretName": "webhook-server-cert"}},
map[string]any{"name": "metrics-certs", "secret": map[string]any{"secretName": "metrics-server-cert"}},
map[string]any{"name": "app-secret", "secret": map[string]any{"secretName": "app-secret"}},
}
volumeMounts := []any{
map[string]any{"name": "webhook-certs", "mountPath": "/tmp/webhook", "readOnly": true},
map[string]any{"name": "metrics-certs", "mountPath": "/tmp/metrics", "readOnly": true},
map[string]any{"name": "app-secret", "mountPath": "/etc/app", "readOnly": true},
}
containers := []any{
map[string]any{"name": "manager", "image": "controller:latest", "volumeMounts": volumeMounts},
}
err := unstructured.SetNestedSlice(resources.Deployment.Object, volumes, "spec", "template", "spec", "volumes")
Expect(err).NotTo(HaveOccurred())
err = unstructured.SetNestedSlice(resources.Deployment.Object, containers, "spec", "template", "spec", "containers")
Expect(err).NotTo(HaveOccurred())

config := converter.ExtractDeploymentConfig()

extraVols, ok := config["extraVolumes"].([]any)
Expect(ok).To(BeTrue())
Expect(extraVols).To(HaveLen(1))
Expect(extraVols[0]).To(HaveKeyWithValue("name", "app-secret"))
extraMounts, ok := config["extraVolumeMounts"].([]any)
Expect(ok).To(BeTrue())
Expect(extraMounts).To(HaveLen(1))
Expect(extraMounts[0]).To(HaveKeyWithValue("name", "app-secret"))
})
})

Context("extractPortFromArg", func() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ func (w *ChartWriter) writeGroupDirectory(
) error {
var finalContent bytes.Buffer

// Convert each resource to YAML and apply templating
for i, resource := range resources {
if i > 0 {
finalContent.WriteString("---\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,17 +744,78 @@ func (t *HelmTemplater) templateSecurityContexts(yamlContent string) string {
return yamlContent
}

// templateVolumeMounts converts volumeMounts sections to keep them as-is since they're webhook-specific
// templateVolumeMounts appends .Values.manager.extraVolumeMounts. Webhook and metrics
// mounts are conditional (makeWebhookVolumeMountsConditional, makeMetricsVolumeMountsConditional).
func (t *HelmTemplater) templateVolumeMounts(yamlContent string) string {
// For webhook volumeMounts, we keep them as-is since they're required for webhook functionality
// They will be conditionally included based on webhook configuration
return yamlContent
return t.appendToListFromValues(yamlContent, "volumeMounts:", ".Values.manager.extraVolumeMounts")
}

// templateVolumes converts volumes sections to keep them as-is since they're webhook-specific
// templateVolumes appends .Values.manager.extraVolumes. Webhook and metrics volumes
// are conditional (makeWebhookVolumesConditional, makeMetricsVolumesConditional).
func (t *HelmTemplater) templateVolumes(yamlContent string) string {
// For webhook volumes, we keep them as-is since they're required for webhook functionality
// They will be conditionally included based on webhook configuration
return t.appendToListFromValues(yamlContent, "volumes:", ".Values.manager.extraVolumes")
}

// appendToListFromValues finds "key:" or "key: []", and either appends values path to an existing list
// or replaces "key: []" with a template that outputs the values path when set. Idempotent if already present.
func (t *HelmTemplater) appendToListFromValues(yamlContent string, keyColon string, valuesPath string) string {
if !strings.Contains(yamlContent, keyColon) {
return yamlContent
}
if strings.Contains(yamlContent, valuesPath) {
return yamlContent
}

lines := strings.Split(yamlContent, "\n")
keyEmpty := keyColon + " []"

for i := range lines {
trimmed := strings.TrimSpace(lines[i])
indentStr, indentLen := leadingWhitespace(lines[i])
childIndent := indentStr + " "
childIndentWidth := strconv.Itoa(len(childIndent))

if trimmed == keyEmpty {
block := []string{
indentStr + keyColon,
childIndent + "{{- if " + valuesPath + " }}",
childIndent + "{{- toYaml " + valuesPath + " | nindent " + childIndentWidth + " }}",
childIndent + "{{- else }}",
childIndent + "[]",
childIndent + "{{- end }}",
}
newLines := append([]string{}, lines[:i]...)
newLines = append(newLines, block...)
newLines = append(newLines, lines[i+1:]...)
return strings.Join(newLines, "\n")
}

if trimmed != keyColon {
continue
}

end := i + 1
for ; end < len(lines); end++ {
tLine := strings.TrimSpace(lines[end])
if tLine == "" {
break
}
lineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], " \t"))
if lineIndent <= indentLen {
break
}
}

block := []string{
childIndent + "{{- if " + valuesPath + " }}",
childIndent + "{{- toYaml " + valuesPath + " | nindent " + childIndentWidth + " }}",
childIndent + "{{- end }}",
}
newLines := append([]string{}, lines[:end]...)
newLines = append(newLines, block...)
newLines = append(newLines, lines[end:]...)
return strings.Join(newLines, "\n")
}
return yamlContent
}

Expand Down
Loading
Loading