Repository: zegl/kube-score
Pull request #290 (2020-08-10 18:29:44)

score: support Ingress networking.k8s.io/v1beta1 API
```
RELNOTE: Added support for the networking.k8s.io/v1beta1 Ingress API
```



Patch:
diff --git a/domain/kube-score.go b/domain/kube-score.go
index f77d616..2375cd2 100644
--- a/domain/kube-score.go
+++ b/domain/kube-score.go
@@ -7,6 +7,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 	networkingv1 "k8s.io/api/networking/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	policyv1beta1 "k8s.io/api/policy/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -68,6 +69,10 @@ type Ingresses interface {
 	Ingresses() []extensionsv1beta1.Ingress
 }
 
+type Networkingv1beta1Ingresses interface {
+	Networkingv1beta1Ingresses() []networkingv1beta1.Ingress
+}
+
 type CronJobs interface {
 	CronJobs() []batchv1beta1.CronJob
 }
@@ -89,6 +94,7 @@ type AllTypes interface {
 	Deployments
 	NetworkPolicies
 	Ingresses
+	Networkingv1beta1Ingresses
 	CronJobs
 	PodDisruptionBudgets
 	HorizontalPodAutoscalers
diff --git a/parser/parse.go b/parser/parse.go
index 016740f..9d5c3d4 100644
--- a/parser/parse.go
+++ b/parser/parse.go
@@ -18,6 +18,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 	networkingv1 "k8s.io/api/networking/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	policyv1beta1 "k8s.io/api/policy/v1beta1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -61,9 +62,11 @@ type parsedObjects struct {
 	podDisruptionBudgets []policyv1beta1.PodDisruptionBudget
 	deployments          []appsv1.Deployment
 	statefulsets         []appsv1.StatefulSet
-	ingresses            []extensionsv1beta1.Ingress
-	cronjobs             []batchv1beta1.CronJob
-	// horizontalPodAutoscalers []autoscalingv1.HorizontalPodAutoscaler
+
+	ingresses                  []extensionsv1beta1.Ingress
+	networkingv1beta1ingresses []networkingv1beta1.Ingress
+
+	cronjobs     []batchv1beta1.CronJob
 	hpaTargeters []ks.HpaTargeter // all versions of HPAs
 }
 
@@ -83,6 +86,10 @@ func (p *parsedObjects) Ingresses() []extensionsv1beta1.Ingress {
 	return p.ingresses
 }
 
+func (p *parsedObjects) Networkingv1beta1Ingresses() []networkingv1beta1.Ingress {
+	return p.networkingv1beta1ingresses
+}
+
 func (p *parsedObjects) PodDisruptionBudgets() []policyv1beta1.PodDisruptionBudget {
 	return p.podDisruptionBudgets
 }
@@ -278,6 +285,11 @@ func decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion sche
 		errs.AddIfErr(decode(fileContents, &ingress))
 		s.ingresses = append(s.ingresses, ingress)
 		s.bothMetas = append(s.bothMetas, ks.BothMeta{ingress.TypeMeta, ingress.ObjectMeta})
+	case networkingv1beta1.SchemeGroupVersion.WithKind("Ingress"):
+		var ingress networkingv1beta1.Ingress
+		errs.AddIfErr(decode(fileContents, &ingress))
+		s.networkingv1beta1ingresses = append(s.networkingv1beta1ingresses, ingress)
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{ingress.TypeMeta, ingress.ObjectMeta})
 
 	case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
 		var hpa autoscalingv1.HorizontalPodAutoscaler
