diff --git a/examples/app/templates/deployment.yaml b/examples/app/templates/deployment.yaml index e591c525..59fc426f 100644 --- a/examples/app/templates/deployment.yaml +++ b/examples/app/templates/deployment.yaml @@ -17,6 +17,13 @@ spec: labels: app: myapp {{- include "app.selectorLabels" . | nindent 8 }} + {{- if .Values.myapp.podLabels }} + {{- toYaml .Values.myapp.podLabels | nindent 8 }} + {{- end }} + annotations: + {{- with .Values.myapp.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} spec: affinity: {{- toYaml .Values.myapp.affinity | nindent 8 }} containers: diff --git a/examples/app/values.yaml b/examples/app/values.yaml index 3c1edaed..7c3070f8 100644 --- a/examples/app/values.yaml +++ b/examples/app/values.yaml @@ -95,6 +95,8 @@ myapp: nodeSelector: region: east type: user-node + podAnnotations: {} + podLabels: {} podSecurityContext: fsGroup: 20000 runAsNonRoot: true diff --git a/examples/operator/templates/deployment.yaml b/examples/operator/templates/deployment.yaml index 5f2fc173..2aecf599 100644 --- a/examples/operator/templates/deployment.yaml +++ b/examples/operator/templates/deployment.yaml @@ -23,6 +23,13 @@ spec: labels: control-plane: controller-manager {{- include "operator.selectorLabels" . | nindent 8 }} + {{- if .Values.controllerManager.podLabels }} + {{- toYaml .Values.controllerManager.podLabels | nindent 8 }} + {{- end }} + annotations: + {{- with .Values.controllerManager.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} spec: containers: - args: {{- toYaml .Values.controllerManager.kubeRbacProxy.args | nindent 8 }} diff --git a/examples/operator/values.yaml b/examples/operator/values.yaml index 495b1641..41cebfd3 100644 --- a/examples/operator/values.yaml +++ b/examples/operator/values.yaml @@ -43,6 +43,8 @@ controllerManager: nodeSelector: region: east type: user-node + podAnnotations: {} + podLabels: {} podSecurityContext: runAsNonRoot: true replicas: 1 diff --git a/pkg/processor/deployment/deployment.go b/pkg/processor/deployment/deployment.go index ad7c9234..3f3caae3 100644 --- a/pkg/processor/deployment/deployment.go +++ b/pkg/processor/deployment/deployment.go @@ -108,23 +108,31 @@ func (d deployment) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstr selector = strings.Trim(selector, " \n") selector = string(yamlformat.Indent([]byte(selector), 4)) + nameCamel := strcase.ToLowerCamel(name) podLabels, err := yamlformat.Marshal(depl.Spec.Template.ObjectMeta.Labels, 8) if err != nil { return true, nil, err } - podLabels += fmt.Sprintf("\n {{- include \"%s.selectorLabels\" . | nindent 8 }}", appMeta.ChartName()) + podLabels += fmt.Sprintf("\n {{- include \"%s.selectorLabels\" . | nindent 8 }}\n {{- if .Values.%s.podLabels }}\n {{- toYaml .Values.%s.podLabels | nindent 8 }}\n {{- end }}", appMeta.ChartName(), nameCamel, nameCamel) + err = unstructured.SetNestedField(values, make(map[string]interface{}), nameCamel, "podLabels") + if err != nil { + return true, nil, err + } - podAnnotations := "" + podAnnotations := "\n annotations:" if len(depl.Spec.Template.ObjectMeta.Annotations) != 0 { - podAnnotations, err = yamlformat.Marshal(map[string]interface{}{"annotations": depl.Spec.Template.ObjectMeta.Annotations}, 6) + staticAnnotations, err := yamlformat.Marshal(depl.Spec.Template.ObjectMeta.Annotations, 8) if err != nil { return true, nil, err } - - podAnnotations = "\n" + podAnnotations + podAnnotations += "\n" + staticAnnotations + } + podAnnotations += fmt.Sprintf("\n {{- with .Values.%s.podAnnotations }}\n {{- toYaml . | nindent 8 }}\n {{- end }}", nameCamel) + err = unstructured.SetNestedField(values, make(map[string]interface{}), nameCamel, "podAnnotations") + if err != nil { + return true, nil, err } - nameCamel := strcase.ToLowerCamel(name) specMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, depl.Spec.Template.Spec, 0) if err != nil { return true, nil, err diff --git a/pkg/processor/deployment/deployment_test.go b/pkg/processor/deployment/deployment_test.go index 07a2046f..2396f5d9 100644 --- a/pkg/processor/deployment/deployment_test.go +++ b/pkg/processor/deployment/deployment_test.go @@ -1,6 +1,7 @@ package deployment import ( + "bytes" "testing" "github.com/arttor/helmify/pkg/metadata" @@ -137,6 +138,126 @@ func Test_deployment_Process(t *testing.T) { }) } +const ( + // strDeplNoAnnotations has no pod template annotations — tests that podAnnotations is + // still seeded in values and the values-driven block is present in the template. + strDeplNoAnnotations = `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + control-plane: controller-manager + name: my-operator-controller-manager + namespace: my-operator-system +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + labels: + control-plane: controller-manager + spec: + containers: + - name: manager + image: controller:latest +` + // strDeplWithAnnotations has static pod template annotations — tests that static + // annotations are preserved and the values-driven block is appended after them. + strDeplWithAnnotations = `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + control-plane: controller-manager + name: my-operator-controller-manager + namespace: my-operator-system +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + labels: + control-plane: controller-manager + annotations: + kubectl.kubernetes.io/default-container: manager + spec: + containers: + - name: manager + image: controller:latest +` +) + +func Test_deployment_podAnnotations(t *testing.T) { + var testInstance deployment + + t.Run("no static annotations - values seeded and template block present", func(t *testing.T) { + obj := internal.GenerateObj(strDeplNoAnnotations) + processed, tmpl, err := testInstance.Process(&metadata.Service{}, obj) + assert.NoError(t, err) + assert.True(t, processed) + + // podAnnotations should be seeded as empty map in values + // the deployment name "my-operator-controller-manager" trims to "controller-manager" + // which becomes "controllerManager" in lowerCamel + vals := tmpl.Values() + controllerManager, ok := vals["myOperatorControllerManager"].(map[string]interface{}) + assert.True(t, ok, "expected myOperatorControllerManager key in values") + podAnnotations, ok := controllerManager["podAnnotations"] + assert.True(t, ok, "expected podAnnotations key in values") + assert.Equal(t, map[string]interface{}{}, podAnnotations) + + // Template output must contain the values-driven annotations block + var buf bytes.Buffer + assert.NoError(t, tmpl.Write(&buf)) + output := buf.String() + assert.Contains(t, output, "{{- with .Values.myOperatorControllerManager.podAnnotations }}") + assert.Contains(t, output, "{{- toYaml . | nindent 8 }}") + assert.Contains(t, output, "annotations:") + }) + + t.Run("static annotations preserved and values block appended", func(t *testing.T) { + obj := internal.GenerateObj(strDeplWithAnnotations) + processed, tmpl, err := testInstance.Process(&metadata.Service{}, obj) + assert.NoError(t, err) + assert.True(t, processed) + + var buf bytes.Buffer + assert.NoError(t, tmpl.Write(&buf)) + output := buf.String() + + // Static annotation must be in the output + assert.Contains(t, output, "kubectl.kubernetes.io/default-container: manager") + // Values-driven block must also be present + assert.Contains(t, output, "{{- with .Values.myOperatorControllerManager.podAnnotations }}") + }) +} + +func Test_deployment_podLabels(t *testing.T) { + var testInstance deployment + + t.Run("podLabels seeded in values and template block present", func(t *testing.T) { + obj := internal.GenerateObj(strDeplNoAnnotations) + processed, tmpl, err := testInstance.Process(&metadata.Service{}, obj) + assert.NoError(t, err) + assert.True(t, processed) + + vals := tmpl.Values() + controllerManager, ok := vals["myOperatorControllerManager"].(map[string]interface{}) + assert.True(t, ok, "expected myOperatorControllerManager key in values") + podLabels, ok := controllerManager["podLabels"] + assert.True(t, ok, "expected podLabels key in values") + assert.Equal(t, map[string]interface{}{}, podLabels) + + var buf bytes.Buffer + assert.NoError(t, tmpl.Write(&buf)) + output := buf.String() + assert.Contains(t, output, "{{- if .Values.myOperatorControllerManager.podLabels }}") + assert.Contains(t, output, "{{- toYaml .Values.myOperatorControllerManager.podLabels | nindent 8 }}") + }) +} + var singleQuotesTest = []struct { input string expected string