Repository: zegl/kube-score
Pull request #514 (2023-02-20 12:05:05)

score: generic check functions


Patch:
diff --git a/score/checks/checks.go b/score/checks/checks.go
index e0fffd6..20a709c 100644
--- a/score/checks/checks.go
+++ b/score/checks/checks.go
@@ -18,16 +18,16 @@ func New(cnf config.Configuration) *Checks {
 		cnf: cnf,
 
 		all:                      make([]ks.Check, 0),
-		metas:                    make(map[string]MetaCheck),
+		metas:                    make(map[string]GenCheck[ks.BothMeta]),
 		pods:                     make(map[string]PodCheck),
-		services:                 make(map[string]ServiceCheck),
-		statefulsets:             make(map[string]StatefulSetCheck),
-		deployments:              make(map[string]DeploymentCheck),
-		networkpolicies:          make(map[string]NetworkPolicyCheck),
-		ingresses:                make(map[string]IngressCheck),
-		cronjobs:                 make(map[string]CronJobCheck),
-		horizontalPodAutoscalers: make(map[string]HorizontalPodAutoscalerCheck),
-		poddisruptionbudgets:     make(map[string]PodDisruptionBudgetCheck),
+		services:                 make(map[string]GenCheck[corev1.Service]),
+		statefulsets:             make(map[string]GenCheck[appsv1.StatefulSet]),
+		deployments:              make(map[string]GenCheck[appsv1.Deployment]),
+		networkpolicies:          make(map[string]GenCheck[networkingv1.NetworkPolicy]),
+		ingresses:                make(map[string]GenCheck[ks.Ingress]),
+		cronjobs:                 make(map[string]GenCheck[ks.CronJob]),
+		horizontalPodAutoscalers: make(map[string]GenCheck[ks.HpaTargeter]),
+		poddisruptionbudgets:     make(map[string]GenCheck[ks.PodDisruptionBudget]),
 	}
 }
 
@@ -59,66 +59,25 @@ type PodCheck struct {
 	Fn PodCheckFn
 }
 
-type ServiceCheckFn = func(corev1.Service) scorecard.TestScore
-type ServiceCheck struct {
-	ks.Check
-	Fn ServiceCheckFn
-}
-
-type StatefulSetCheckFn = func(appsv1.StatefulSet) (scorecard.TestScore, error)
-type StatefulSetCheck struct {
-	ks.Check
-	Fn StatefulSetCheckFn
-}
-
-type DeploymentCheckFn = func(appsv1.Deployment) (scorecard.TestScore, error)
-type DeploymentCheck struct {
-	ks.Check
-	Fn DeploymentCheckFn
-}
-
-type NetworkPolicyCheckFn = func(networkingv1.NetworkPolicy) scorecard.TestScore
-type NetworkPolicyCheck struct {
-	ks.Check
-	Fn NetworkPolicyCheckFn
-}
+type CheckFunc[T any] func(T) (scorecard.TestScore, error)
 