diff --git a/score/checks/checks.go b/score/checks/checks.go
index 68308aa..043a87c 100644
--- a/score/checks/checks.go
+++ b/score/checks/checks.go
@@ -1,6 +1,7 @@
 package checks
 
 import (
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	"strings"
 
 	appsv1 "k8s.io/api/apps/v1"
@@ -84,7 +85,8 @@ type NetworkPolicyCheck struct {
 	Fn NetworkPolicyCheckFn
 }
 
-type IngressCheckFn = func(extensionsv1beta1.Ingress) scorecard.TestScore
+// Calls to IngressCheckFn should set either parameter 1 or 2, but not both.
+type IngressCheckFn = func(*extensionsv1beta1.Ingress, *networkingv1beta1.Ingress) scorecard.TestScore
 type IngressCheck struct {
 	ks.Check
 	Fn IngressCheckFn
diff --git a/score/ingress/ingress.go b/score/ingress/ingress.go
index 3514dff..65585ba 100644
--- a/score/ingress/ingress.go
+++ b/score/ingress/ingress.go
@@ -2,56 +2,111 @@ package ingress
 
 import (
 	"fmt"
+	corev1 "k8s.io/api/core/v1"
+	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
+
 	ks "github.com/zegl/kube-score/domain"
 	"github.com/zegl/kube-score/score/checks"
 	"github.com/zegl/kube-score/scorecard"
-	corev1 "k8s.io/api/core/v1"
-	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 )
 
 func Register(allChecks *checks.Checks, services ks.Services) {
 	allChecks.RegisterIngressCheck("Ingress targets Service", `Makes sure that the Ingress targets a Service`, ingressTargetsService(services.Services()))
 }
 
-func ingressTargetsService(allServices []corev1.Service) func(extensionsv1beta1.Ingress) scorecard.TestScore {
-	return func(ingress extensionsv1beta1.Ingress) (score scorecard.TestScore) {
-		allRulesHaveMatches := true
+func ingressTargetsService(allServices []corev1.Service) func(*extensionsv1beta1.Ingress, *networkingv1beta1.Ingress) scorecard.TestScore {
+	return func(
+		ingressExtensions *extensionsv1beta1.Ingress,
+		ingressNetworking *networkingv1beta1.Ingress,
+	) (score scorecard.TestScore) {
+		if ingressExtensions != nil {
+			return ingressTargetsServiceExtensions(*ingressExtensions, allServices)
+		} else {
+			return ingressTargetsServiceNetworking(*ingressNetworking, allServices)
+		}
+	}
+}
+
+// The function body of ingressTargetsServiceExtensions and ingressTargetsServiceNetworking are identical
+// but handles different versions of the API.
 
-		for _, rule := range ingress.Spec.Rules {
-			for _, path := range rule.IngressRuleValue.HTTP.Paths {
+func ingressTargetsServiceExtensions(ingress extensionsv1beta1.Ingress, allServices []corev1.Service) (score scorecard.TestScore) {
+	allRulesHaveMatches := true
 
-				pathHasMatch := false
+	for _, rule := range ingress.Spec.Rules {
+		for _, path := range rule.IngressRuleValue.HTTP.Paths {
 
-				for _, service := range allServices {
-					if service.Namespace != ingress.Namespace {
-						continue
-					}
+			pathHasMatch := false
 
-					if service.Name == path.Backend.ServiceName {
-						for _, servicePort := range service.Spec.Ports {
-							if path.Backend.ServicePort.IntVal > 0 && servicePort.Port == path.Backend.ServicePort.IntVal {
-								pathHasMatch = true
-							} else if servicePort.Name == path.Backend.ServicePort.StrVal {
-								pathHasMatch = true
-							}
-						}
-					}
+			for _, service := range allServices {
+				if service.Namespace != ingress.Namespace {
+					continue
 				}
 
-				if !pathHasMatch {
-					allRulesHaveMatches = false
-					score.AddComment(path.Path, "No service match was found", fmt.Sprintf("No service with name %s and port %d was found", path.Backend.ServiceName, path.Backend.ServicePort.IntVal))
+				if service.Name == path.Backend.ServiceName {
+					for _, servicePort := range service.Spec.Ports {
+						if path.Backend.ServicePort.IntVal > 0 && servicePort.Port == path.Backend.ServicePort.IntVal {
+							pathHasMatch = true
+						} else if servicePort.Name == path.Backend.ServicePort.StrVal {
+							pathHasMatch = true
+						}
+					}
 				}
 			}
 
+			if !pathHasMatch {
+				allRulesHaveMatches = false
+				score.AddComment(path.Path, "No service match was found", fmt.Sprintf("No service with name %s and port %d was found", path.Backend.ServiceName, path.Backend.ServicePort.IntVal))
+			}
 		}
+	}
 
-		if allRulesHaveMatches {
-			score.Grade = scorecard.GradeAllOK
-		} else {
-			score.Grade = scorecard.GradeCritical
+	if allRulesHaveMatches {
+		score.Grade = scorecard.GradeAllOK
+	} else {
+		score.Grade = scorecard.GradeCritical
+	}
+
+	return
+}
+
+func ingressTargetsServiceNetworking(ingress networkingv1beta1.Ingress, allServices []corev1.Service) (score scorecard.TestScore) {
+	allRulesHaveMatches := true
+
+	for _, rule := range ingress.Spec.Rules {
+		for _, path := range rule.IngressRuleValue.HTTP.Paths {
+
+			pathHasMatch := false
+
+			for _, service := range allServices {
+				if service.Namespace != ingress.Namespace {
+					continue
+				}
+
+				if service.Name == path.Backend.ServiceName {
+					for _, servicePort := range service.Spec.Ports {
+						if path.Backend.ServicePort.IntVal > 0 && servicePort.Port == path.Backend.ServicePort.IntVal {
+							pathHasMatch = true
+						} else if servicePort.Name == path.Backend.ServicePort.StrVal {
+							pathHasMatch = true
+						}
+					}
+				}
+			}
+
+			if !pathHasMatch {
+				allRulesHaveMatches = false
+				score.AddComment(path.Path, "No service match was found", fmt.Sprintf("No service with name %s and port %d was found", path.Backend.ServiceName, path.Backend.ServicePort.IntVal))
+			}
 		}
+	}
 
-		return
+	if allRulesHaveMatches {
+		score.Grade = scorecard.GradeAllOK
+	} else {
+		score.Grade = scorecard.GradeCritical
 	}
+
+	return
 }
diff --git a/score/score.go b/score/score.go
index bc1ce61..4293f93 100644
--- a/score/score.go
+++ b/score/score.go
@@ -54,7 +54,13 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	for _, ingress := range allObjects.Ingresses() {
 		o := newObject(ingress.TypeMeta, ingress.ObjectMeta)
 		for _, test := range allChecks.Ingresses() {
-			o.Add(test.Fn(ingress), test.Check)
+			o.Add(test.Fn(&ingress, nil), test.Check)
+		}
+	}
+	for _, ingress := range allObjects.Networkingv1beta1Ingresses() {
+		o := newObject(ingress.TypeMeta, ingress.ObjectMeta)
+		for _, test := range allChecks.Ingresses() {
+			o.Add(test.Fn(nil, &ingress), test.Check)
 		}
 	}
 


Test patch:
diff --git a/score/ingress_test.go b/score/ingress_test.go
index c5678e9..b7b53b0 100644
--- a/score/ingress_test.go
+++ b/score/ingress_test.go
@@ -15,3 +15,13 @@ func TestIngressTargetsServiceNoMatch(t *testing.T) {
 	t.Parallel()
 	testExpectedScore(t, "ingress-targets-service-no-match.yaml", "Ingress targets Service", scorecard.GradeCritical)
 }
+
+func TestNetworkingIngressTargetsService(t *testing.T) {
+	t.Parallel()
+	testExpectedScore(t, "ingress-networkingv1beta1-targets-service.yaml", "Ingress targets Service", scorecard.GradeAllOK)
+}
+
+func TestNetworkingIngressTargetsServiceNoMatch(t *testing.T) {
+	t.Parallel()
+	testExpectedScore(t, "ingress-networkingv1beta1-targets-service-no-match.yaml", "Ingress targets Service", scorecard.GradeCritical)
+}
diff --git a/score/testdata/ingress-networkingv1beta1-targets-service-no-match.yaml b/score/testdata/ingress-networkingv1beta1-targets-service-no-match.yaml
new file mode 100644
index 0000000..03cd4d3
--- /dev/null
+++ b/score/testdata/ingress-networkingv1beta1-targets-service-no-match.yaml
@@ -0,0 +1,32 @@
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+  name: app-ingress
+  namespace: testspace
+spec:
+  rules:
+  - http:
+      paths:
+      - path: /app
+        backend:
+          serviceName: app-service
+          servicePort: 5601
+
+      - path: /no-match
+        backend:
+          serviceName: app-service-123123
+          servicePort: 5601
+
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: app-service
+  namespace: testspace
+spec:
+  selector:
+    app: kibana
+  ports:
+  - name: http
+    protocol: TCP
+    port: 5601
diff --git a/score/testdata/ingress-networkingv1beta1-targets-service.yaml b/score/testdata/ingress-networkingv1beta1-targets-service.yaml
new file mode 100644
index 0000000..d5aaf2a
--- /dev/null
+++ b/score/testdata/ingress-networkingv1beta1-targets-service.yaml
@@ -0,0 +1,26 @@
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+  name: app-ingress
+  namespace: testspace
+spec:
+  rules:
+  - http:
+      paths:
+      - path: /app
+        backend:
+          serviceName: app-service
+          servicePort: 5601
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: app-service
+  namespace: testspace
+spec:
+  selector:
+    app: kibana
+  ports:
+  - name: http
+    protocol: TCP
+    port: 5601