Repository: arttor/helmify

Issue:
support serviceAccount helper
The serviceAccount Name helper is not used, we have a use case where we need to modify the serviceAccount name from the values.yaml.

Hints from issue comments:
@arttor: released in [v0.4.19](https://github.com/arttor/helmify/releases/tag/v0.4.19)

Solution patch:
diff --git a/examples/app/templates/_helpers.tpl b/examples/app/templates/_helpers.tpl
index 6e13b3a..4aabe0d 100644
--- a/examples/app/templates/_helpers.tpl
+++ b/examples/app/templates/_helpers.tpl
@@ -54,9 +54,12 @@ app.kubernetes.io/instance: {{ .Release.Name }}
 Create the name of the service account to use
 */}}
 {{- define "app.serviceAccountName" -}}
-{{- if .Values.serviceAccount.create }}
-{{- default (include "app.fullname" .) .Values.serviceAccount.name }}
+{{- $default := (include "app.fullname" .) }}
+{{- with .Values.serviceAccount }}
+{{- if .create }}
+{{- default $default .name }}
 {{- else }}
-{{- default "default" .Values.serviceAccount.name }}
+{{- default "default" .name }}
+{{- end }}
 {{- end }}
 {{- end }}
diff --git a/examples/app/templates/batch-job.yaml b/examples/app/templates/batch-job.yaml
index c5aeb49..d14b8d3 100644
--- a/examples/app/templates/batch-job.yaml
+++ b/examples/app/templates/batch-job.yaml
@@ -21,4 +21,9 @@ spec:
           | default .Chart.AppVersion }}
         name: pi
         resources: {}
+      nodeSelector: {{- toYaml .Values.batchJob.nodeSelector | nindent 8 }}
       restartPolicy: Never
+      serviceAccountName: {{ include "app.serviceAccountName" . }}
+      tolerations: {{- toYaml .Values.batchJob.tolerations | nindent 8 }}
+      topologySpreadConstraints: {{- toYaml .Values.batchJob.topologySpreadConstraints
+        | nindent 8 }}
diff --git a/examples/app/templates/cron-job.yaml b/examples/app/templates/cron-job.yaml
index bd0f073..3025af6 100644
--- a/examples/app/templates/cron-job.yaml
+++ b/examples/app/templates/cron-job.yaml
@@ -22,5 +22,10 @@ spec:
             imagePullPolicy: {{ .Values.cronJob.hello.imagePullPolicy }}
             name: hello
             resources: {}
+          nodeSelector: {{- toYaml .Values.cronJob.nodeSelector | nindent 12 }}
           restartPolicy: OnFailure
+          serviceAccountName: {{ include "app.serviceAccountName" . }}
+          tolerations: {{- toYaml .Values.cronJob.tolerations | nindent 12 }}
+          topologySpreadConstraints: {{- toYaml .Values.cronJob.topologySpreadConstraints
+            | nindent 12 }}
   schedule: {{ .Values.cronJob.schedule | quote }}
diff --git a/examples/app/templates/daemonset.yaml b/examples/app/templates/daemonset.yaml
index e26e64b..06eedd4 100644
--- a/examples/app/templates/daemonset.yaml
+++ b/examples/app/templates/daemonset.yaml
@@ -32,11 +32,12 @@ spec:
         - mountPath: /var/lib/docker/containers
           name: varlibdockercontainers
           readOnly: true
+      nodeSelector: {{- toYaml .Values.fluentdElasticsearch.nodeSelector | nindent 8 }}
+      serviceAccountName: {{ include "app.serviceAccountName" . }}
       terminationGracePeriodSeconds: 30
-      tolerations:
-      - effect: NoSchedule
-        key: node-role.kubernetes.io/master
-        operator: Exists
+      tolerations: {{- toYaml .Values.fluentdElasticsearch.tolerations | nindent 8 }}
+      topologySpreadConstraints: {{- toYaml .Values.fluentdElasticsearch.topologySpreadConstraints
+        | nindent 8 }}
       volumes:
       - hostPath:
           path: /var/log
diff --git a/examples/app/templates/deployment.yaml b/examples/app/templates/deployment.yaml
index 4d691eb..3567b1e 100644
--- a/examples/app/templates/deployment.yaml
+++ b/examples/app/templates/deployment.yaml
@@ -96,7 +96,11 @@ spec:
         resources: {}
       nodeSelector: {{- toYaml .Values.myapp.nodeSelector | nindent 8 }}
       securityContext: {{- toYaml .Values.myapp.podSecurityContext | nindent 8 }}
+      serviceAccountName: {{ include "app.serviceAccountName" . }}
       terminationGracePeriodSeconds: 10
+      tolerations: {{- toYaml .Values.myapp.tolerations | nindent 8 }}
+      topologySpreadConstraints: {{- toYaml .Values.myapp.topologySpreadConstraints | nindent
+        8 }}
       volumes:
       - configMap:
           name: {{ include "app.fullname" . }}-my-config
diff --git a/examples/app/templates/statefulset.yaml b/examples/app/templates/statefulset.yaml
index 418c0d2..641cc37 100644
--- a/examples/app/templates/statefulset.yaml
+++ b/examples/app/templates/statefulset.yaml
@@ -29,6 +29,11 @@ spec:
         volumeMounts:
         - mountPath: /usr/share/nginx/html
           name: www
