Repository: zegl/kube-score (go)

Issue:
podAntiAffinity throws error when topologyKey for zones are used
**Which version of kube-score are you using?**

kube-score version: 1.12.0, commit: 82810c42bb501443116bfb67dde81bc2f68fa318, built: 2021-08-28T20:47:35Z

Wrote a deployment with 

```yaml
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app.kubernetes.io/name
                  operator: In
                  values:
                  - secret-app
              topologyKey: topology.kubernetes.io/zone
            weight: 60
 ``` 

Got told:

```sh
apps/v1/Deployment secret-app in secret-app                              🤔                                             
    [WARNING] Deployment has host PodAntiAffinity                                                                            
        · Deployment does not have a host podAntiAffinity set                                                                
            It's recommended to set a podAntiAffinity that stops multiple pods                                               
            from a deployment from being scheduled on the same node. This                                                    
            increases availability in case the node becomes unavailable.                                                     
``` 


Though if I change the topologyKey to hostname it works..

```yaml
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app.kubernetes.io/name
                  operator: In
                  values:
                  - secret-app
              topologyKey: topology.kubernetes.io/hostname
            weight: 60
 ``` 

Commands used to invoke kubescore


```
kustomize build . | kube-score score --ignore-test container-image-pull-policy --ignore-test pod-networkpolicy --ignore-tes
t deployment-has-poddisruptionbudget -                                                                                       
```

Pull request:
score/apps: support more topology keys
<p>score/apps: support more topology keys</p><p>Adds support for additional “approved” topology keys in the hasPodAntiAffinity score</p>

Fixes #390

```
RELNOTE: Add support for more topology kets in the hasPodAntiAffinity test
```

---

This PR was created from Gustav Westling's (zegl) [workspace](https://getsturdy.com/kube-score-QEduUvS/ec482ab3-1b84-47fc-8b77-3974d49c7dbb) on [Sturdy](https://getsturdy.com/).

Join your team, and code and collaborate on Sturdy, [join now!](https://getsturdy.com/get-started/github)

Update this PR by making changes through Sturdy.


Patch:
diff --git a/score/apps/apps.go b/score/apps/apps.go
index 4b907bf..1b7659b 100644
--- a/score/apps/apps.go
+++ b/score/apps/apps.go
@@ -117,11 +117,21 @@ func statefulsetHasAntiAffinity(statefulset appsv1.StatefulSet) (score scorecard
 	return
 }
 
-func hasPodAntiAffinity(selfLables internal.MapLables, affinity *corev1.Affinity) bool {
+func hasPodAntiAffinity(selfLabels internal.MapLables, affinity *corev1.Affinity) bool {
+	approvedTopologyKeys := map[string]struct{}{
+		"kubernetes.io/hostname":        {},
+		"topology.kubernetes.io/region": {},
+		"topology.kubernetes.io/zone":   {},
+
+		// Deprecated in Kubernetes v1.17
+		"failure-domain.beta.kubernetes.io/region": {},
+		"failure-domain.beta.kubernetes.io/zone":   {},
+	}
+
 	for _, pref := range affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
-		if pref.PodAffinityTerm.TopologyKey == "kubernetes.io/hostname" {
+		if _, ok := approvedTopologyKeys[pref.PodAffinityTerm.TopologyKey]; ok {
 			if selector, err := metav1.LabelSelectorAsSelector(pref.PodAffinityTerm.LabelSelector); err == nil {
-				if selector.Matches(internal.MapLables(selfLables)) {
+				if selector.Matches(selfLabels) {
 					return true
 				}
 			}
@@ -129,9 +139,9 @@ func hasPodAntiAffinity(selfLables internal.MapLables, affinity *corev1.Affinity
 	}
 
 	for _, req := range affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
-		if req.TopologyKey == "kubernetes.io/hostname" {
+		if _, ok := approvedTopologyKeys[req.TopologyKey]; ok {
 			if selector, err := metav1.LabelSelectorAsSelector(req.LabelSelector); err == nil {
-				if selector.Matches(internal.MapLables(selfLables)) {
+				if selector.Matches(selfLabels) {
 					return true
 				}
 			}


Test patch:
diff --git a/score/apps/apps_test.go b/score/apps/apps_test.go
index 9a5e1c3..1994ea0 100644
--- a/score/apps/apps_test.go
+++ b/score/apps/apps_test.go
@@ -52,6 +52,66 @@ func antiAffinityTestCases() []testcase {
 			},
 			expectedSkipped: false,
 		},
+		{
+			// OK! (required) ( topology.kubernetes.io/zone )
+			expectedGrade: scorecard.GradeAllOK,
+			replicas:      i(5),
+			affinity: &corev1.Affinity{
+				PodAntiAffinity: &corev1.PodAntiAffinity{
+					RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
+						{
+							TopologyKey: "topology.kubernetes.io/zone",
+							LabelSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{
+									"app": "foo",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedSkipped: false,
+		},
+		{
+			// OK! (required) ( topology.kubernetes.io/region )
+			expectedGrade: scorecard.GradeAllOK,
+			replicas:      i(5),
+			affinity: &corev1.Affinity{
+				PodAntiAffinity: &corev1.PodAntiAffinity{
+					RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
+						{
+							TopologyKey: "topology.kubernetes.io/region",
+							LabelSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{
+									"app": "foo",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedSkipped: false,
+		},
+		{
+			// Not OK! (required) ( some other topology key )
+			expectedGrade: scorecard.GradeWarning,
+			replicas:      i(5),
+			affinity: &corev1.Affinity{
+				PodAntiAffinity: &corev1.PodAntiAffinity{
+					RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
+						{
+							TopologyKey: "topology.kubernetes.io/what-is-this-key",
+							LabelSelector: &metav1.LabelSelector{
+								MatchLabels: map[string]string{
+									"app": "foo",
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedSkipped: false,
+		},
 		{
 			// OK (preferred)
 			expectedGrade: scorecard.GradeAllOK,