-type IngressCheckFn = func(ks.Ingress) scorecard.TestScore
-type IngressCheck struct {
+type GenCheck[T any] struct {
 	ks.Check
-	Fn IngressCheckFn
-}
-
-type CronJobCheckFn = func(ks.CronJob) scorecard.TestScore
-type CronJobCheck struct {
-	ks.Check
-	Fn CronJobCheckFn
-}
-
-type HorizontalPodAutoscalerCheckFn = func(ks.HpaTargeter) scorecard.TestScore
-type HorizontalPodAutoscalerCheck struct {
-	ks.Check
-	Fn HorizontalPodAutoscalerCheckFn
-}
-
-type PodDisruptionBudgetCheckFn = func(ks.PodDisruptionBudget) scorecard.TestScore
-type PodDisruptionBudgetCheck struct {
-	ks.Check
-	Fn PodDisruptionBudgetCheckFn
+	Fn CheckFunc[T]
 }
 
 type Checks struct {
 	all                      []ks.Check
-	metas                    map[string]MetaCheck
+	metas                    map[string]GenCheck[ks.BothMeta]
 	pods                     map[string]PodCheck
-	services                 map[string]ServiceCheck
-	statefulsets             map[string]StatefulSetCheck
-	deployments              map[string]DeploymentCheck
-	networkpolicies          map[string]NetworkPolicyCheck
-	ingresses                map[string]IngressCheck
-	cronjobs                 map[string]CronJobCheck
-	horizontalPodAutoscalers map[string]HorizontalPodAutoscalerCheck
-	poddisruptionbudgets     map[string]PodDisruptionBudgetCheck
+	services                 map[string]GenCheck[corev1.Service]
+	statefulsets             map[string]GenCheck[appsv1.StatefulSet]
+	deployments              map[string]GenCheck[appsv1.Deployment]
+	networkpolicies          map[string]GenCheck[networkingv1.NetworkPolicy]
+	ingresses                map[string]GenCheck[ks.Ingress]
+	cronjobs                 map[string]GenCheck[ks.CronJob]
+	horizontalPodAutoscalers map[string]GenCheck[ks.HpaTargeter]
+	poddisruptionbudgets     map[string]GenCheck[ks.PodDisruptionBudget]
 
 	cnf config.Configuration
 }
@@ -128,17 +87,17 @@ func (c Checks) isEnabled(check ks.Check) bool {
 	return !ok
 }
 
-func (c *Checks) RegisterMetaCheck(name, comment string, fn MetaCheckFn) {
+func (c *Checks) RegisterMetaCheck(name, comment string, fn CheckFunc[ks.BothMeta]) {
 	ch := NewCheck(name, "all", comment, false)
-	c.registerMetaCheck(MetaCheck{ch, fn})
+	c.registerMetaCheck(GenCheck[ks.BothMeta]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalMetaCheck(name, comment string, fn MetaCheckFn) {
+func (c *Checks) RegisterOptionalMetaCheck(name, comment string, fn CheckFunc[ks.BothMeta]) {
 	ch := NewCheck(name, "all", comment, true)
-	c.registerMetaCheck(MetaCheck{ch, fn})
+	c.registerMetaCheck(GenCheck[ks.BothMeta]{ch, fn})
 }
 
-func (c *Checks) registerMetaCheck(ch MetaCheck) {
+func (c *Checks) registerMetaCheck(ch GenCheck[ks.BothMeta]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -147,7 +106,7 @@ func (c *Checks) registerMetaCheck(ch MetaCheck) {
 	c.metas[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) Metas() map[string]MetaCheck {
+func (c *Checks) Metas() map[string]GenCheck[ks.BothMeta] {
 	return c.metas
 }
 
@@ -174,17 +133,17 @@ func (c *Checks) Pods() map[string]PodCheck {
 	return c.pods
 }
 
-func (c *Checks) RegisterHorizontalPodAutoscalerCheck(name, comment string, fn HorizontalPodAutoscalerCheckFn) {
+func (c *Checks) RegisterHorizontalPodAutoscalerCheck(name, comment string, fn CheckFunc[ks.HpaTargeter]) {
 	ch := NewCheck(name, "HorizontalPodAutoscaler", comment, false)
-	c.registerHorizontalPodAutoscalerCheck(HorizontalPodAutoscalerCheck{ch, fn})
+	c.registerHorizontalPodAutoscalerCheck(GenCheck[ks.HpaTargeter]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalHorizontalPodAutoscalerCheck(name, comment string, fn HorizontalPodAutoscalerCheckFn) {
+func (c *Checks) RegisterOptionalHorizontalPodAutoscalerCheck(name, comment string, fn CheckFunc[ks.HpaTargeter]) {
 	ch := NewCheck(name, "HorizontalPodAutoscaler", comment, true)
-	c.registerHorizontalPodAutoscalerCheck(HorizontalPodAutoscalerCheck{ch, fn})
+	c.registerHorizontalPodAutoscalerCheck(GenCheck[ks.HpaTargeter]{ch, fn})
 }
 
-func (c *Checks) registerHorizontalPodAutoscalerCheck(ch HorizontalPodAutoscalerCheck) {
+func (c *Checks) registerHorizontalPodAutoscalerCheck(ch GenCheck[ks.HpaTargeter]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -193,21 +152,21 @@ func (c *Checks) registerHorizontalPodAutoscalerCheck(ch HorizontalPodAutoscaler
 	c.horizontalPodAutoscalers[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) HorizontalPodAutoscalers() map[string]HorizontalPodAutoscalerCheck {
+func (c *Checks) HorizontalPodAutoscalers() map[string]GenCheck[ks.HpaTargeter] {
 	return c.horizontalPodAutoscalers
 }
 
-func (c *Checks) RegisterCronJobCheck(name, comment string, fn CronJobCheckFn) {
+func (c *Checks) RegisterCronJobCheck(name, comment string, fn CheckFunc[ks.CronJob]) {
 	ch := NewCheck(name, "CronJob", comment, false)
-	c.registerCronJobCheck(CronJobCheck{ch, fn})
+	c.registerCronJobCheck(GenCheck[ks.CronJob]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalCronJobCheck(name, comment string, fn CronJobCheckFn) {
+func (c *Checks) RegisterOptionalCronJobCheck(name, comment string, fn CheckFunc[ks.CronJob]) {
 	ch := NewCheck(name, "CronJob", comment, true)
-	c.registerCronJobCheck(CronJobCheck{ch, fn})
+	c.registerCronJobCheck(GenCheck[ks.CronJob]{ch, fn})
 }
 
-func (c *Checks) registerCronJobCheck(ch CronJobCheck) {
+func (c *Checks) registerCronJobCheck(ch GenCheck[ks.CronJob]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -216,21 +175,21 @@ func (c *Checks) registerCronJobCheck(ch CronJobCheck) {
 	c.cronjobs[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) CronJobs() map[string]CronJobCheck {
+func (c *Checks) CronJobs() map[string]GenCheck[ks.CronJob] {
 	return c.cronjobs
 }
 
-func (c *Checks) RegisterStatefulSetCheck(name, comment string, fn StatefulSetCheckFn) {
+func (c *Checks) RegisterStatefulSetCheck(name, comment string, fn CheckFunc[appsv1.StatefulSet]) {
 	ch := NewCheck(name, "StatefulSet", comment, false)
-	c.registerStatefulSetCheck(StatefulSetCheck{ch, fn})
+	c.registerStatefulSetCheck(GenCheck[appsv1.StatefulSet]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalStatefulSetCheck(name, comment string, fn StatefulSetCheckFn) {
+func (c *Checks) RegisterOptionalStatefulSetCheck(name, comment string, fn CheckFunc[appsv1.StatefulSet]) {
 	ch := NewCheck(name, "StatefulSet", comment, true)
-	c.registerStatefulSetCheck(StatefulSetCheck{ch, fn})
+	c.registerStatefulSetCheck(GenCheck[appsv1.StatefulSet]{ch, fn})
 }
 
-func (c *Checks) registerStatefulSetCheck(ch StatefulSetCheck) {
+func (c *Checks) registerStatefulSetCheck(ch GenCheck[appsv1.StatefulSet]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -239,21 +198,21 @@ func (c *Checks) registerStatefulSetCheck(ch StatefulSetCheck) {
 	c.statefulsets[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) StatefulSets() map[string]StatefulSetCheck {
+func (c *Checks) StatefulSets() map[string]GenCheck[appsv1.StatefulSet] {
 	return c.statefulsets
 }
 
-func (c *Checks) RegisterDeploymentCheck(name, comment string, fn DeploymentCheckFn) {
+func (c *Checks) RegisterDeploymentCheck(name, comment string, fn CheckFunc[appsv1.Deployment]) {
 	ch := NewCheck(name, "Deployment", comment, false)
-	c.registerDeploymentCheck(DeploymentCheck{ch, fn})
+	c.registerDeploymentCheck(GenCheck[appsv1.Deployment]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalDeploymentCheck(name, comment string, fn DeploymentCheckFn) {
+func (c *Checks) RegisterOptionalDeploymentCheck(name, comment string, fn CheckFunc[appsv1.Deployment]) {
 	ch := NewCheck(name, "Deployment", comment, true)
-	c.registerDeploymentCheck(DeploymentCheck{ch, fn})
+	c.registerDeploymentCheck(GenCheck[appsv1.Deployment]{ch, fn})
 }
 
-func (c *Checks) registerDeploymentCheck(ch DeploymentCheck) {
+func (c *Checks) registerDeploymentCheck(ch GenCheck[appsv1.Deployment]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -262,21 +221,21 @@ func (c *Checks) registerDeploymentCheck(ch DeploymentCheck) {
 	c.deployments[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) Deployments() map[string]DeploymentCheck {
+func (c *Checks) Deployments() map[string]GenCheck[appsv1.Deployment] {
 	return c.deployments
 }
 
-func (c *Checks) RegisterIngressCheck(name, comment string, fn IngressCheckFn) {
+func (c *Checks) RegisterIngressCheck(name, comment string, fn CheckFunc[ks.Ingress]) {
 	ch := NewCheck(name, "Ingress", comment, false)
-	c.registerIngressCheck(IngressCheck{ch, fn})
+	c.registerIngressCheck(GenCheck[ks.Ingress]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalIngressCheck(name, comment string, fn IngressCheckFn) {
+func (c *Checks) RegisterOptionalIngressCheck(name, comment string, fn CheckFunc[ks.Ingress]) {
 	ch := NewCheck(name, "Ingress", comment, true)
-	c.registerIngressCheck(IngressCheck{ch, fn})
+	c.registerIngressCheck(GenCheck[ks.Ingress]{ch, fn})
 }
 
-func (c *Checks) registerIngressCheck(ch IngressCheck) {
+func (c *Checks) registerIngressCheck(ch GenCheck[ks.Ingress]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -285,21 +244,21 @@ func (c *Checks) registerIngressCheck(ch IngressCheck) {
 	c.ingresses[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) Ingresses() map[string]IngressCheck {
+func (c *Checks) Ingresses() map[string]GenCheck[ks.Ingress] {
 	return c.ingresses
 }
 
-func (c *Checks) RegisterNetworkPolicyCheck(name, comment string, fn NetworkPolicyCheckFn) {
+func (c *Checks) RegisterNetworkPolicyCheck(name, comment string, fn CheckFunc[networkingv1.NetworkPolicy]) {
 	ch := NewCheck(name, "NetworkPolicy", comment, false)
-	c.registerNetworkPolicyCheck(NetworkPolicyCheck{ch, fn})
+	c.registerNetworkPolicyCheck(GenCheck[networkingv1.NetworkPolicy]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalNetworkPolicyCheck(name, comment string, fn NetworkPolicyCheckFn) {
+func (c *Checks) RegisterOptionalNetworkPolicyCheck(name, comment string, fn CheckFunc[networkingv1.NetworkPolicy]) {
 	ch := NewCheck(name, "NetworkPolicy", comment, true)
-	c.registerNetworkPolicyCheck(NetworkPolicyCheck{ch, fn})
+	c.registerNetworkPolicyCheck(GenCheck[networkingv1.NetworkPolicy]{ch, fn})
 }
 
-func (c *Checks) registerNetworkPolicyCheck(ch NetworkPolicyCheck) {
+func (c *Checks) registerNetworkPolicyCheck(ch GenCheck[networkingv1.NetworkPolicy]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -308,39 +267,40 @@ func (c *Checks) registerNetworkPolicyCheck(ch NetworkPolicyCheck) {
 	c.networkpolicies[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) NetworkPolicies() map[string]NetworkPolicyCheck {
+func (c *Checks) NetworkPolicies() map[string]GenCheck[networkingv1.NetworkPolicy] {
 	return c.networkpolicies
 }
 
-func (c *Checks) RegisterPodDisruptionBudgetCheck(name, comment string, fn PodDisruptionBudgetCheckFn) {
+func (c *Checks) RegisterPodDisruptionBudgetCheck(name, comment string, fn CheckFunc[ks.PodDisruptionBudget]) {
 	ch := NewCheck(name, "PodDisruptionBudget", comment, false)
-	c.registerPodDisruptionBudgetCheck(PodDisruptionBudgetCheck{ch, fn})
+	c.registerPodDisruptionBudgetCheck(GenCheck[ks.PodDisruptionBudget]{ch, fn})
 }
 
-func (c *Checks) registerPodDisruptionBudgetCheck(ch PodDisruptionBudgetCheck) {
+func (c *Checks) registerPodDisruptionBudgetCheck(ch GenCheck[ks.PodDisruptionBudget]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
 		return
 	}
+
 	c.poddisruptionbudgets[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) PodDisruptionBudgets() map[string]PodDisruptionBudgetCheck {
+func (c *Checks) PodDisruptionBudgets() map[string]GenCheck[ks.PodDisruptionBudget] {
 	return c.poddisruptionbudgets
 }
 
-func (c *Checks) RegisterServiceCheck(name, comment string, fn ServiceCheckFn) {
+func (c *Checks) RegisterServiceCheck(name, comment string, fn CheckFunc[corev1.Service]) {
 	ch := NewCheck(name, "Service", comment, false)
-	c.registerServiceCheck(ServiceCheck{ch, fn})
+	c.registerServiceCheck(GenCheck[corev1.Service]{ch, fn})
 }
 
-func (c *Checks) RegisterOptionalServiceCheck(name, comment string, fn ServiceCheckFn) {
+func (c *Checks) RegisterOptionalServiceCheck(name, comment string, fn CheckFunc[corev1.Service]) {
 	ch := NewCheck(name, "Service", comment, true)
-	c.registerServiceCheck(ServiceCheck{ch, fn})
+	c.registerServiceCheck(GenCheck[corev1.Service]{ch, fn})
 }
 
-func (c *Checks) registerServiceCheck(ch ServiceCheck) {
+func (c *Checks) registerServiceCheck(ch GenCheck[corev1.Service]) {
 	c.all = append(c.all, ch.Check)
 
 	if !c.isEnabled(ch.Check) {
@@ -349,7 +309,7 @@ func (c *Checks) registerServiceCheck(ch ServiceCheck) {
 	c.services[machineFriendlyName(ch.Name)] = ch
 }
 
-func (c *Checks) Services() map[string]ServiceCheck {
+func (c *Checks) Services() map[string]GenCheck[corev1.Service] {
 	return c.services
 }
 
diff --git a/score/cronjob/cronjob.go b/score/cronjob/cronjob.go
index fdb8ab3..1553a46 100644
--- a/score/cronjob/cronjob.go
+++ b/score/cronjob/cronjob.go
@@ -11,7 +11,7 @@ func Register(allChecks *checks.Checks) {
 	allChecks.RegisterCronJobCheck("CronJob RestartPolicy", `Makes sure CronJobs have a valid RestartPolicy`, cronJobHasRestartPolicy)
 }
 
-func cronJobHasDeadline(job ks.CronJob) (score scorecard.TestScore) {
+func cronJobHasDeadline(job ks.CronJob) (score scorecard.TestScore, err error) {
 	if job.StartingDeadlineSeconds() == nil {
 		score.Grade = scorecard.GradeCritical
 		score.AddComment("", "The CronJob should have startingDeadlineSeconds configured",
@@ -24,8 +24,7 @@ func cronJobHasDeadline(job ks.CronJob) (score scorecard.TestScore) {
 }
 
 // CronJob restartPolicy must be "OnFailure" or "Never". It cannot be empty (unspecified)
-func cronJobHasRestartPolicy(job ks.CronJob) (score scorecard.TestScore) {
-
+func cronJobHasRestartPolicy(job ks.CronJob) (score scorecard.TestScore, err error) {
 	podTmpl := job.GetPodTemplateSpec()
 	restartPolicy := podTmpl.Spec.RestartPolicy
 
diff --git a/score/disruptionbudget/disruptionbudget.go b/score/disruptionbudget/disruptionbudget.go
index 080beac..b94dbde 100644
--- a/score/disruptionbudget/disruptionbudget.go
+++ b/score/disruptionbudget/disruptionbudget.go
@@ -87,7 +87,7 @@ func deploymentHas(budgets []ks.PodDisruptionBudget) func(appsv1.Deployment) (sc
 	}
 }
 
-func hasPolicy(pdb ks.PodDisruptionBudget) (score scorecard.TestScore) {
+func hasPolicy(pdb ks.PodDisruptionBudget) (score scorecard.TestScore, err error) {
 	spec := pdb.Spec()
 	if spec.MinAvailable == nil && spec.MaxUnavailable == nil {
 		score.AddComment("", "PodDisruptionBudget missing policy", "PodDisruptionBudget should specify minAvailable or maxUnavailable.")
diff --git a/score/hpa/hpa.go b/score/hpa/hpa.go
index 898dc94..7414fbf 100644
--- a/score/hpa/hpa.go
+++ b/score/hpa/hpa.go
@@ -10,8 +10,8 @@ func Register(allChecks *checks.Checks, allTargetableObjs []domain.BothMeta) {
 	allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler has target", `Makes sure that the HPA targets a valid object`, hpaHasTarget(allTargetableObjs))
 }
 
-func hpaHasTarget(allTargetableObjs []domain.BothMeta) func(hpa domain.HpaTargeter) scorecard.TestScore {
-	return func(hpa domain.HpaTargeter) (score scorecard.TestScore) {
+func hpaHasTarget(allTargetableObjs []domain.BothMeta) func(hpa domain.HpaTargeter) (scorecard.TestScore, error) {
+	return func(hpa domain.HpaTargeter) (score scorecard.TestScore, err error) {
 		targetRef := hpa.HpaTarget()
 		var hasTarget bool
 		for _, t := range allTargetableObjs {
diff --git a/score/ingress/ingress.go b/score/ingress/ingress.go
index aa4b7c8..f36c51b 100644
--- a/score/ingress/ingress.go
+++ b/score/ingress/ingress.go
@@ -12,13 +12,13 @@ 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 []ks.Service) func(ks.Ingress) scorecard.TestScore {
-	return func(ingress ks.Ingress) (score scorecard.TestScore) {
+func ingressTargetsService(allServices []ks.Service) func(ks.Ingress) (scorecard.TestScore, error) {
+	return func(ingress ks.Ingress) (scorecard.TestScore, error) {
 		return ingressTargetsServiceCommon(ingress, allServices)
 	}
 }
 
-func ingressTargetsServiceCommon(ingress ks.Ingress, allServices []ks.Service) (score scorecard.TestScore) {
+func ingressTargetsServiceCommon(ingress ks.Ingress, allServices []ks.Service) (score scorecard.TestScore, err error) {
 	allRulesHaveMatches := true
 
 	for _, rule := range ingress.Rules() {
diff --git a/score/meta/labels.go b/score/meta/labels.go
index a0270cb..81fe121 100644
--- a/score/meta/labels.go
+++ b/score/meta/labels.go
@@ -12,7 +12,7 @@ func Register(allChecks *checks.Checks) {
 	allChecks.RegisterMetaCheck("Label values", "Validates label values", validateLabelValues)
 }
 
-func validateLabelValues(meta domain.BothMeta) (score scorecard.TestScore) {
+func validateLabelValues(meta domain.BothMeta) (score scorecard.TestScore, err error) {
 	score.Grade = scorecard.GradeAllOK
 	r := regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$")
 	for key, value := range meta.ObjectMeta.Labels {
diff --git a/score/networkpolicy/networkpolicy.go b/score/networkpolicy/networkpolicy.go
index fa1757f..2a8bfba 100644
--- a/score/networkpolicy/networkpolicy.go
+++ b/score/networkpolicy/networkpolicy.go
@@ -83,8 +83,8 @@ func podHasNetworkPolicy(allNetpols []ks.NetworkPolicy) func(spec corev1.PodTemp
 	}
 }
 
-func networkPolicyTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(networkingv1.NetworkPolicy) scorecard.TestScore {
-	return func(netpol networkingv1.NetworkPolicy) (score scorecard.TestScore) {
+func networkPolicyTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(networkingv1.NetworkPolicy) (scorecard.TestScore, error) {
+	return func(netpol networkingv1.NetworkPolicy) (score scorecard.TestScore, err error) {
 		hasMatch := false
 
 		for _, p := range pods {
diff --git a/score/score.go b/score/score.go
index a987969..c8fafcc 100644
--- a/score/score.go
+++ b/score/score.go
@@ -54,14 +54,22 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	for _, ingress := range allObjects.Ingresses() {
 		o := newObject(ingress.GetTypeMeta(), ingress.GetObjectMeta())
 		for _, test := range allChecks.Ingresses() {
-			o.Add(test.Fn(ingress), test.Check, ingress, ingress.GetObjectMeta().Annotations)
+			fn, err := test.Fn(ingress)
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, ingress, ingress.GetObjectMeta().Annotations)
 		}
 	}
 
 	for _, meta := range allObjects.Metas() {
 		o := newObject(meta.TypeMeta, meta.ObjectMeta)
 		for _, test := range allChecks.Metas() {
-			o.Add(test.Fn(meta), test.Check, meta, meta.ObjectMeta.Annotations)
+			fn, err := test.Fn(meta)
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, meta, meta.ObjectMeta.Annotations)
 		}
 	}
 
@@ -90,18 +98,22 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	for _, service := range allObjects.Services() {
 		o := newObject(service.Service().TypeMeta, service.Service().ObjectMeta)
 		for _, test := range allChecks.Services() {
-			o.Add(test.Fn(service.Service()), test.Check, service, service.Service().Annotations)
+			fn, err := test.Fn(service.Service())
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, service, service.Service().Annotations)
 		}
 	}
 
 	for _, statefulset := range allObjects.StatefulSets() {
 		o := newObject(statefulset.StatefulSet().TypeMeta, statefulset.StatefulSet().ObjectMeta)
 		for _, test := range allChecks.StatefulSets() {
-			res, err := test.Fn(statefulset.StatefulSet())
+			fn, err := test.Fn(statefulset.StatefulSet())
 			if err != nil {
 				return nil, err
 			}
-			o.Add(res, test.Check, statefulset, statefulset.StatefulSet().ObjectMeta.Annotations)
+			o.Add(fn, test.Check, statefulset, statefulset.StatefulSet().ObjectMeta.Annotations)
 		}
 	}
 
@@ -119,28 +131,44 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	for _, netpol := range allObjects.NetworkPolicies() {
 		o := newObject(netpol.NetworkPolicy().TypeMeta, netpol.NetworkPolicy().ObjectMeta)
 		for _, test := range allChecks.NetworkPolicies() {
-			o.Add(test.Fn(netpol.NetworkPolicy()), test.Check, netpol, netpol.NetworkPolicy().ObjectMeta.Annotations)
+			fn, err := test.Fn(netpol.NetworkPolicy())
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, netpol, netpol.NetworkPolicy().ObjectMeta.Annotations)
 		}
 	}
 
 	for _, cjob := range allObjects.CronJobs() {
 		o := newObject(cjob.GetTypeMeta(), cjob.GetObjectMeta())
 		for _, test := range allChecks.CronJobs() {
-			o.Add(test.Fn(cjob), test.Check, cjob, cjob.GetObjectMeta().Annotations)
+			fn, err := test.Fn(cjob)
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, cjob, cjob.GetObjectMeta().Annotations)
 		}
 	}
 
 	for _, hpa := range allObjects.HorizontalPodAutoscalers() {
 		o := newObject(hpa.GetTypeMeta(), hpa.GetObjectMeta())
 		for _, test := range allChecks.HorizontalPodAutoscalers() {
-			o.Add(test.Fn(hpa), test.Check, hpa, hpa.GetObjectMeta().Annotations)
+			fn, err := test.Fn(hpa)
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, hpa, hpa.GetObjectMeta().Annotations)
 		}
 	}
 
 	for _, pdb := range allObjects.PodDisruptionBudgets() {
 		o := newObject(pdb.GetTypeMeta(), pdb.GetObjectMeta())
 		for _, test := range allChecks.PodDisruptionBudgets() {
-			o.Add(test.Fn(pdb), test.Check, pdb, pdb.GetObjectMeta().Annotations)
+			fn, err := test.Fn(pdb)
+			if err != nil {
+				return nil, err
+			}
+			o.Add(fn, test.Check, pdb, pdb.GetObjectMeta().Annotations)
 		}
 	}
 
diff --git a/score/service/service.go b/score/service/service.go
index 1baf74b..e281db6 100644
--- a/score/service/service.go
+++ b/score/service/service.go
@@ -16,7 +16,7 @@ func Register(allChecks *checks.Checks, pods ks.Pods, podspeccers ks.PodSpeccers
 
 // serviceTargetsPod checks if a Service targets a pod and issues a critical warning if no matching pod
 // could be found
-func serviceTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(corev1.Service) scorecard.TestScore {
+func serviceTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(corev1.Service) (scorecard.TestScore, error) {
 	podsInNamespace := make(map[string][]map[string]string)
 	for _, p := range pods {
 		pod := p.Pod()
@@ -32,7 +32,7 @@ func serviceTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(corev1.Ser
 		podsInNamespace[podSpec.GetObjectMeta().Namespace] = append(podsInNamespace[podSpec.GetObjectMeta().Namespace], podSpec.GetPodTemplateSpec().Labels)
 	}
 
-	return func(service corev1.Service) (score scorecard.TestScore) {
+	return func(service corev1.Service) (score scorecard.TestScore, err error) {
 		// Services of type ExternalName does not have a selector
 		if service.Spec.Type == corev1.ServiceTypeExternalName {
 			score.Grade = scorecard.GradeAllOK
@@ -59,7 +59,7 @@ func serviceTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(corev1.Ser
 	}
 }
 
-func serviceType(service corev1.Service) (score scorecard.TestScore) {
+func serviceType(service corev1.Service) (score scorecard.TestScore, err error) {
 	if service.Spec.Type == corev1.ServiceTypeNodePort {
 		score.Grade = scorecard.GradeWarning
 		score.AddComment("", "The service is of type NodePort", "NodePort services should be avoided as they are insecure, and can not be used together with NetworkPolicies. LoadBalancers or use of an Ingress is recommended over NodePorts.")
diff --git a/score/stable/stable_version.go b/score/stable/stable_version.go
index 78c4610..b026dc9 100644
--- a/score/stable/stable_version.go
+++ b/score/stable/stable_version.go
@@ -14,8 +14,8 @@ func Register(kubernetesVersion config.Semver, allChecks *checks.Checks) {
 }
 
 // ScoreMetaStableAvailable checks if the supplied TypeMeta is an unstable object type, that has a stable(r) replacement
-func metaStableAvailable(kubernetsVersion config.Semver) func(meta domain.BothMeta) (score scorecard.TestScore) {
-	return func(meta domain.BothMeta) (score scorecard.TestScore) {
+func metaStableAvailable(kubernetsVersion config.Semver) func(meta domain.BothMeta) (scorecard.TestScore, error) {
+	return func(meta domain.BothMeta) (score scorecard.TestScore, err error) {
 		type recommendedApi struct {
 			newAPI         string
 			availableSince config.Semver


Test patch:
diff --git a/score/hpa/hpa_test.go b/score/hpa/hpa_test.go
index 4d086f8..8b4be19 100644
--- a/score/hpa/hpa_test.go
+++ b/score/hpa/hpa_test.go
@@ -160,7 +160,7 @@ func TestHpaHasTarget(t *testing.T) {
 
 	for _, tc := range testcases {
 		fn := hpaHasTarget(tc.allTargets)
-		score := fn(hpav1{tc.hpa})
+		score, _ := fn(hpav1{tc.hpa})
 		assert.Equal(t, tc.expectedGrade, score.Grade)
 	}
 }
diff --git a/score/meta/labels_test.go b/score/meta/labels_test.go
index d95b0a3..160c752 100644
--- a/score/meta/labels_test.go
+++ b/score/meta/labels_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestInvalidLabel(t *testing.T) {
 	t.Parallel()
-	s := validateLabelValues(domain.BothMeta{
+	s, _ := validateLabelValues(domain.BothMeta{
 		ObjectMeta: metav1.ObjectMeta{
 			Labels: map[string]string{
 				"foo": "engineering/kustomize", // label values can't contain slashes
@@ -29,7 +29,7 @@ func TestInvalidLabel(t *testing.T) {
 }
 func TestOKLabel(t *testing.T) {
 	t.Parallel()
-	s := validateLabelValues(domain.BothMeta{
+	s, _ := validateLabelValues(domain.BothMeta{
 		ObjectMeta: metav1.ObjectMeta{
 			Labels: map[string]string{
 				"foo": "foo-bar",
diff --git a/score/stable/stable_version_test.go b/score/stable/stable_version_test.go
index b195aa9..5db8056 100644
--- a/score/stable/stable_version_test.go
+++ b/score/stable/stable_version_test.go
@@ -13,35 +13,35 @@ import (
 
 func TestStableVersionOldKubernetesVersion(t *testing.T) {
 	oldKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 4})
-	scoreOld := oldKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
+	scoreOld, _ := oldKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
 	assert.Equal(t, scorecard.GradeAllOK, scoreOld.Grade)
 	assert.Equal(t, []scorecard.TestScoreComment(nil), scoreOld.Comments)
 }
 
 func TestStableVersionNewKubernetesVersion(t *testing.T) {
 	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 18})
-	scoreNew := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
+	scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
 	assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
 	assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind extensions/v1beta1/Deployment is deprecated", Description: "It's recommended to use apps/v1 instead which has been available since Kubernetes v1.9", DocumentationURL: ""}}, scoreNew.Comments)
 }
 
 func TestStableVersionIngress(t *testing.T) {
 	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 20})
-	scoreNew := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "extensions/v1beta1"}})
+	scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "extensions/v1beta1"}})
 	assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
 	assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind extensions/v1beta1/Ingress is deprecated", Description: "It's recommended to use networking.k8s.io/v1 instead which has been available since Kubernetes v1.19", DocumentationURL: ""}}, scoreNew.Comments)
 }
 
 func TestStableVersionPodDisruptionBudget(t *testing.T) {
 	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
-	scoreNew := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "PodDisruptionBudget", APIVersion: "policy/v1beta1"}})
+	scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "PodDisruptionBudget", APIVersion: "policy/v1beta1"}})
 	assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
 	assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind policy/v1beta1/PodDisruptionBudget is deprecated", Description: "It's recommended to use policy/v1 instead which has been available since Kubernetes v1.21", DocumentationURL: ""}}, scoreNew.Comments)
 }
 
 func TestStableNetworkingIngress(t *testing.T) {
 	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
-	scoreNew := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1beta1"}})
+	scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1beta1"}})
 	assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
 	assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind networking.k8s.io/v1beta1/Ingress is deprecated", Description: "It's recommended to use networking.k8s.io/v1 instead which has been available since Kubernetes v1.19", DocumentationURL: ""}}, scoreNew.Comments)
 }