diff --git a/examples/app/templates/batch-job.yaml b/examples/app/templates/batch-job.yaml index d14b8d3..781c3ff 100644 --- a/examples/app/templates/batch-job.yaml +++ b/examples/app/templates/batch-job.yaml @@ -8,6 +8,7 @@ spec: backoffLimit: {{ .Values.batchJob.backoffLimit }} template: spec: + affinity: {{- toYaml .Values.batchJob.affinity | nindent 8 }} containers: - command: - perl diff --git a/examples/app/templates/cron-job.yaml b/examples/app/templates/cron-job.yaml index 3025af6..2066f13 100644 --- a/examples/app/templates/cron-job.yaml +++ b/examples/app/templates/cron-job.yaml @@ -9,6 +9,7 @@ spec: spec: template: spec: + affinity: {{- toYaml .Values.cronJob.affinity | nindent 12 }} containers: - command: - /bin/sh diff --git a/examples/app/templates/daemonset.yaml b/examples/app/templates/daemonset.yaml index 06eedd4..e6c2cf9 100644 --- a/examples/app/templates/daemonset.yaml +++ b/examples/app/templates/daemonset.yaml @@ -16,6 +16,7 @@ spec: name: fluentd-elasticsearch {{- include "app.selectorLabels" . | nindent 8 }} spec: + affinity: {{- toYaml .Values.fluentdElasticsearch.affinity | nindent 8 }} containers: - env: - name: KUBERNETES_CLUSTER_DOMAIN diff --git a/examples/app/templates/deployment.yaml b/examples/app/templates/deployment.yaml index 3567b1e..e591c52 100644 --- a/examples/app/templates/deployment.yaml +++ b/examples/app/templates/deployment.yaml @@ -18,6 +18,7 @@ spec: app: myapp {{- include "app.selectorLabels" . | nindent 8 }} spec: + affinity: {{- toYaml .Values.myapp.affinity | nindent 8 }} containers: - args: {{- toYaml .Values.myapp.app.args | nindent 8 }} command: diff --git a/examples/app/templates/statefulset.yaml b/examples/app/templates/statefulset.yaml index 641cc37..da83dec 100644 --- a/examples/app/templates/statefulset.yaml +++ b/examples/app/templates/statefulset.yaml @@ -15,6 +15,7 @@ spec: labels: app: nginx spec: + affinity: {{- toYaml .Values.web.affinity | nindent 8 }} containers: - env: - name: KUBERNETES_CLUSTER_DOMAIN diff --git a/examples/app/values.yaml b/examples/app/values.yaml index 54f03b4..f36118b 100644 --- a/examples/app/values.yaml +++ b/examples/app/values.yaml @@ -1,4 +1,5 @@ batchJob: + affinity: {} backoffLimit: 4 nodeSelector: {} pi: @@ -8,6 +9,7 @@ batchJob: tolerations: [] topologySpreadConstraints: [] cronJob: + affinity: {} hello: image: repository: busybox @@ -18,6 +20,7 @@ cronJob: tolerations: [] topologySpreadConstraints: [] fluentdElasticsearch: + affinity: {} fluentdElasticsearch: image: repository: quay.io/fluentd_elasticsearch/fluentd @@ -64,6 +67,13 @@ mySecretVars: var1: "" var2: "" myapp: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists app: args: - --health-probe-bind-address=:8081 @@ -140,6 +150,7 @@ pvc: storageLimit: 5Gi storageRequest: 3Gi web: + affinity: {} nginx: image: repository: registry.k8s.io/nginx-slim diff --git a/pkg/processor/pod/pod.go b/pkg/processor/pod/pod.go index 08d6bd4..6c49115 100644 --- a/pkg/processor/pod/pod.go +++ b/pkg/processor/pod/pod.go @@ -99,6 +99,27 @@ func ProcessSpec(objName string, appMeta helmify.AppMetadata, spec corev1.PodSpe } } + // process affinity if presented: + err = unstructured.SetNestedField(specMap, fmt.Sprintf(`{{- toYaml .Values.%s.affinity | nindent %d }}`, objName, nindent), "affinity") + if err != nil { + return nil, nil, err + } + if spec.Affinity != nil { + affinityMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(spec.Affinity) + if err != nil { + return nil, nil, err + } + err = unstructured.SetNestedField(values, affinityMap, objName, "affinity") + if err != nil { + return nil, nil, err + } + } else { + err = unstructured.SetNestedField(values, map[string]interface{}{}, objName, "affinity") + 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 { diff --git a/pkg/processor/pod/pod_test.go b/pkg/processor/pod/pod_test.go index 5ac49c0..9ca7051 100644 --- a/pkg/processor/pod/pod_test.go +++ b/pkg/processor/pod/pod_test.go @@ -137,6 +137,35 @@ spec: runAsNonRoot: true runAsUser: 65532 +` + strDeploymentWithAffinity = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: localhost:6001/my_project:latest + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + ` ) @@ -171,6 +200,7 @@ func Test_pod_Process(t *testing.T) { "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", "serviceAccountName": `{{ include ".serviceAccountName" . }}`, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", }, specMap) assert.Equal(t, helmify.Values{ @@ -188,6 +218,7 @@ func Test_pod_Process(t *testing.T) { "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, + "affinity": map[string]interface{}{}, }, }, tmpl) }) @@ -221,6 +252,7 @@ func Test_pod_Process(t *testing.T) { "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", }, specMap) assert.Equal(t, helmify.Values{ @@ -234,6 +266,7 @@ func Test_pod_Process(t *testing.T) { "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, + "affinity": map[string]interface{}{}, }, }, tmpl) }) @@ -267,6 +300,7 @@ func Test_pod_Process(t *testing.T) { "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", }, specMap) assert.Equal(t, helmify.Values{ @@ -280,6 +314,7 @@ func Test_pod_Process(t *testing.T) { "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, + "affinity": map[string]interface{}{}, }, }, tmpl) }) @@ -313,6 +348,7 @@ func Test_pod_Process(t *testing.T) { "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", }, specMap) assert.Equal(t, helmify.Values{ @@ -326,6 +362,7 @@ func Test_pod_Process(t *testing.T) { "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, + "affinity": map[string]interface{}{}, }, }, tmpl) }) @@ -354,6 +391,7 @@ func Test_pod_Process(t *testing.T) { "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", }, specMap) assert.Equal(t, helmify.Values{ @@ -373,8 +411,65 @@ func Test_pod_Process(t *testing.T) { "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, + "affinity": map[string]interface{}{}, }, }, tmpl) }) + t.Run("deployment with affinity", func(t *testing.T) { + var deploy appsv1.Deployment + obj := internal.GenerateObj(strDeploymentWithAffinity) + err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy) + specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0) + assert.NoError(t, err) + assert.Equal(t, map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "env": []interface{}{ + map[string]interface{}{ + "name": "KUBERNETES_CLUSTER_DOMAIN", + "value": "{{ quote .Values.kubernetesClusterDomain }}", + }, + }, + "image": "{{ .Values.nginx.nginx.image.repository }}:{{ .Values.nginx.nginx.image.tag | default .Chart.AppVersion }}", + "name": "nginx", + "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 }}", + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", + }, specMap) + assert.Equal(t, helmify.Values{ + "nginx": map[string]interface{}{ + "affinity": map[string]interface{}{ + "nodeAffinity": map[string]interface{}{ + "requiredDuringSchedulingIgnoredDuringExecution": map[string]interface{}{ + "nodeSelectorTerms": []interface{}{ + map[string]interface{}{ + "matchExpressions": []interface{}{ + map[string]interface{}{ + "key": "node-role.kubernetes.io/control-plane", + "operator": "Exists", + }, + }, + }, + }, + }, + }, + }, + "nginx": map[string]interface{}{ + "image": map[string]interface{}{ + "repository": "localhost:6001/my_project", + "tag": "latest", + }, + }, + "nodeSelector": map[string]interface{}{}, + "tolerations": []interface{}{}, + "topologySpreadConstraints": []interface{}{}, + }, + }, tmpl) + }) } diff --git a/test_data/sample-app.yaml b/test_data/sample-app.yaml index e3e1319..8b1b18f 100644 --- a/test_data/sample-app.yaml +++ b/test_data/sample-app.yaml @@ -94,6 +94,13 @@ spec: nodeSelector: region: east type: user-node + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists terminationGracePeriodSeconds: 10 volumes: - configMap: @@ -166,7 +173,7 @@ metadata: spec: ipFamilyPolicy: SingleStack ipFamilies: - - IPv4 + - IPv4 ports: - name: https port: 8443