Repository: zegl/kube-score
Pull request #605 (2024-07-05 11:02:18)

build(deps): bump golang.org/x/term from 0.21.0 to 0.22.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.21.0 to 0.22.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/golang/term/commit/c976cb1d70cf59933953bec8a47c867e343c9eac"><code>c976cb1</code></a> go.mod: update golang.org/x dependencies</li>
<li>See full diff in <a href="https://github.com/golang/term/compare/v0.21.0...v0.22.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/term&package-manager=go_modules&previous-version=0.21.0&new-version=0.22.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go
index 2976595..7fbc900 100644
--- a/cmd/kube-score/main.go
+++ b/cmd/kube-score/main.go
@@ -18,6 +18,7 @@ import (
 	"github.com/zegl/kube-score/renderer/ci"
 	"github.com/zegl/kube-score/renderer/human"
 	"github.com/zegl/kube-score/renderer/json_v2"
+	"github.com/zegl/kube-score/renderer/junit"
 	"github.com/zegl/kube-score/renderer/sarif"
 	"github.com/zegl/kube-score/score"
 	"github.com/zegl/kube-score/score/checks"
@@ -125,9 +126,9 @@ func scoreFiles(binName string, args []string) error {
 		return nil
 	}
 
-	if *outputFormat != "human" && *outputFormat != "ci" && *outputFormat != "json" && *outputFormat != "sarif" {
+	if *outputFormat != "human" && *outputFormat != "ci" && *outputFormat != "json" && *outputFormat != "sarif" && *outputFormat != "junit" {
 		fs.Usage()
-		return fmt.Errorf("Error: --output-format must be set to: 'human', 'json', 'sarif' or 'ci'")
+		return fmt.Errorf("Error: --output-format must be set to: 'human', 'json', 'sarif', 'junit' or 'ci'")
 	}
 
 	acceptedColors := map[string]bool{
@@ -255,6 +256,8 @@ Use "-" as filename to read from STDIN.`, execName(binName))
 		r = ci.CI(scoreCard)
 	case *outputFormat == "sarif":
 		r = sarif.Output(scoreCard)
+	case *outputFormat == "junit":
+		r = junit.JUnit(scoreCard)
 	default:
 		return fmt.Errorf("error: Unknown --output-format or --output-version")
 	}
diff --git a/go.mod b/go.mod
index 4122b8e..f8481ce 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,11 @@ require (
 	github.com/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb
 	github.com/fatih/color v1.17.0
 	github.com/google/go-cmp v0.6.0
+	github.com/jstemmer/go-junit-report/v2 v2.1.0
 	github.com/mattn/go-isatty v0.0.20
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.9.0
-	golang.org/x/term v0.21.0
+	golang.org/x/term v0.22.0
 	gopkg.in/yaml.v3 v3.0.1
 	k8s.io/api v0.30.2
 	k8s.io/apimachinery v0.30.2
@@ -25,7 +26,7 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	golang.org/x/net v0.23.0 // indirect
-	golang.org/x/sys v0.21.0 // indirect
+	golang.org/x/sys v0.22.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 8f50cfb..c13b84f 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,7 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -17,6 +18,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
+github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -64,10 +67,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
-golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
diff --git a/renderer/junit/junit.go b/renderer/junit/junit.go
new file mode 100644
index 0000000..89ca2c0
--- /dev/null
+++ b/renderer/junit/junit.go
@@ -0,0 +1,88 @@
+package junit
+
+import (
+	"bytes"
+	"io"
+
+	"github.com/jstemmer/go-junit-report/v2/junit"
+	"github.com/zegl/kube-score/scorecard"
+)
+
+// JUnit XML output
+func JUnit(scoreCard *scorecard.Scorecard) io.Reader {
+	testSuites := junit.Testsuites{
+		Name: "kube-score",
+	}
+
+	for _, scoredObject := range *scoreCard {
+		testsuite := junit.Testsuite{
+			Name: scoredObject.HumanFriendlyRef(),
+		}
+
+		for _, testScore := range scoredObject.Checks {
+			if len(testScore.Comments) == 0 {
+				if testScore.Skipped {
+					testsuite.AddTestcase(junit.Testcase{
+						Name:      testScore.Check.Name,
+						Classname: scoredObject.HumanFriendlyRef(),
+						Skipped:   &junit.Result{},
+					})
+				} else {
+					if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
+						testsuite.AddTestcase(junit.Testcase{
+							Name:      testScore.Check.Name,
+							Classname: scoredObject.HumanFriendlyRef(),
+						})
+					} else {
+						testsuite.AddTestcase(junit.Testcase{
+							Name:      testScore.Check.Name,
+							Classname: scoredObject.HumanFriendlyRef(),
+							Failure:   &junit.Result{},
+						})
+					}
+				}
+			} else {
+				for _, comment := range testScore.Comments {
+					message := comment.Summary
+					if comment.Path != "" {
+						message = "(" + comment.Path + ") " + comment.Summary
+					}
+
+					if testScore.Skipped {
+						testsuite.AddTestcase(junit.Testcase{
+							Name:      testScore.Check.Name,
+							Classname: scoredObject.HumanFriendlyRef(),
+							Skipped: &junit.Result{
+								Message: message,
+							},
+						})
+					} else {
+						if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
+							testsuite.AddTestcase(junit.Testcase{
+								Name:      testScore.Check.Name,
+								Classname: scoredObject.HumanFriendlyRef(),
+							})
+						} else {
+							testsuite.AddTestcase(junit.Testcase{
+								Name:      testScore.Check.Name,
+								Classname: scoredObject.HumanFriendlyRef(),
+								Failure: &junit.Result{
+									Message: message,
+								},
+							})
+						}
+
+					}
+				}
+			}
+		}
+		testSuites.AddSuite(testsuite)
+	}
+
+	buffer := &bytes.Buffer{}
+	err := testSuites.WriteXML(buffer)
+	if err != nil {
+		panic(err)
+	}
+	return buffer
+}


Test patch:
diff --git a/renderer/junit/junit_test.go b/renderer/junit/junit_test.go
new file mode 100644
index 0000000..54daff3
--- /dev/null
+++ b/renderer/junit/junit_test.go
@@ -0,0 +1,132 @@
+package junit
+
+import (
+	"io"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/zegl/kube-score/domain"
+	"github.com/zegl/kube-score/scorecard"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func getTestCard() *scorecard.Scorecard {
+	checks := []scorecard.TestScore{
+		{
+			Check: domain.Check{
+				Name: "test-warning-two-comments",
+			},
+			Grade: scorecard.GradeWarning,
+			Comments: []scorecard.TestScoreComment{
+				{
+					Path:        "a",
+					Summary:     "summary",
+					Description: "description",
+				},
+				{
+					// No path
+					Summary:     "summary",
+					Description: "description",
+				},
+			},
+		},
+		{
+			Check: domain.Check{
+				Name: "test-ok-comment",
+			},
+			Grade: scorecard.GradeAllOK,
+			Comments: []scorecard.TestScoreComment{
+				{
+					Path:        "a",
+					Summary:     "summary",
+					Description: "description",
+				},
+			},
+		},
+		{
+			Check: domain.Check{
+				Name: "test-skipped-comment",
+			},
+			Skipped: true,
+			Comments: []scorecard.TestScoreComment{
+				{
+					Path:        "a",
+					Summary:     "skipped sum",
+					Description: "skipped description",
+				},
+			},
+		},
+		{
+			Check: domain.Check{
+				Name: "test-skipped-no-comment",
+			},
+			Skipped: true,
+		},
+	}
+
+	return &scorecard.Scorecard{
+		"a": &scorecard.ScoredObject{
+			TypeMeta: v1.TypeMeta{
+				Kind:       "Testing",
+				APIVersion: "v1",
+			},
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foofoo",
+			},
+			Checks: checks,
+		},
+
+		// No namespace
+		"b": &scorecard.ScoredObject{
+			TypeMeta: v1.TypeMeta{
+				Kind:       "Testing",
+				APIVersion: "v1",
+			},
+			ObjectMeta: v1.ObjectMeta{
+				Name: "bar-no-namespace",
+			},
+			Checks: checks,
+		},
+	}
+}
+
+func TestJUnitOutput(t *testing.T) {
+	t.Parallel()
+	r := JUnit(getTestCard())
+	all, err := io.ReadAll(r)
+	assert.Nil(t, err)
+	assert.Equal(t, `<testsuites name="kube-score" tests="10" failures="4" skipped="4">
+	<testsuite name="foo/foofoo v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
+		<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
+			<failure message="(a) summary"></failure>
+		</testcase>
+		<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
+			<failure message="summary"></failure>
+		</testcase>
+		<testcase name="test-ok-comment" classname="foo/foofoo v1/Testing"></testcase>
+		<testcase name="test-skipped-comment" classname="foo/foofoo v1/Testing">
+			<skipped message="(a) skipped sum"></skipped>
+		</testcase>
+		<testcase name="test-skipped-no-comment" classname="foo/foofoo v1/Testing">
+			<skipped message=""></skipped>
+		</testcase>
+	</testsuite>
+	<testsuite name="bar-no-namespace v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
+		<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
+			<failure message="(a) summary"></failure>
+		</testcase>
+		<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
+			<failure message="summary"></failure>
+		</testcase>
+		<testcase name="test-ok-comment" classname="bar-no-namespace v1/Testing"></testcase>
+		<testcase name="test-skipped-comment" classname="bar-no-namespace v1/Testing">
+			<skipped message="(a) skipped sum"></skipped>
+		</testcase>
+		<testcase name="test-skipped-no-comment" classname="bar-no-namespace v1/Testing">
+			<skipped message=""></skipped>
+		</testcase>
+	</testsuite>
+</testsuites>
+`, string(all))
+}