+      nodeSelector: {{- toYaml .Values.web.nodeSelector | nindent 8 }}
+      serviceAccountName: {{ include "app.serviceAccountName" . }}
+      tolerations: {{- toYaml .Values.web.tolerations | nindent 8 }}
+      topologySpreadConstraints: {{- toYaml .Values.web.topologySpreadConstraints |
+        nindent 8 }}
   updateStrategy: {}
   volumeClaimTemplates:
   - metadata:
diff --git a/examples/app/values.yaml b/examples/app/values.yaml
index 2d23d62..0023b06 100644
--- a/examples/app/values.yaml
+++ b/examples/app/values.yaml
@@ -1,16 +1,22 @@
 batchJob:
   backoffLimit: 4
+  nodeSelector: {}
   pi:
     image:
       repository: perl
       tag: 5.34.0
+  tolerations: []
+  topologySpreadConstraints: []
 cronJob:
   hello:
     image:
       repository: busybox
       tag: "1.28"
     imagePullPolicy: IfNotPresent
+  nodeSelector: {}
   schedule: '* * * * *'
+  tolerations: []
+  topologySpreadConstraints: []
 fluentdElasticsearch:
   fluentdElasticsearch:
     image:
@@ -22,6 +28,12 @@ fluentdElasticsearch:
       requests:
         cpu: 100m
         memory: 200Mi
+  nodeSelector: {}
+  tolerations:
+  - effect: NoSchedule
+    key: node-role.kubernetes.io/master
+    operator: Exists
+  topologySpreadConstraints: []
 kubernetesClusterDomain: cluster.local
 myConfig:
   dummyconfigmapkey: dummyconfigmapvalue
@@ -89,6 +101,8 @@ myapp:
       tag: v0.8.0
   replicas: 3
   revisionHistoryLimit: 5
+  tolerations: []
+  topologySpreadConstraints: []
 myappLbService:
   loadBalancerSourceRanges:
   - 10.0.0.0/8
@@ -121,7 +135,10 @@ web:
     image:
       repository: registry.k8s.io/nginx-slim
       tag: "0.8"
+  nodeSelector: {}
   replicas: 2
+  tolerations: []
+  topologySpreadConstraints: []
   volumeClaims:
     www:
       requests:
diff --git a/examples/operator/templates/deployment.yaml b/examples/operator/templates/deployment.yaml
index 67400bb..5f2fc17 100644
--- a/examples/operator/templates/deployment.yaml
+++ b/examples/operator/templates/deployment.yaml
@@ -96,15 +96,11 @@ spec:
       nodeSelector: {{- toYaml .Values.controllerManager.nodeSelector | nindent 8 }}
       securityContext: {{- toYaml .Values.controllerManager.podSecurityContext | nindent
         8 }}
-      serviceAccountName: {{ include "operator.fullname" . }}-controller-manager
+      serviceAccountName: {{ include "operator.serviceAccountName" . }}
       terminationGracePeriodSeconds: 10
-      topologySpreadConstraints:
-      - matchLabelKeys:
-        - app
-        - pod-template-hash
-        maxSkew: 1
-        topologyKey: kubernetes.io/hostname
-        whenUnsatisfiable: DoNotSchedule
+      tolerations: {{- toYaml .Values.controllerManager.tolerations | nindent 8 }}
+      topologySpreadConstraints: {{- toYaml .Values.controllerManager.topologySpreadConstraints
+        | nindent 8 }}
       volumes:
       - configMap:
           name: {{ include "operator.fullname" . }}-manager-config
diff --git a/examples/operator/templates/leader-election-rbac.yaml b/examples/operator/templates/leader-election-rbac.yaml
index 2f1ed8f..c163656 100644
--- a/examples/operator/templates/leader-election-rbac.yaml
+++ b/examples/operator/templates/leader-election-rbac.yaml
@@ -49,5 +49,5 @@ roleRef:
   name: '{{ include "operator.fullname" . }}-leader-election-role'
 subjects:
 - kind: ServiceAccount
-  name: '{{ include "operator.fullname" . }}-controller-manager'
+  name: '{{ include "operator.serviceAccountName" . }}'
   namespace: '{{ .Release.Namespace }}'
diff --git a/examples/operator/templates/manager-rbac.yaml b/examples/operator/templates/manager-rbac.yaml
index 8a59b24..10aee77 100644
--- a/examples/operator/templates/manager-rbac.yaml
+++ b/examples/operator/templates/manager-rbac.yaml
@@ -98,5 +98,5 @@ roleRef:
   name: '{{ include "operator.fullname" . }}-manager-role'
 subjects:
 - kind: ServiceAccount
-  name: '{{ include "operator.fullname" . }}-controller-manager'
+  name: '{{ include "operator.serviceAccountName" . }}'
   namespace: '{{ .Release.Namespace }}'
diff --git a/examples/operator/templates/proxy-rbac.yaml b/examples/operator/templates/proxy-rbac.yaml
index 6bc9aa8..d9b76f0 100644
--- a/examples/operator/templates/proxy-rbac.yaml
+++ b/examples/operator/templates/proxy-rbac.yaml
@@ -30,5 +30,5 @@ roleRef:
   name: '{{ include "operator.fullname" . }}-proxy-role'
 subjects:
 - kind: ServiceAccount
-  name: '{{ include "operator.fullname" . }}-controller-manager'
+  name: '{{ include "operator.serviceAccountName" . }}'
   namespace: '{{ .Release.Namespace }}'
