Repository: zegl/kube-score
Pull request #446 (2022-03-20 11:02:54)

Enable more linters in CI.
This enables the last four linters left out from #438 Tests currently fail, and I'll try to fix them today. See #440 for reference.

While doing this, I had a look at `go.mod` Are you sure 1.13 will work with the current code? 




Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 05cadd7..d47fb6b 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -3,11 +3,11 @@ linters:
   disable-all: true
   enable:
     - deadcode
-    # - errcheck
+    - errcheck
     - gosimple
-    # - govet
+    - govet
     - ineffassign
-    # - staticcheck
+    - staticcheck
     - structcheck
     - typecheck
     - unused
@@ -16,7 +16,7 @@ linters:
     - contextcheck
     - cyclop
     - durationcheck
-    # - errname
+    - errname
     - errorlint
     - exportloopref
     - goimports
diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go
index 8058e47..9529cfc 100644
--- a/cmd/kube-score/main.go
+++ b/cmd/kube-score/main.go
@@ -12,7 +12,7 @@ import (
 	"path/filepath"
 
 	flag "github.com/spf13/pflag"
-	"golang.org/x/crypto/ssh/terminal"
+	"golang.org/x/term"
 
 	"github.com/zegl/kube-score/config"
 	ks "github.com/zegl/kube-score/domain"
@@ -40,7 +40,10 @@ func main() {
 		},
 
 		"list": func(helpName string, args []string) {
-			listChecks(helpName, args)
+			if err := listChecks(helpName, args); err != nil {
+				_, _ = fmt.Fprintf(os.Stderr, "Failed to list checks: %v\n", err)
+				os.Exit(1)
+			}
 		},
 
 		"version": func(helpName string, args []string) {
@@ -205,12 +208,13 @@ Use "-" as filename to read from STDIN.`, execName(binName))
 	case *outputFormat == "json" && version == "v2":
 		r = json_v2.Output(scoreCard)
 	case *outputFormat == "human" && version == "v1":
-		termWidth, _, err := terminal.GetSize(int(os.Stdin.Fd()))
+		termWidth, _, err := term.GetSize(int(os.Stdin.Fd()))
 		// Assume a width of 80 if it can't be detected
 		if err != nil {
 			termWidth = 80
 		}
-		r = human.Human(scoreCard, *verboseOutput, termWidth)
+		_, err = human.Human(scoreCard, *verboseOutput, termWidth)
+		return err
 	case *outputFormat == "ci" && version == "v1":
 		r = ci.CI(scoreCard)
 	case *outputFormat == "sarif":
@@ -238,15 +242,18 @@ func getOutputVersion(flagValue, format string) string {
 	}
 }
 
-func listChecks(binName string, args []string) {
+func listChecks(binName string, args []string) error {
 	fs := flag.NewFlagSet(binName, flag.ExitOnError)
 	printHelp := fs.Bool("help", false, "Print help")
 	setDefault(fs, binName, "list", false)
-	fs.Parse(args)
+	err := fs.Parse(args)
+	if err != nil {
+		return nil
+	}
 
 	if *printHelp {
 		fs.Usage()
-		return
+		return nil
 	}
 
 	allChecks := score.RegisterAllChecks(parser.Empty(), config.Configuration{})
@@ -257,9 +264,14 @@ func listChecks(binName string, args []string) {
 		if c.Optional {
 			optionalString = "optional"
 		}
-		output.Write([]string{c.ID, c.TargetType, c.Comment, optionalString})
+		err := output.Write([]string{c.ID, c.TargetType, c.Comment, optionalString})
+		if err != nil {
+			return nil
+		}
 	}
 	output.Flush()
+
+	return nil
 }
 
 func listToStructMap(items *[]string) map[string]struct{} {
diff --git a/go.mod b/go.mod
index 1e6889e..c191c50 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ require (
 	github.com/fatih/color v1.13.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.7.1
-	golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f
+	golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	k8s.io/api v0.23.5
 	k8s.io/apimachinery v0.23.5
diff --git a/go.sum b/go.sum
index e14ba31..c345110 100644
--- a/go.sum
+++ b/go.sum
@@ -120,8 +120,6 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
-golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -156,7 +154,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -170,7 +167,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/parser/error.go b/parser/error.go
index 47b4f09..5f1938e 100644
--- a/parser/error.go
+++ b/parser/error.go
@@ -1,26 +1,25 @@
 package parser
 
 import (
-	"fmt"
 	"strings"
 )
 
-type parseError []error
+type parseErrors []error
 
-func (p parseError) Error() string {
+func (p parseErrors) Error() string {
 	var s []string
 	for _, e := range p {
 		s = append(s, e.Error())
 	}
-	return fmt.Sprintf(strings.Join(s, "\n"))
+	return strings.Join(s, "\n")
 }
 
-func (p *parseError) AddIfErr(err error) {
+func (p *parseErrors) AddIfErr(err error) {
 	if err != nil {
 		*p = append(*p, err)
 	}
 }
 
-func (p parseError) Any() bool {
+func (p parseErrors) Any() bool {
 	return len(p) > 0
 }
diff --git a/parser/parse.go b/parser/parse.go
index 8af3b50..ad561fc 100644
--- a/parser/parse.go
+++ b/parser/parse.go
@@ -40,20 +40,63 @@ var scheme = runtime.NewScheme()
 var codecs = serializer.NewCodecFactory(scheme)
 
 func init() {
-	addToScheme(scheme)
+	if err := addToScheme(scheme); err != nil {
+		panic(err)
+	}
 }
 
-func addToScheme(scheme *runtime.Scheme) {
-	corev1.AddToScheme(scheme)
-	appsv1.AddToScheme(scheme)
-	networkingv1.AddToScheme(scheme)
-	extensionsv1beta1.AddToScheme(scheme)
-	appsv1beta1.AddToScheme(scheme)
-	appsv1beta2.AddToScheme(scheme)
-	batchv1.AddToScheme(scheme)
-	batchv1beta1.AddToScheme(scheme)
-	policyv1beta1.AddToScheme(scheme)
-	policyv1.AddToScheme(scheme)
+func addToScheme(scheme *runtime.Scheme) error {
+	err := corev1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = appsv1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = networkingv1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = extensionsv1beta1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = appsv1beta1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = appsv1beta2.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = batchv1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = batchv1beta1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = policyv1beta1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	err = policyv1.AddToScheme(scheme)
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 type detectKind struct {
@@ -222,44 +265,44 @@ func detectFileLocation(fileName string, fileOffset int, fileContents []byte) ks
 func decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion schema.GroupVersionKind, fileName string, fileOffset int, fileContents []byte) error {
 	addPodSpeccer := func(ps ks.PodSpecer) {
 		s.podspecers = append(s.podspecers, ps)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{ps.GetTypeMeta(), ps.GetObjectMeta(), ps})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ps.GetTypeMeta(), ObjectMeta: ps.GetObjectMeta(), FileLocationer: ps})
 	}
 
 	fileLocation := detectFileLocation(fileName, fileOffset, fileContents)
 
-	var errs parseError
+	var errs parseErrors
 
 	switch detectedVersion {
 	case corev1.SchemeGroupVersion.WithKind("Pod"):
 		var pod corev1.Pod
 		errs.AddIfErr(decode(fileContents, &pod))
-		p := internalpod.Pod{pod, fileLocation}
+		p := internalpod.Pod{Obj: pod, Location: fileLocation}
 		s.pods = append(s.pods, p)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{pod.TypeMeta, pod.ObjectMeta, p})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: pod.TypeMeta, ObjectMeta: pod.ObjectMeta, FileLocationer: p})
 
 	case batchv1.SchemeGroupVersion.WithKind("Job"):
 		var job batchv1.Job
 		errs.AddIfErr(decode(fileContents, &job))
-		addPodSpeccer(internal.Batchv1Job{job, fileLocation})
+		addPodSpeccer(internal.Batchv1Job{Job: job, Location: fileLocation})
 
 	case batchv1beta1.SchemeGroupVersion.WithKind("CronJob"):
 		var cronjob batchv1beta1.CronJob
 		errs.AddIfErr(decode(fileContents, &cronjob))
-		cjob := internalcronjob.CronJobV1beta1{cronjob, fileLocation}
+		cjob := internalcronjob.CronJobV1beta1{Obj: cronjob, Location: fileLocation}
 		addPodSpeccer(cjob)
 		s.cronjobs = append(s.cronjobs, cjob)
 
 	case batchv1.SchemeGroupVersion.WithKind("CronJob"):
 		var cronjob batchv1.CronJob
 		errs.AddIfErr(decode(fileContents, &cronjob))
-		cjob := internalcronjob.CronJobV1{cronjob, fileLocation}
+		cjob := internalcronjob.CronJobV1{Obj: cronjob, Location: fileLocation}
 		addPodSpeccer(cjob)
 		s.cronjobs = append(s.cronjobs, cjob)
 
 	case appsv1.SchemeGroupVersion.WithKind("Deployment"):
 		var deployment appsv1.Deployment
 		errs.AddIfErr(decode(fileContents, &deployment))
-		deploy := internal.Appsv1Deployment{deployment, fileLocation}
+		deploy := internal.Appsv1Deployment{Obj: deployment, Location: fileLocation}
 		addPodSpeccer(deploy)
 
 		// TODO: Support older versions of Deployment as well?
@@ -267,20 +310,20 @@ func decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion sche
 	case appsv1beta1.SchemeGroupVersion.WithKind("Deployment"):
 		var deployment appsv1beta1.Deployment
 		errs.AddIfErr(decode(fileContents, &deployment))
-		addPodSpeccer(internal.Appsv1beta1Deployment{deployment, fileLocation})
+		addPodSpeccer(internal.Appsv1beta1Deployment{Deployment: deployment, Location: fileLocation})
 	case appsv1beta2.SchemeGroupVersion.WithKind("Deployment"):
 		var deployment appsv1beta2.Deployment
 		errs.AddIfErr(decode(fileContents, &deployment))
-		addPodSpeccer(internal.Appsv1beta2Deployment{deployment, fileLocation})
+		addPodSpeccer(internal.Appsv1beta2Deployment{Deployment: deployment, Location: fileLocation})
 	case extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment"):
 		var deployment extensionsv1beta1.Deployment
 		errs.AddIfErr(decode(fileContents, &deployment))
-		addPodSpeccer(internal.Extensionsv1beta1Deployment{deployment, fileLocation})
+		addPodSpeccer(internal.Extensionsv1beta1Deployment{Deployment: deployment, Location: fileLocation})
 
 	case appsv1.SchemeGroupVersion.WithKind("StatefulSet"):
 		var statefulSet appsv1.StatefulSet
 		errs.AddIfErr(decode(fileContents, &statefulSet))
-		sset := internal.Appsv1StatefulSet{statefulSet, fileLocation}
+		sset := internal.Appsv1StatefulSet{Obj: statefulSet, Location: fileLocation}
 		addPodSpeccer(sset)
 
 		// TODO: Support older versions of StatefulSet as well?
@@ -288,93 +331,101 @@ func decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion sche
 	case appsv1beta1.SchemeGroupVersion.WithKind("StatefulSet"):
 		var statefulSet appsv1beta1.StatefulSet
 		errs.AddIfErr(decode(fileContents, &statefulSet))
-		addPodSpeccer(internal.Appsv1beta1StatefulSet{statefulSet, fileLocation})
+		addPodSpeccer(internal.Appsv1beta1StatefulSet{StatefulSet: statefulSet, Location: fileLocation})
 	case appsv1beta2.SchemeGroupVersion.WithKind("StatefulSet"):
 		var statefulSet appsv1beta2.StatefulSet
 		errs.AddIfErr(decode(fileContents, &statefulSet))
-		addPodSpeccer(internal.Appsv1beta2StatefulSet{statefulSet, fileLocation})
+		addPodSpeccer(internal.Appsv1beta2StatefulSet{StatefulSet: statefulSet, Location: fileLocation})
 
 	case appsv1.SchemeGroupVersion.WithKind("DaemonSet"):
 		var daemonset appsv1.DaemonSet
 		errs.AddIfErr(decode(fileContents, &daemonset))
-		addPodSpeccer(internal.Appsv1DaemonSet{daemonset, fileLocation})
+		addPodSpeccer(internal.Appsv1DaemonSet{DaemonSet: daemonset, Location: fileLocation})
 	case appsv1beta2.SchemeGroupVersion.WithKind("DaemonSet"):
 		var daemonset appsv1beta2.DaemonSet
 		errs.AddIfErr(decode(fileContents, &daemonset))
-		addPodSpeccer(internal.Appsv1beta2DaemonSet{daemonset, fileLocation})
+		addPodSpeccer(internal.Appsv1beta2DaemonSet{DaemonSet: daemonset, Location: fileLocation})
 	case extensionsv1beta1.SchemeGroupVersion.WithKind("DaemonSet"):
 		var daemonset extensionsv1beta1.DaemonSet
 		errs.AddIfErr(decode(fileContents, &daemonset))
-		addPodSpeccer(internal.Extensionsv1beta1DaemonSet{daemonset, fileLocation})
+		addPodSpeccer(internal.Extensionsv1beta1DaemonSet{DaemonSet: daemonset, Location: fileLocation})
 
 	case networkingv1.SchemeGroupVersion.WithKind("NetworkPolicy"):
 		var netpol networkingv1.NetworkPolicy
 		errs.AddIfErr(decode(fileContents, &netpol))
-		np := internalnetpol.NetworkPolicy{netpol, fileLocation}
+		np := internalnetpol.NetworkPolicy{Obj: netpol, Location: fileLocation}
 		s.networkPolicies = append(s.networkPolicies, np)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{netpol.TypeMeta, netpol.ObjectMeta, np})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: netpol.TypeMeta, ObjectMeta: netpol.ObjectMeta, FileLocationer: np})
 
 	case corev1.SchemeGroupVersion.WithKind("Service"):
 		var service corev1.Service
 		errs.AddIfErr(decode(fileContents, &service))
-		serv := internalservice.Service{service, fileLocation}
+		serv := internalservice.Service{Obj: service, Location: fileLocation}
 		s.services = append(s.services, serv)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{service.TypeMeta, service.ObjectMeta, serv})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: service.TypeMeta, ObjectMeta: service.ObjectMeta, FileLocationer: serv})
 
 	case policyv1beta1.SchemeGroupVersion.WithKind("PodDisruptionBudget"):
 		var disruptBudget policyv1beta1.PodDisruptionBudget
 		errs.AddIfErr(decode(fileContents, &disruptBudget))
-		dbug := internalpdb.PodDisruptionBudgetV1beta1{disruptBudget, fileLocation}
+		dbug := internalpdb.PodDisruptionBudgetV1beta1{Obj: disruptBudget, Location: fileLocation}
 		s.podDisruptionBudgets = append(s.podDisruptionBudgets, dbug)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{disruptBudget.TypeMeta, disruptBudget.ObjectMeta, dbug})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: disruptBudget.TypeMeta, ObjectMeta: disruptBudget.ObjectMeta, FileLocationer: dbug})
 	case policyv1.SchemeGroupVersion.WithKind("PodDisruptionBudget"):
 		var disruptBudget policyv1.PodDisruptionBudget
 		errs.AddIfErr(decode(fileContents, &disruptBudget))
-		dbug := internalpdb.PodDisruptionBudgetV1{disruptBudget, fileLocation}
+		dbug := internalpdb.PodDisruptionBudgetV1{Obj: disruptBudget, Location: fileLocation}
 		s.podDisruptionBudgets = append(s.podDisruptionBudgets, dbug)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{disruptBudget.TypeMeta, disruptBudget.ObjectMeta, dbug})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{
+			TypeMeta:       disruptBudget.TypeMeta,
+			ObjectMeta:     disruptBudget.ObjectMeta,
+			FileLocationer: dbug,
+		})
 
 	case extensionsv1beta1.SchemeGroupVersion.WithKind("Ingress"):
 		var ingress extensionsv1beta1.Ingress
 		errs.AddIfErr(decode(fileContents, &ingress))
-		ing := internal.ExtensionsIngressV1beta1{ingress, fileLocation}
+		ing := internal.ExtensionsIngressV1beta1{Ingress: ingress, Location: fileLocation}
 		s.ingresses = append(s.ingresses, ing)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{ingress.TypeMeta, ingress.ObjectMeta, ing})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
 
 	case networkingv1beta1.SchemeGroupVersion.WithKind("Ingress"):
 		var ingress networkingv1beta1.Ingress
 		errs.AddIfErr(decode(fileContents, &ingress))
-		ing := internal.IngressV1beta1{ingress, fileLocation}
+		ing := internal.IngressV1beta1{Ingress: ingress, Location: fileLocation}
 		s.ingresses = append(s.ingresses, ing)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{ingress.TypeMeta, ingress.ObjectMeta, ing})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
 
 	case networkingv1.SchemeGroupVersion.WithKind("Ingress"):
 		var ingress networkingv1.Ingress
 		errs.AddIfErr(decode(fileContents, &ingress))
-		ing := internal.IngressV1{ingress, fileLocation}
+		ing := internal.IngressV1{Ingress: ingress, Location: fileLocation}
 		s.ingresses = append(s.ingresses, ing)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{ingress.TypeMeta, ingress.ObjectMeta, ing})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
 
 	case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
 		var hpa autoscalingv1.HorizontalPodAutoscaler
 		errs.AddIfErr(decode(fileContents, &hpa))
-		h := internal.HPAv1{hpa, fileLocation}
+		h := internal.HPAv1{HorizontalPodAutoscaler: hpa, Location: fileLocation}
 		s.hpaTargeters = append(s.hpaTargeters, h)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{hpa.TypeMeta, hpa.ObjectMeta, h})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h})
 
 	case autoscalingv2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
 		var hpa autoscalingv2beta1.HorizontalPodAutoscaler
 		errs.AddIfErr(decode(fileContents, &hpa))
-		h := internal.HPAv2beta1{hpa, fileLocation}
+		h := internal.HPAv2beta1{HorizontalPodAutoscaler: hpa, Location: fileLocation}
 		s.hpaTargeters = append(s.hpaTargeters, h)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{hpa.TypeMeta, hpa.ObjectMeta, h})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h})
 
 	case autoscalingv2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
 		var hpa autoscalingv2beta2.HorizontalPodAutoscaler
 		errs.AddIfErr(decode(fileContents, &hpa))
-		h := internal.HPAv2beta2{hpa, fileLocation}
+		h := internal.HPAv2beta2{HorizontalPodAutoscaler: hpa, Location: fileLocation}
 		s.hpaTargeters = append(s.hpaTargeters, h)
-		s.bothMetas = append(s.bothMetas, ks.BothMeta{hpa.TypeMeta, hpa.ObjectMeta, h})
+		s.bothMetas = append(s.bothMetas, ks.BothMeta{
+			TypeMeta:       hpa.TypeMeta,
+			ObjectMeta:     hpa.ObjectMeta,
+			FileLocationer: h,
+		})
 
 	default:
 		if cnf.VerboseOutput > 1 {
diff --git a/renderer/human/human.go b/renderer/human/human.go
index 7011317..480ae0f 100644
--- a/renderer/human/human.go
+++ b/renderer/human/human.go
@@ -15,7 +15,7 @@ import (
 	"github.com/zegl/kube-score/scorecard"
 )
 
-func Human(scoreCard *scorecard.Scorecard, verboseOutput int, termWidth int) io.Reader {
+func Human(scoreCard *scorecard.Scorecard, verboseOutput int, termWidth int) (io.Reader, error) {
 	// Print the items sorted by scorecard key
 	var keys []string
 	for k := range *scoreCard {
@@ -37,7 +37,7 @@ func Human(scoreCard *scorecard.Scorecard, verboseOutput int, termWidth int) io.
 		}
 
 		// Adjust to termsize
-		fmt.Fprintf(w, safeRepeat(" ", min(80, termWidth)-writtenHeaderChars-2))
+		fmt.Fprint(w, safeRepeat(" ", min(80, termWidth)-writtenHeaderChars-2))
 
 		switch {
 		case scoredObject.AnyBelowOrEqualToGrade(scorecard.GradeCritical):
@@ -50,11 +50,15 @@ func Human(scoreCard *scorecard.Scorecard, verboseOutput int, termWidth int) io.
 
 		for _, card := range scoredObject.Checks {
 			r := outputHumanStep(card, verboseOutput, termWidth)
-			io.Copy(w, r)
+			_, err := io.Copy(w, r)
+			if err != nil {
+				return nil, err
+			}
+
 		}
 	}
 
-	return w
+	return w, nil
 }
 
 func outputHumanStep(card scorecard.TestScore, verboseOutput int, termWidth int) io.Reader {
@@ -108,7 +112,7 @@ func outputHumanStep(card scorecard.TestScore, verboseOutput int, termWidth int)
 			wrapper := wordwrap.Wrapper(wrapWidth, false)
 			wrapped := wrapper(comment.Description)
 			fmt.Fprintln(w)
-			fmt.Fprintf(w, wordwrap.Indent(wrapped, strings.Repeat(" ", 12), false))
+			fmt.Fprint(w, wordwrap.Indent(wrapped, strings.Repeat(" ", 12), false))
 		}
 
 		if len(comment.DocumentationURL) > 0 {
diff --git a/score/stable/stable_version.go b/score/stable/stable_version.go
index 3d2cec6..78c4610 100644
--- a/score/stable/stable_version.go
+++ b/score/stable/stable_version.go
@@ -23,29 +23,29 @@ func metaStableAvailable(kubernetsVersion config.Semver) func(meta domain.BothMe
 
 		withStable := map[string]map[string]recommendedApi{
 			"extensions/v1beta1": {
-				"Deployment":   recommendedApi{"apps/v1", config.Semver{1, 9}},
-				"DaemonSet":    recommendedApi{"apps/v1", config.Semver{1, 9}},
-				"Ingress":      recommendedApi{"networking.k8s.io/v1", config.Semver{1, 19}},
-				"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{1, 19}},
+				"Deployment":   recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
+				"DaemonSet":    recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
+				"Ingress":      recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
+				"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
 			},
 			"apps/v1beta1": {
-				"Deployment":  recommendedApi{"apps/v1", config.Semver{1, 9}},
-				"StatefulSet": recommendedApi{"apps/v1", config.Semver{1, 9}},
+				"Deployment":  recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
+				"StatefulSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
 			},
 			"apps/v1beta2": {
-				"Deployment":  recommendedApi{"apps/v1", config.Semver{1, 9}},
-				"StatefulSet": recommendedApi{"apps/v1", config.Semver{1, 9}},
-				"DaemonSet":   recommendedApi{"apps/v1", config.Semver{1, 9}},
+				"Deployment":  recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
+				"StatefulSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
+				"DaemonSet":   recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
 			},
 			"batch/v1beta1": {
-				"CronJob": recommendedApi{"batch/v1", config.Semver{1, 21}},
+				"CronJob": recommendedApi{"batch/v1", config.Semver{Major: 1, Minor: 21}},
 			},
 			"policy/v1beta1": {
-				"PodDisruptionBudget": recommendedApi{"policy/v1", config.Semver{1, 21}},
+				"PodDisruptionBudget": recommendedApi{"policy/v1", config.Semver{Major: 1, Minor: 21}},
 			},
 			"networking.k8s.io/v1beta1": {
-				"Ingress":      recommendedApi{"networking.k8s.io/v1", config.Semver{1, 19}},
-				"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{1, 19}},
+				"Ingress":      recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
+				"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
 			},
 		}
 


Test patch:
diff --git a/renderer/human/output_test.go b/renderer/human/output_test.go
index 267fe0a..2addec5 100644
--- a/renderer/human/output_test.go
+++ b/renderer/human/output_test.go
@@ -95,7 +95,8 @@ func getTestCard() *scorecard.Scorecard {
 
 func TestHumanOutputDefault(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCard(), 0, 100)
+	r, err := Human(getTestCard(), 0, 100)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -117,7 +118,8 @@ v1/Testing bar-no-namespace                                                   
 
 func TestHumanOutputVerbose1(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCard(), 1, 100)
+	r, err := Human(getTestCard(), 1, 100)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -145,7 +147,8 @@ v1/Testing bar-no-namespace                                                   
 
 func TestHumanOutputVerbose2(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCard(), 2, 100)
+	r, err := Human(getTestCard(), 2, 100)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -262,7 +265,8 @@ func getTestCardAllOK() *scorecard.Scorecard {
 
 func TestHumanOutputAllOKDefault(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardAllOK(), 0, 100)
+	r, err := Human(getTestCardAllOK(), 0, 100)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      ✅
@@ -304,7 +308,8 @@ func getTestCardLongDescription() *scorecard.Scorecard {
 
 func TestHumanOutputLogDescription120Width(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardLongDescription(), 0, 120)
+	r, err := Human(getTestCardLongDescription(), 0, 120)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -319,7 +324,8 @@ func TestHumanOutputLogDescription120Width(t *testing.T) {
 
 func TestHumanOutputLogDescription100Width(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardLongDescription(), 0, 100)
+	r, err := Human(getTestCardLongDescription(), 0, 100)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -335,7 +341,8 @@ func TestHumanOutputLogDescription100Width(t *testing.T) {
 
 func TestHumanOutputLogDescription80Width(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardLongDescription(), 0, 80)
+	r, err := Human(getTestCardLongDescription(), 0, 80)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo                                                      🤔
@@ -352,7 +359,8 @@ func TestHumanOutputLogDescription80Width(t *testing.T) {
 
 func TestHumanOutputLogDescription0Width(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardLongDescription(), 0, 0)
+	r, err := Human(getTestCardLongDescription(), 0, 0)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing foo in foofoo🤔
@@ -405,7 +413,8 @@ func getTestCardLongTitle() *scorecard.Scorecard {
 
 func TestHumanOutputWithLongObjectNames(t *testing.T) {
 	t.Parallel()
-	r := Human(getTestCardLongTitle(), 0, 80)
+	r, err := Human(getTestCardLongTitle(), 0, 80)
+	assert.Nil(t, err)
 	all, err := ioutil.ReadAll(r)
 	assert.Nil(t, err)
 	assert.Equal(t, `v1/Testing this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title in foofoo🤔
diff --git a/score/filelocation_test.go b/score/filelocation_test.go
index eb974f3..b5bee06 100644
--- a/score/filelocation_test.go
+++ b/score/filelocation_test.go
@@ -12,7 +12,7 @@ import (
 func TestFileLocationHelm(t *testing.T) {
 	sc, err := testScore(config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile("linenumbers-helm.yaml")},
-		KubernetesVersion: config.Semver{1, 18},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 18},
 	})
 	assert.Nil(t, err)
 	for _, c := range sc {
@@ -25,7 +25,7 @@ func TestFileLocationHelm(t *testing.T) {
 func TestFileLocation(t *testing.T) {
 	sc, err := testScore(config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile("linenumbers.yaml")},
-		KubernetesVersion: config.Semver{1, 18},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 18},
 	})
 	assert.Nil(t, err)
 	for _, c := range sc {
diff --git a/score/score_test.go b/score/score_test.go
index 8282b02..be5c570 100644
--- a/score/score_test.go
+++ b/score/score_test.go
@@ -55,7 +55,7 @@ func testScore(config config.Configuration) (scorecard.Scorecard, error) {
 func testExpectedScore(t *testing.T, filename string, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment {
 	return testExpectedScoreWithConfig(t, config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile(filename)},
-		KubernetesVersion: config.Semver{1, 18},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 18},
 	}, testcase, expectedScore)
 }
 
diff --git a/score/stable/stable_version_test.go b/score/stable/stable_version_test.go
index 12fb200..b195aa9 100644
--- a/score/stable/stable_version_test.go
+++ b/score/stable/stable_version_test.go
@@ -12,35 +12,35 @@ import (
 )
 
 func TestStableVersionOldKubernetesVersion(t *testing.T) {
-	oldKubernetes := metaStableAvailable(config.Semver{1, 4})
+	oldKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 4})
 	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{1, 18})
