From e5ea31cb45816a28297f2164916975cd89c10117 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Sun, 8 Feb 2026 08:34:47 +0000 Subject: [PATCH] fix(helm/v2-alpha): Fixed Helm chart generation to support custom container names by reading the kubectl.kubernetes.io/default-container annotation instead of hardcoding "manager" Generated-by: Cursor/Claude --- .../internal/kustomize/helm_templater.go | 29 +++- .../internal/kustomize/helm_templater_test.go | 135 ++++++++++++++++++ 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go index b4f46aa5956..81298f623e2 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go @@ -62,6 +62,20 @@ func NewHelmTemplater(detectedPrefix, chartName, managerNamespace string) *HelmT } } +// getDefaultContainerName extracts the container name from kubectl.kubernetes.io/default-container annotation. +// This allows the Helm plugin to work with any container name, not just "manager". +// If the annotation is not found, it falls back to "manager" for backward compatibility. +func (t *HelmTemplater) getDefaultContainerName(yamlContent string) string { + // Look for kubectl.kubernetes.io/default-container annotation + pattern := regexp.MustCompile(`kubectl\.kubernetes\.io/default-container:\s+(\S+)`) + matches := pattern.FindStringSubmatch(yamlContent) + if len(matches) > 1 { + return matches[1] + } + // Fallback to "manager" for backward compatibility with older scaffolds + return "manager" +} + // resourceNameTemplate creates a Helm template for a resource name with 63-char safety. // Uses .resourceName helper which intelligently truncates when base + suffix > 63 chars. // Template name is scoped to the chart to prevent collisions when used as a Helm dependency. @@ -530,7 +544,8 @@ func (t *HelmTemplater) templateDeploymentFields(yamlContent string) string { // templateEnvironmentVariables exposes environment variables via values.yaml func (t *HelmTemplater) templateEnvironmentVariables(yamlContent string) string { - if !strings.Contains(yamlContent, "name: manager") { + containerName := t.getDefaultContainerName(yamlContent) + if !strings.Contains(yamlContent, "name: "+containerName) { return yamlContent } @@ -583,7 +598,8 @@ func (t *HelmTemplater) templateEnvironmentVariables(yamlContent string) string // templateResources converts resource sections to Helm templates func (t *HelmTemplater) templateResources(yamlContent string) string { - if !strings.Contains(yamlContent, "name: manager") || !strings.Contains(yamlContent, "resources:") { + containerName := t.getDefaultContainerName(yamlContent) + if !strings.Contains(yamlContent, "name: "+containerName) || !strings.Contains(yamlContent, "resources:") { return yamlContent } @@ -770,7 +786,8 @@ func (t *HelmTemplater) templatePodSecurityContext(yamlContent string) string { // templateContainerSecurityContext exposes container securityContext via values.yaml func (t *HelmTemplater) templateContainerSecurityContext(yamlContent string) string { - if !strings.Contains(yamlContent, "name: manager") || !strings.Contains(yamlContent, "securityContext:") { + containerName := t.getDefaultContainerName(yamlContent) + if !strings.Contains(yamlContent, "name: "+containerName) || !strings.Contains(yamlContent, "securityContext:") { return yamlContent } @@ -836,7 +853,8 @@ func leadingWhitespace(line string) (string, int) { // templateControllerManagerArgs exposes controller manager args via values.yaml while keeping core defaults func (t *HelmTemplater) templateControllerManagerArgs(yamlContent string) string { - if !strings.Contains(yamlContent, "name: manager") { + containerName := t.getDefaultContainerName(yamlContent) + if !strings.Contains(yamlContent, "name: "+containerName) { return yamlContent } @@ -937,7 +955,8 @@ func (t *HelmTemplater) templateControllerManagerArgs(yamlContent string) string // templateImageReference converts hardcoded image references to Helm templates func (t *HelmTemplater) templateImageReference(yamlContent string) string { - if !strings.Contains(yamlContent, "name: manager") { + containerName := t.getDefaultContainerName(yamlContent) + if !strings.Contains(yamlContent, "name: "+containerName) { return yamlContent } diff --git a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go index 3f563adc1e9..9263a3db1ff 100644 --- a/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go +++ b/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go @@ -1935,4 +1935,139 @@ subjects: Expect(result).To(ContainSubstring(expectedSA)) }) }) + + Context("custom container name support", func() { + It("should template deployment fields when container name is not 'manager'", func() { + deployment := &unstructured.Unstructured{} + deployment.SetAPIVersion("apps/v1") + deployment.SetKind("Deployment") + deployment.SetName("test-project-controller-manager") + + // Deployment with custom container name "osiris-manager" using default-container annotation + content := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-project-controller-manager + namespace: test-project-system +spec: + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: osiris-manager + spec: + containers: + - name: osiris-manager + image: docker.io/server/osiris:1.0.5 + imagePullPolicy: Always + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + args: + - --leader-elect + - --health-probe-bind-address=:8081 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + volumeMounts: [] + serviceAccountName: controller-manager + volumes: []` + + result := templater.ApplyHelmSubstitutions(content, deployment) + + // Should template image reference (not hardcoded) + Expect(result).To(ContainSubstring( + `image: "{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}"`)) + Expect(result).NotTo(ContainSubstring("image: docker.io/server/osiris:1.0.5")) + + // Should template imagePullPolicy + Expect(result).To(ContainSubstring("imagePullPolicy: {{ .Values.manager.image.pullPolicy }}")) + Expect(result).NotTo(ContainSubstring("imagePullPolicy: Always")) + + // Should template resources + Expect(result).To(ContainSubstring("{{- if .Values.manager.resources }}")) + Expect(result).To(ContainSubstring("{{- toYaml .Values.manager.resources | nindent")) + + // Should template environment variables + Expect(result).To(ContainSubstring("{{- if .Values.manager.env }}")) + Expect(result).To(ContainSubstring("{{- toYaml .Values.manager.env | nindent")) + + // Should template args + Expect(result).To(ContainSubstring("{{- range .Values.manager.args }}")) + + // Container name should remain "osiris-manager" + Expect(result).To(ContainSubstring("name: osiris-manager")) + }) + + It("should fall back to 'manager' when default-container annotation is missing", func() { + deployment := &unstructured.Unstructured{} + deployment.SetAPIVersion("apps/v1") + deployment.SetKind("Deployment") + deployment.SetName("test-project-controller-manager") + + // Deployment without default-container annotation (backward compatibility test) + content := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-project-controller-manager +spec: + template: + spec: + containers: + - name: manager + image: controller:latest + resources: + limits: + cpu: 500m + memory: 128Mi` + + result := templater.ApplyHelmSubstitutions(content, deployment) + + // Should still template fields for "manager" container + Expect(result).To(ContainSubstring( + `image: "{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}"`)) + Expect(result).To(ContainSubstring("{{- if .Values.manager.resources }}")) + }) + + It("should not template when container name doesn't match annotation", func() { + deployment := &unstructured.Unstructured{} + deployment.SetAPIVersion("apps/v1") + deployment.SetKind("Deployment") + deployment.SetName("test-project-controller-manager") + + // Deployment with mismatched annotation and container name + content := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-project-controller-manager +spec: + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: main-container + spec: + containers: + - name: sidecar + image: sidecar:latest + resources: + limits: + cpu: 100m` + + result := templater.ApplyHelmSubstitutions(content, deployment) + + // Should NOT template sidecar container (doesn't match annotation) + Expect(result).To(ContainSubstring("image: sidecar:latest")) + Expect(result).NotTo(ContainSubstring("{{ .Values.manager.image.repository }}")) + }) + }) })