diff --git a/pkg/processor/pod/pod.go b/pkg/processor/pod/pod.go index 08d6bd4..2bb5ff2 100644 --- a/pkg/processor/pod/pod.go +++ b/pkg/processor/pod/pod.go @@ -151,6 +151,34 @@ 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 + } + if len(affinityMap) > 0 { + 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 + } + } + } else { + err = unstructured.SetNestedField(values, map[string]interface{}{}, objName, "affinity") + if err != nil { + return nil, nil, err + } + } + return specMap, values, nil } diff --git a/pkg/processor/pod/pod_test.go b/pkg/processor/pod/pod_test.go index 5ac49c0..83754d9 100644 --- a/pkg/processor/pod/pod_test.go +++ b/pkg/processor/pod/pod_test.go @@ -40,6 +40,54 @@ spec: - containerPort: 80 ` + strDeploymentWithPodSpecs = ` +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: nginx:1.14.2 + ports: + - containerPort: 80 + nodeSelector: + region: east + type: user-node + tolerations: + - key: "dedicated" + operator: "Equal" + value: "special-user" + effect: "NoSchedule" + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app: nginx + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux +` + strDeploymentWithTagAndDigest = ` apiVersion: apps/v1 kind: Deployment @@ -167,6 +215,7 @@ func Test_pod_Process(t *testing.T) { "resources": map[string]interface{}{}, }, }, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", "topologySpreadConstraints": "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", @@ -185,6 +234,7 @@ func Test_pod_Process(t *testing.T) { "--arg", }, }, + "affinity": map[string]interface{}{}, "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, @@ -217,6 +267,7 @@ func Test_pod_Process(t *testing.T) { "resources": map[string]interface{}{}, }, }, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", @@ -231,6 +282,7 @@ func Test_pod_Process(t *testing.T) { "tag": "1.14.2", }, }, + "affinity": map[string]interface{}{}, "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, @@ -263,6 +315,7 @@ func Test_pod_Process(t *testing.T) { "resources": map[string]interface{}{}, }, }, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", @@ -277,6 +330,7 @@ func Test_pod_Process(t *testing.T) { "tag": "1.14.2@sha256:cb5c1bddd1b5665e1867a7fa1b5fa843a47ee433bbb75d4293888b71def53229", }, }, + "affinity": map[string]interface{}{}, "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, @@ -309,6 +363,7 @@ func Test_pod_Process(t *testing.T) { "resources": map[string]interface{}{}, }, }, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", "serviceAccountName": `{{ include ".serviceAccountName" . }}`, "tolerations": "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", @@ -323,6 +378,7 @@ func Test_pod_Process(t *testing.T) { "tag": "latest", }, }, + "affinity": map[string]interface{}{}, "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, @@ -349,6 +405,7 @@ func Test_pod_Process(t *testing.T) { "resources": map[string]interface{}{}, }, }, + "affinity": "{{- toYaml .Values.nginx.affinity | nindent 8 }}", "securityContext": "{{- toYaml .Values.nginx.podSecurityContext | nindent 8 }}", "nodeSelector": "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", "serviceAccountName": `{{ include ".serviceAccountName" . }}`, @@ -370,6 +427,7 @@ func Test_pod_Process(t *testing.T) { "tag": "latest", }, }, + "affinity": map[string]interface{}{}, "nodeSelector": map[string]interface{}{}, "tolerations": []interface{}{}, "topologySpreadConstraints": []interface{}{}, @@ -377,4 +435,49 @@ func Test_pod_Process(t *testing.T) { }, tmpl) }) + t.Run("deployment with pod-level specs", func(t *testing.T) { + var deploy appsv1.Deployment + obj := internal.GenerateObj(strDeploymentWithPodSpecs) + err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deploy) + specMap, tmpl, err := ProcessSpec("nginx", &metadata.Service{}, deploy.Spec.Template.Spec, 0) + assert.NoError(t, err) + + // specMap should have all pod-level specs as templated values + assert.Equal(t, "{{- toYaml .Values.nginx.nodeSelector | nindent 8 }}", specMap["nodeSelector"]) + assert.Equal(t, "{{- toYaml .Values.nginx.tolerations | nindent 8 }}", specMap["tolerations"]) + assert.Equal(t, "{{- toYaml .Values.nginx.topologySpreadConstraints | nindent 8 }}", specMap["topologySpreadConstraints"]) + assert.Equal(t, "{{- toYaml .Values.nginx.affinity | nindent 8 }}", specMap["affinity"]) + + // values should contain the actual values from the manifest + nginxValues := tmpl["nginx"].(map[string]interface{}) + + // nodeSelector values + nodeSelector := nginxValues["nodeSelector"].(map[string]interface{}) + assert.Equal(t, "east", nodeSelector["region"]) + assert.Equal(t, "user-node", nodeSelector["type"]) + + // tolerations values + tolerations := nginxValues["tolerations"].([]interface{}) + assert.Len(t, tolerations, 1) + toleration := tolerations[0].(map[string]interface{}) + assert.Equal(t, "dedicated", toleration["key"]) + assert.Equal(t, "Equal", toleration["operator"]) + assert.Equal(t, "special-user", toleration["value"]) + assert.Equal(t, "NoSchedule", toleration["effect"]) + + // topologySpreadConstraints values + tsc := nginxValues["topologySpreadConstraints"].([]interface{}) + assert.Len(t, tsc, 1) + constraint := tsc[0].(map[string]interface{}) + assert.Equal(t, float64(1), constraint["maxSkew"]) + assert.Equal(t, "kubernetes.io/hostname", constraint["topologyKey"]) + assert.Equal(t, "DoNotSchedule", constraint["whenUnsatisfiable"]) + + // affinity values + affinity := nginxValues["affinity"].(map[string]interface{}) + assert.Contains(t, affinity, "nodeAffinity") + nodeAffinity := affinity["nodeAffinity"].(map[string]interface{}) + assert.Contains(t, nodeAffinity, "requiredDuringSchedulingIgnoredDuringExecution") + }) + }