diff --git a/examples/operator/templates/serviceaccount.yaml b/examples/operator/templates/serviceaccount.yaml
index c06f9ea..02d22cc 100644
--- a/examples/operator/templates/serviceaccount.yaml
+++ b/examples/operator/templates/serviceaccount.yaml
@@ -1,8 +1,13 @@
+{{ if .Values.serviceAccount.create }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
-  name: {{ include "operator.fullname" . }}-controller-manager
+  name: {{ include "operator.serviceAccountName" . }}
   labels:
   {{- include "operator.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
   annotations:
-    {{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }}
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
+{{- end }}
diff --git a/examples/operator/values.yaml b/examples/operator/values.yaml
index a0c2c88..495b164 100644
--- a/examples/operator/values.yaml
+++ b/examples/operator/values.yaml
@@ -46,14 +46,19 @@ controllerManager:
   podSecurityContext:
     runAsNonRoot: true
   replicas: 1
-  serviceAccount:
-    annotations:
-      k8s.acme.org/some-meta-data: ACME Inc.
   strategy:
     rollingUpdate:
       maxSurge: 25%
       maxUnavailable: 25%
     type: RollingUpdate
+  tolerations: []
+  topologySpreadConstraints:
+  - matchLabelKeys:
+    - app
+    - pod-template-hash
+    maxSkew: 1
+    topologyKey: kubernetes.io/hostname
+    whenUnsatisfiable: DoNotSchedule
 kubernetesClusterDomain: cluster.local
 managerConfig:
   controllerManagerConfigYaml: |-
@@ -89,6 +94,12 @@ secretRegistryCredentials:
 secretVars:
   var1: ""
   var2: ""
+serviceAccount:
+  annotations:
+    k8s.acme.org/some-meta-data: ACME Inc.
+  automount: true
+  create: true
+  name: ""
 webhookService:
   ports:
   - port: 443
diff --git a/pkg/helm/init.go b/pkg/helm/init.go
index f7e5b43..2d2a261 100644
--- a/pkg/helm/init.go
+++ b/pkg/helm/init.go
@@ -91,10 +91,13 @@ app.kubernetes.io/instance: {{ .Release.Name }}
 Create the name of the service account to use
 */}}
 {{- define "<CHARTNAME>.serviceAccountName" -}}
-{{- if .Values.serviceAccount.create }}
-{{- default (include "<CHARTNAME>.fullname" .) .Values.serviceAccount.name }}
+{{- $default := (include "<CHARTNAME>.fullname" .) }}
+{{- with .Values.serviceAccount }}
+{{- if .create }}
+{{- default $default .name }}
 {{- else }}
-{{- default "default" .Values.serviceAccount.name }}
+{{- default "default" .name }}
+{{- end }}
 {{- end }}
 {{- end }}
 `
diff --git a/pkg/processor/daemonset/daemonset.go b/pkg/processor/daemonset/daemonset.go
index 851abe3..7caac53 100644
--- a/pkg/processor/daemonset/daemonset.go
+++ b/pkg/processor/daemonset/daemonset.go
@@ -2,11 +2,12 @@ package daemonset
 
 import (
 	"fmt"
-	"github.com/arttor/helmify/pkg/processor/pod"
 	"io"
 	"strings"
 	"text/template"
 
+	"github.com/arttor/helmify/pkg/processor/pod"
+
 	"github.com/arttor/helmify/pkg/helmify"
 	"github.com/arttor/helmify/pkg/processor"
 	yamlformat "github.com/arttor/helmify/pkg/yaml"
@@ -98,7 +99,7 @@ func (d daemonset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstru
 	}
 
 	nameCamel := strcase.ToLowerCamel(name)
-	specMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, dae.Spec.Template.Spec)
+	specMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, dae.Spec.Template.Spec, 0)
 	if err != nil {
 		return true, nil, err
 	}
diff --git a/pkg/processor/deployment/deployment.go b/pkg/processor/deployment/deployment.go
index b41cc23..ad7c923 100644
--- a/pkg/processor/deployment/deployment.go
+++ b/pkg/processor/deployment/deployment.go
@@ -125,7 +125,7 @@ func (d deployment) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstr
 	}
 
 	nameCamel := strcase.ToLowerCamel(name)
-	specMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, depl.Spec.Template.Spec)
+	specMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, depl.Spec.Template.Spec, 0)
 	if err != nil {
 		return true, nil, err
 	}
diff --git a/pkg/processor/job/cron.go b/pkg/processor/job/cron.go
index 1baf186..44063ea 100644
--- a/pkg/processor/job/cron.go
+++ b/pkg/processor/job/cron.go
@@ -2,18 +2,19 @@ package job
 
 import (
 	"fmt"
+	"io"
+	"strings"
+	"text/template"
+
 	"github.com/arttor/helmify/pkg/helmify"
 	"github.com/arttor/helmify/pkg/processor"
 	"github.com/arttor/helmify/pkg/processor/pod"
 	yamlformat "github.com/arttor/helmify/pkg/yaml"
 	"github.com/iancoleman/strcase"
-	"io"
 	batchv1 "k8s.io/api/batch/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"strings"
-	"text/template"
 )
 
 var cronTempl, _ = template.New("cron").Parse(
@@ -105,7 +106,7 @@ func (p cron) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructure
 	}
 
 	// process job pod template:
-	podSpecMap, podValues, err := pod.ProcessSpec(nameCamelCase, appMeta, jobObj.Spec.JobTemplate.Spec.Template.Spec)
+	podSpecMap, podValues, err := pod.ProcessSpec(nameCamelCase, appMeta, jobObj.Spec.JobTemplate.Spec.Template.Spec, 4)
 	if err != nil {
 		return true, nil, err
 	}
diff --git a/pkg/processor/job/job.go b/pkg/processor/job/job.go
index 5f1ec53..f879a73 100644
--- a/pkg/processor/job/job.go
+++ b/pkg/processor/job/job.go
@@ -2,18 +2,19 @@ package job
 
 import (
 	"fmt"
+	"io"
+	"strings"
+	"text/template"
+
 	"github.com/arttor/helmify/pkg/helmify"
 	"github.com/arttor/helmify/pkg/processor"
 	"github.com/arttor/helmify/pkg/processor/pod"
 	yamlformat "github.com/arttor/helmify/pkg/yaml"
 	"github.com/iancoleman/strcase"
-	"io"
 	batchv1 "k8s.io/api/batch/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"strings"
-	"text/template"
 )
 
 var jobTempl, _ = template.New("job").Parse(
@@ -104,7 +105,7 @@ func (p job) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructured
 		}
 	}
 	// process job pod template:
-	podSpecMap, podValues, err := pod.ProcessSpec(nameCamelCase, appMeta, jobObj.Spec.Template.Spec)
+	podSpecMap, podValues, err := pod.ProcessSpec(nameCamelCase, appMeta, jobObj.Spec.Template.Spec, 0)
 	if err != nil {
 		return true, nil, err
 	}
diff --git a/pkg/processor/pod/pod.go b/pkg/processor/pod/pod.go
index 88adb82..08d6bd4 100644
--- a/pkg/processor/pod/pod.go
+++ b/pkg/processor/pod/pod.go
@@ -1,6 +1,7 @@
 package pod
 
 import (
+	"encoding/json"
 	"fmt"
 	"strings"
 
@@ -15,8 +16,11 @@ import (
 
 const imagePullPolicyTemplate = "{{ .Values.%[1]s.%[2]s.imagePullPolicy }}"
 const envValue = "{{ quote .Values.%[1]s.%[2]s.%[3]s.%[4]s }}"
+const baseIndent = 8
+
+func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpec, addIndent int) (map[string]interface{}, helmify.Values, error) {
+	nindent := baseIndent + addIndent
 
-func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpec) (map[string]interface{}, helmify.Values, error) {
 	values, err := processPodSpec(objName, appMeta, &spec)
 	if err != nil {
 		return nil, nil, err
@@ -39,12 +43,12 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe
 		return nil, nil, fmt.Errorf("%w: unable to convert podSpec to map", err)
 	}
 
-	specMap, values, err = processNestedContainers(specMap, objName, values, "containers")
+	specMap, values, err = processNestedContainers(specMap, objName, values, "containers", nindent)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	specMap, values, err = processNestedContainers(specMap, objName, values, "initContainers")
+	specMap, values, err = processNestedContainers(specMap, objName, values, "initContainers", nindent)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -56,7 +60,7 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe
 		}
 	}
 
-	err = securityContext.ProcessContainerSecurityContext(objName, specMap, &values)
+	err = securityContext.ProcessContainerSecurityContext(objName, specMap, &values, nindent)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -66,7 +70,7 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe
 			return nil, nil, err
 		}
 		if len(securityContextMap) > 0 {
-			err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%[1]s.podSecurityContext | nindent 8 }}`, objName), "securityContext")
+			err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%[1]s.podSecurityContext | nindent %d }}`, objName, nindent), "securityContext")
 			if err != nil {
 				return nil, nil, err
 			}
@@ -79,12 +83,69 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe
 	}
 
 	// process nodeSelector if presented:
+	err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.nodeSelector | nindent %d }}`, objName, nindent), "nodeSelector")
+	if err != nil {
+		return nil, nil, err
+	}
 	if spec.NodeSelector != nil {
-		err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.nodeSelector | nindent 8 }}`, objName), "nodeSelector")
+		err = unstructured.SetNestedStringMap(values, spec.NodeSelector, objName, "nodeSelector")
 		if err != nil {
 			return nil, nil, err
 		}
-		err = unstructured.SetNestedStringMap(values, spec.NodeSelector, objName, "nodeSelector")
+	} else {
+		err = unstructured.SetNestedField(values, map[string]interface{}{}, objName, "nodeSelector")
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	// process tolerations if presented:
+	err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.tolerations | nindent %d }}`, objName, nindent), "tolerations")
+	if err != nil {
+		return nil, nil, err
+	}
+	if spec.Tolerations != nil {
+		tolerations := make([]any, len(spec.Tolerations))
+		inrec, err := json.Marshal(spec.Tolerations)
+		if err != nil {
+			return nil, nil, err
+		}
+		err = json.Unmarshal(inrec, &tolerations)
+		if err != nil {
+			return nil, nil, err
+		}
+		err = unstructured.SetNestedSlice(values, tolerations, objName, "tolerations")
+		if err != nil {
+			return nil, nil, err
+		}
+	} else {
+		err = unstructured.SetNestedSlice(values, []any{}, objName, "tolerations")
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	// process topologySpreadConstraints if presented:
+	err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.topologySpreadConstraints | nindent %d }}`, objName, nindent), "topologySpreadConstraints")
+	if err != nil {
+		return nil, nil, err
+	}
+	if spec.TopologySpreadConstraints != nil {
+		topologySpreadConstraints := make([]any, len(spec.TopologySpreadConstraints))
+		inrec, err := json.Marshal(spec.TopologySpreadConstraints)
+		if err != nil {
+			return nil, nil, err
+		}
+		err = json.Unmarshal(inrec, &topologySpreadConstraints)
+		if err != nil {
+			return nil, nil, err
+		}
+		err = unstructured.SetNestedSlice(values, topologySpreadConstraints, objName, "topologySpreadConstraints")
+		if err != nil {
+			return nil, nil, err
+		}
+	} else {
+		err = unstructured.SetNestedSlice(values, []any{}, objName, "topologySpreadConstraints")
 		if err != nil {
 			return nil, nil, err
 		}
@@ -93,14 +154,14 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe
 	return specMap, values, nil
 }
 
-func processNestedContainers(specMap map[string]interface{}, objName string, values map[string]interface{}, containerKey string) (map[string]interface{}, map[string]interface{}, error) {
+func processNestedContainers(specMap map[string]interface{}, objName string, values map[string]interface{}, containerKey string, nindent int) (map[string]interface{}, map[string]interface{}, error) {
 	containers, _, err := unstructured.NestedSlice(specMap, containerKey)
 	if err != nil {
 		return nil, nil, err
 	}
 
 	if len(containers) > 0 {
-		containers, values, err = processContainers(objName, values, containerKey, containers)
+		containers, values, err = processContainers(objName, values, containerKey, containers, nindent)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -114,7 +175,7 @@ func processNestedContainers(specMap map[string]interface{}, objName string, val
 	return specMap, values, nil
 }
 
-func processContainers(objName string, values helmify.Values, containerType string, containers []interface{}) ([]interface{}, helmify.Values, error) {
+func processContainers(objName string, values helmify.Values, containerType string, containers []interface{}, nindent int) ([]interface{}, helmify.Values, error) {
 	for i := range containers {
 		containerName := strcase.ToLowerCamel((containers[i].(map[string]interface{})["name"]).(string))
 		res, exists, err := unstructured.NestedMap(values, objName, containerName, "resources")
@@ -122,7 +183,7 @@ func processContainers(objName string, values helmify.Values, containerType stri
 			return nil, nil, err
 		}
 		if exists && len(res) > 0 {
-			err = unstructured.SetNestedField(containers[i].(map[string]interface{}), fmt.Sprintf(`{{- toYaml .Values.%s.%s.resources | nindent 10 }}`, objName, containerName), "resources")
+			err = unstructured.SetNestedField(containers[i].(map[string]interface{}), fmt.Sprintf(`{{- toYaml .Values.%s.%s.resources | nindent %d }}`, objName, containerName, nindent+2), "resources")
 			if err != nil {
 				return nil, nil, err
 			}
@@ -133,7 +194,7 @@ func processContainers(objName string, values helmify.Values, containerType stri
 			return nil, nil, err
 		}
 		if exists && len(args) > 0 {
-			err = unstructured.SetNestedField(containers[i].(map[string]interface{}), fmt.Sprintf(`{{- toYaml .Values.%[1]s.%[2]s.args | nindent 8 }}`, objName, containerName), "args")
+			err = unstructured.SetNestedField(containers[i].(map[string]interface{}), fmt.Sprintf(`{{- toYaml .Values.%[1]s.%[2]s.args | nindent %d }}`, objName, containerName, nindent), "args")
 			if err != nil {
 				return nil, nil, err
 			}
@@ -173,7 +234,7 @@ func processPodSpec(name string, appMeta helmify.AppMetadata, pod *corev1.PodSpe
 			v.Secret.SecretName = appMeta.TemplatedName(v.Secret.SecretName)
 		}
 	}
-	pod.ServiceAccountName = appMeta.TemplatedName(pod.ServiceAccountName)
+	pod.ServiceAccountName = fmt.Sprintf(`{{ include "%s.serviceAccountName" . }}`, appMeta.ChartName())
 
 	for i, s := range pod.ImagePullSecrets {
 		pod.ImagePullSecrets[i].Name = appMeta.TemplatedName(s.Name)
diff --git a/pkg/processor/rbac/clusterrolebinding.go b/pkg/processor/rbac/clusterrolebinding.go
index 5b97b9f..5356f43 100644
--- a/pkg/processor/rbac/clusterrolebinding.go
+++ b/pkg/processor/rbac/clusterrolebinding.go
@@ -60,7 +60,7 @@ func (r clusterRoleBinding) Process(appMeta helmify.AppMetadata, obj *unstructur
 
 	for i, s := range rb.Subjects {
 		s.Namespace = "{{ .Release.Namespace }}"
-		s.Name = appMeta.TemplatedName(s.Name)
+		s.Name = fmt.Sprintf("{{ include \"%s.serviceAccountName\" . }}", appMeta.ChartName())
 		rb.Subjects[i] = s
 	}
 	subjects, err := yamlformat.Marshal(map[string]interface{}{"subjects": &rb.Subjects}, 0)
diff --git a/pkg/processor/rbac/rolebinding.go b/pkg/processor/rbac/rolebinding.go
index 41a6d82..4287294 100644
--- a/pkg/processor/rbac/rolebinding.go
+++ b/pkg/processor/rbac/rolebinding.go
@@ -58,7 +58,7 @@ func (r roleBinding) Process(appMeta helmify.AppMetadata, obj *unstructured.Unst
 
 	for i, s := range rb.Subjects {
 		s.Namespace = "{{ .Release.Namespace }}"
-		s.Name = appMeta.TemplatedName(s.Name)
+		s.Name = fmt.Sprintf("{{ include \"%s.serviceAccountName\" . }}", appMeta.ChartName())
 		rb.Subjects[i] = s
 	}
 	subjects, err := yamlformat.Marshal(map[string]interface{}{"subjects": &rb.Subjects}, 0)
diff --git a/pkg/processor/rbac/serviceaccount.go b/pkg/processor/rbac/serviceaccount.go
index bfb4e5e..2009f4a 100644
--- a/pkg/processor/rbac/serviceaccount.go
+++ b/pkg/processor/rbac/serviceaccount.go
@@ -1,13 +1,30 @@
 package rbac
 
 import (
-	"github.com/arttor/helmify/pkg/helmify"
-	"github.com/arttor/helmify/pkg/processor"
+	"fmt"
 	"io"
+
+	"github.com/arttor/helmify/pkg/helmify"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
+const (
+	saTempl = `{{ if .Values.serviceAccount.create }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "%[1]s.serviceAccountName" . }}
+  labels:
+  {{- include "%[1]s.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
+{{- end }}`
+)
+
 var serviceAccountGVC = schema.GroupVersionKind{
 	Group:   "",
 	Version: "v1",
@@ -27,10 +44,19 @@ func (sa serviceAccount) Process(appMeta helmify.AppMetadata, obj *unstructured.
 		return false, nil, nil
 	}
 	values := helmify.Values{}
-	meta, err := processor.ProcessObjMeta(appMeta, obj, processor.WithAnnotations(values))
+	_, _ = values.Add(true, "serviceAccount", "create")
+	_, _ = values.Add("", "serviceAccount", "name")
+	_, _ = values.Add(true, "serviceAccount", "automount")
+	valuesAnnotations := make(map[string]interface{})
+	for k, v := range obj.GetAnnotations() {
+		valuesAnnotations[k] = v
+	}
+	err := unstructured.SetNestedField(values, valuesAnnotations, "serviceAccount", "annotations")
 	if err != nil {
 		return true, nil, err
 	}
+	tmpl := saTempl
+	meta := fmt.Sprintf(tmpl, appMeta.ChartName())
 
 	return true, &saResult{
 		data:   []byte(meta),
diff --git a/pkg/processor/security-context/container_security_context.go b/pkg/processor/security-context/container_security_context.go
index 08420e8..7630360 100644
--- a/pkg/processor/security-context/container_security_context.go
+++ b/pkg/processor/security-context/container_security_context.go
@@ -11,17 +11,17 @@ import (
 const (
 	sc           = "securityContext"
 	cscValueName = "containerSecurityContext"
-	helmTemplate = "{{- toYaml .Values.%[1]s.%[2]s.containerSecurityContext | nindent 10 }}"
+	helmTemplate = "{{- toYaml .Values.%[1]s.%[2]s.containerSecurityContext | nindent %[3]d }}"
 )
 
 // ProcessContainerSecurityContext adds 'securityContext' to the podSpec in specMap, if it doesn't have one already defined.
-func ProcessContainerSecurityContext(nameCamel string, specMap map[string]interface{}, values *helmify.Values) error {
-	err := processSecurityContext(nameCamel, "containers", specMap, values)
+func ProcessContainerSecurityContext(nameCamel string, specMap map[string]interface{}, values *helmify.Values, nindent int) error {
+	err := processSecurityContext(nameCamel, "containers", specMap, values, nindent)
 	if err != nil {
 		return err
 	}
 
-	err = processSecurityContext(nameCamel, "initContainers", specMap, values)
+	err = processSecurityContext(nameCamel, "initContainers", specMap, values, nindent)
 	if err != nil {
 		return err
 	}
@@ -29,13 +29,13 @@ func ProcessContainerSecurityContext(nameCamel string, specMap map[string]interf
 	return nil
 }
 
-func processSecurityContext(nameCamel string, containerType string, specMap map[string]interface{}, values *helmify.Values) error {
+func processSecurityContext(nameCamel string, containerType string, specMap map[string]interface{}, values *helmify.Values, nindent int) error {
 	if containers, defined := specMap[containerType]; defined {
 		for _, container := range containers.([]interface{}) {
 			castedContainer := container.(map[string]interface{})
 			containerName := strcase.ToLowerCamel(castedContainer["name"].(string))
 			if _, defined2 := castedContainer["securityContext"]; defined2 {
-				err := setSecContextValue(nameCamel, containerName, castedContainer, values)
+				err := setSecContextValue(nameCamel, containerName, castedContainer, values, nindent)
 				if err != nil {
 					return err
 				}
@@ -49,14 +49,14 @@ func processSecurityContext(nameCamel string, containerType string, specMap map[
 	return nil
 }
 
-func setSecContextValue(resourceName string, containerName string, castedContainer map[string]interface{}, values *helmify.Values) error {
+func setSecContextValue(resourceName string, containerName string, castedContainer map[string]interface{}, values *helmify.Values, nindent int) error {
 	if castedContainer["securityContext"] != nil {
 		err := unstructured.SetNestedField(*values, castedContainer["securityContext"], resourceName, containerName, cscValueName)
 		if err != nil {
 			return err
 		}
 
-		valueString := fmt.Sprintf(helmTemplate, resourceName, containerName)
+		valueString := fmt.Sprintf(helmTemplate, resourceName, containerName, nindent+2)
 
 		err = unstructured.SetNestedField(castedContainer, valueString, sc)
 		if err != nil {
diff --git a/pkg/processor/statefulset/statefulset.go b/pkg/processor/statefulset/statefulset.go
index 1091151..7306da2 100644
--- a/pkg/processor/statefulset/statefulset.go
+++ b/pkg/processor/statefulset/statefulset.go
@@ -2,11 +2,12 @@ package statefulset
 
 import (
 	"fmt"
-	"github.com/arttor/helmify/pkg/processor/pod"
 	"io"
 	"strings"
 	"text/template"
 
+	"github.com/arttor/helmify/pkg/processor/pod"
+
 	"github.com/arttor/helmify/pkg/helmify"
 	"github.com/arttor/helmify/pkg/processor"
 	yamlformat "github.com/arttor/helmify/pkg/yaml"
@@ -108,7 +109,7 @@ func (d statefulset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unst
 	}
 
 	// process pod spec:
-	podSpecMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, ssSpec.Template.Spec)
+	podSpecMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, ssSpec.Template.Spec, 0)
 	if err != nil {
 		return true, nil, err
 	}
diff --git a/test_data/sample-app.yaml b/test_data/sample-app.yaml
index d2399c8..4e75493 100644
--- a/test_data/sample-app.yaml
+++ b/test_data/sample-app.yaml
@@ -154,7 +154,7 @@ spec:
     app: myapp
   type: LoadBalancer
   loadBalancerSourceRanges:
-  - 10.0.0.0/8
+    - 10.0.0.0/8
 ---
 apiVersion: networking.k8s.io/v1
 kind: Ingress
@@ -233,7 +233,7 @@ data:
       name: default-oneperhost-pod-template
     spec:
       templates:
-        podTemplates: 
+        podTemplates:
           - name: default-oneperhost-pod-template
             distribution: "OnePerHost"
 ---
@@ -254,34 +254,34 @@ spec:
         name: fluentd-elasticsearch
     spec:
       tolerations:
-      # this toleration is to have the daemonset runnable on master nodes
-      # remove it if your masters can't run pods
-      - key: node-role.kubernetes.io/master
-        operator: Exists
-        effect: NoSchedule
+        # this toleration is to have the daemonset runnable on master nodes
+        # remove it if your masters can't run pods
+        - key: node-role.kubernetes.io/master
+          operator: Exists
+          effect: NoSchedule
       containers:
-      - name: fluentd-elasticsearch
-        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
-        resources:
-          limits:
-            memory: 200Mi
-          requests:
-            cpu: 100m
-            memory: 200Mi
-        volumeMounts:
-        - name: varlog
-          mountPath: /var/log
-        - name: varlibdockercontainers
-          mountPath: /var/lib/docker/containers
-          readOnly: true
+        - name: fluentd-elasticsearch
+          image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
+          resources:
+            limits:
+              memory: 200Mi
+            requests:
+              cpu: 100m
+              memory: 200Mi
+          volumeMounts:
+            - name: varlog
+              mountPath: /var/log
+            - name: varlibdockercontainers
+              mountPath: /var/lib/docker/containers
+              readOnly: true
       terminationGracePeriodSeconds: 30
       volumes:
-      - name: varlog
-        hostPath:
-          path: /var/log
-      - name: varlibdockercontainers
-        hostPath:
-          path: /var/lib/docker/containers
+        - name: varlog
+          hostPath:
+            path: /var/log
+        - name: varlibdockercontainers
+          hostPath:
+            path: /var/lib/docker/containers
 ---
 apiVersion: batch/v1
 kind: Job
@@ -293,7 +293,7 @@ spec:
       containers:
         - name: pi
           image: perl:5.34.0
-          command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
+          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
       restartPolicy: Never
   backoffLimit: 4
 ---
@@ -359,7 +359,7 @@ spec:
     - metadata:
         name: www
       spec:
-        accessModes: [ "ReadWriteOnce" ]
+        accessModes: ["ReadWriteOnce"]
         resources:
           requests:
             storage: 1Gi



Test patch:
diff --git a/pkg/processor/pod/pod_test.go b/pkg/processor/pod/pod_test.go
index 5a2fd72..5ac49c0 100644
--- a/pkg/processor/pod/pod_test.go
+++ b/pkg/processor/pod/pod_test.go
@@ -145,7 +145,7 @@ func Test_pod_Process(t *testing.T) {
 		var deploy appsv1.Deployment
 		obj := internal.GenerateObj(strDeployment)
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
-		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec)
+		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0)
 		assert.NoError(t, err)
 
 		assert.Equal(t, map[string]interface{}{
@@ -167,6 +167,10 @@ func Test_pod_Process(t *testing.T) {
 					"resources": map[string]interface{}{},
 				},
 			},
+			"tolerations":               "{{- toYaml .Values.nginx.tolerations | nindent 8 }}",
+			"topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}",
+			"nodeSelector":              "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}",
+			"serviceAccountName":        `{{ include ".serviceAccountName" . }}`,
 		}, specMap)
 
 		assert.Equal(t, helmify.Values{
@@ -181,6 +185,9 @@ func Test_pod_Process(t *testing.T) {
 						"--arg",
 					},
 				},
+				"nodeSelector":              map[string]interface{}{},
+				"tolerations":               []interface{}{},
+				"topologySpreadConstraints": []interface{}{},
 			},
 		}, tmpl)
 	})
@@ -189,7 +196,7 @@ func Test_pod_Process(t *testing.T) {
 		var deploy appsv1.Deployment
 		obj := internal.GenerateObj(strDeploymentWithNoArgs)
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
-		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec)
+		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0)
 		assert.NoError(t, err)
 
 		assert.Equal(t, map[string]interface{}{
@@ -210,6 +217,10 @@ func Test_pod_Process(t *testing.T) {
 					"resources": map[string]interface{}{},
 				},
 			},
+			"nodeSelector":              "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}",
+			"serviceAccountName":        `{{ include ".serviceAccountName" . }}`,
+			"tolerations":               "{{- toYaml .Values.nginx.tolerations | nindent 8 }}",
+			"topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}",
 		}, specMap)
 
 		assert.Equal(t, helmify.Values{
@@ -220,6 +231,9 @@ func Test_pod_Process(t *testing.T) {
 						"tag":        "1.14.2",
 					},
 				},
+				"nodeSelector":              map[string]interface{}{},
+				"tolerations":               []interface{}{},
+				"topologySpreadConstraints": []interface{}{},
 			},
 		}, tmpl)
 	})
@@ -228,7 +242,7 @@ func Test_pod_Process(t *testing.T) {
 		var deploy appsv1.Deployment
 		obj := internal.GenerateObj(strDeploymentWithTagAndDigest)
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
-		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec)
+		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0)
 		assert.NoError(t, err)
 
 		assert.Equal(t, map[string]interface{}{
@@ -249,6 +263,10 @@ func Test_pod_Process(t *testing.T) {
 					"resources": map[string]interface{}{},
 				},
 			},
+			"nodeSelector":              "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}",
+			"serviceAccountName":        `{{ include ".serviceAccountName" . }}`,
+			"tolerations":               "{{- toYaml .Values.nginx.tolerations | nindent 8 }}",
+			"topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}",
 		}, specMap)
 
 		assert.Equal(t, helmify.Values{
@@ -259,6 +277,9 @@ func Test_pod_Process(t *testing.T) {
 						"tag":        "1.14.2@sha256:cb5c1bddd1b5665e1867a7fa1b5fa843a47ee433bbb75d4293888b71def53229",
 					},
 				},
+				"nodeSelector":              map[string]interface{}{},
+				"tolerations":               []interface{}{},
+				"topologySpreadConstraints": []interface{}{},
 			},
 		}, tmpl)
 	})
@@ -267,7 +288,7 @@ func Test_pod_Process(t *testing.T) {
 		var deploy appsv1.Deployment
 		obj := internal.GenerateObj(strDeploymentWithPort)
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
-		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec)
+		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0)
 		assert.NoError(t, err)
 
 		assert.Equal(t, map[string]interface{}{
@@ -288,6 +309,10 @@ func Test_pod_Process(t *testing.T) {
 					"resources": map[string]interface{}{},
 				},
 			},
+			"nodeSelector":              "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}",
+			"serviceAccountName":        `{{ include ".serviceAccountName" . }}`,
+			"tolerations":               "{{- toYaml .Values.nginx.tolerations | nindent 8 }}",
+			"topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}",
 		}, specMap)
 
 		assert.Equal(t, helmify.Values{
@@ -298,6 +323,9 @@ func Test_pod_Process(t *testing.T) {
 						"tag":        "latest",
 					},
 				},
+				"nodeSelector":              map[string]interface{}{},
+				"tolerations":               []interface{}{},
+				"topologySpreadConstraints": []interface{}{},
 			},
 		}, tmpl)
 	})
@@ -305,7 +333,7 @@ func Test_pod_Process(t *testing.T) {
 		var deploy appsv1.Deployment
 		obj := internal.GenerateObj(strDeploymentWithPodSecurityContext)
 		err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy)
-		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec)
+		specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0)
 		assert.NoError(t, err)
 		assert.Equal(t, map[string]interface{}{
 			"containers": []interface{}{
@@ -321,7 +349,11 @@ func Test_pod_Process(t *testing.T) {
 					"resources": map[string]interface{}{},
 				},
 			},
-			"securityContext": "{{- toYaml .Values.nginx.podSecurityContext | nindent 8 }}",
+			"securityContext":           "{{- toYaml .Values.nginx.podSecurityContext | nindent 8 }}",
+			"nodeSelector":              "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}",
+			"serviceAccountName":        `{{ include ".serviceAccountName" . }}`,
+			"tolerations":               "{{- toYaml .Values.nginx.tolerations | nindent 8 }}",
+			"topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}",
 		}, specMap)
 
 		assert.Equal(t, helmify.Values{
@@ -338,6 +370,9 @@ func Test_pod_Process(t *testing.T) {
 						"tag":        "latest",
 					},
 				},
+				"nodeSelector":              map[string]interface{}{},
+				"tolerations":               []interface{}{},
+				"topologySpreadConstraints": []interface{}{},
 			},
 		}, tmpl)
 	})
diff --git a/pkg/processor/security-context/container_security_context_test.go b/pkg/processor/security-context/container_security_context_test.go
index 8f072f3..db237a9 100644
--- a/pkg/processor/security-context/container_security_context_test.go
+++ b/pkg/processor/security-context/container_security_context_test.go
@@ -93,7 +93,7 @@ func TestProcessContainerSecurityContext(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ProcessContainerSecurityContext(tt.args.nameCamel, tt.args.specMap, tt.args.values)
+			ProcessContainerSecurityContext(tt.args.nameCamel, tt.args.specMap, tt.args.values, 8)
 			assert.Equal(t, tt.want, tt.args.values)
 		})
 	}
@@ -140,7 +140,7 @@ func Test_setSecContextValue(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			setSecContextValue(tt.args.resourceName, tt.args.containerName, tt.args.castedContainer, tt.args.values)
+			setSecContextValue(tt.args.resourceName, tt.args.containerName, tt.args.castedContainer, tt.args.values, 8)
 			assert.Equal(t, tt.want, tt.args.values)
 		})
 	}