+	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 18})
 	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{1, 20})
+	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 20})
 	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{1, 21})
+	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
 	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{1, 21})
+	newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
 	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)
diff --git a/score/stable_version_test.go b/score/stable_version_test.go
index 21c50ae..6a47233 100644
--- a/score/stable_version_test.go
+++ b/score/stable_version_test.go
@@ -17,7 +17,7 @@ func TestStatefulSetAppsv1beta1Kubernetes1dot4(t *testing.T) {
 	t.Parallel()
 	testExpectedScoreWithConfig(t, config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")},
-		KubernetesVersion: config.Semver{1, 4},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 4},
 	}, "Stable version", scorecard.GradeAllOK)
 }
 
@@ -25,7 +25,7 @@ func TestStatefulSetAppsv1beta1Kubernetes1dot18(t *testing.T) {
 	t.Parallel()
 	testExpectedScoreWithConfig(t, config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")},
-		KubernetesVersion: config.Semver{1, 18},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 18},
 	}, "Stable version", scorecard.GradeWarning)
 }
 
@@ -73,7 +73,7 @@ func TestCronJobBatchv1beta1Kubernetes1dot21(t *testing.T) {
 	t.Parallel()
 	testExpectedScoreWithConfig(t, config.Configuration{
 		AllFiles:          []ks.NamedReader{testFile("cronjob-batchv1beta1.yaml")},
-		KubernetesVersion: config.Semver{1, 21},
+		KubernetesVersion: config.Semver{Major: 1, Minor: 21},
 	}, "Stable version", scorecard.GradeWarning)
 }