Skip to content

Commit 5585270

Browse files
authored
Merge pull request #1631 from simonbaird/plain-text-output
Human readable output
2 parents 40b4b0f + 1f08226 commit 5585270

File tree

14 files changed

+328
-2
lines changed

14 files changed

+328
-2
lines changed

cmd/validate/image.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
6969
spec *app.SnapshotSpec
7070
strict bool
7171
images string
72+
noColor bool
73+
forceColor bool
7274
}{
7375
strict: true,
7476
}
@@ -402,6 +404,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
402404
return err
403405
}
404406
p := format.NewTargetParser(applicationsnapshot.JSON, cmd.OutOrStdout(), utils.FS(cmd.Context()))
407+
utils.SetColorEnabled(data.noColor, data.forceColor)
405408
if err := report.WriteAll(data.output, p); err != nil {
406409
return err
407410
}
@@ -487,6 +490,12 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
487490
violations, include the title and the description of the failed policy
488491
rule.`))
489492

493+
cmd.Flags().BoolVar(&data.noColor, "no-color", data.info, hd.Doc(`
494+
Disable color when using text output even when the current terminal supports it`))
495+
496+
cmd.Flags().BoolVar(&data.forceColor, "color", data.info, hd.Doc(`
497+
Enable color when using text output even when the current terminal does not support it`))
498+
490499
if len(data.input) > 0 || len(data.filePath) > 0 || len(data.images) > 0 {
491500
if err := cmd.MarkFlagRequired("image"); err != nil {
492501
panic(err)

docs/modules/ROOT/pages/ec_validate_image.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Use a regular expression to match certificate attributes.
115115
--certificate-identity-regexp:: Regular expression for the URL of the certificate identity for keyless verification
116116
--certificate-oidc-issuer:: URL of the certificate OIDC issuer for keyless verification
117117
--certificate-oidc-issuer-regexp:: Regular expresssion for the URL of the certificate OIDC issuer for keyless verification
118+
--color:: Enable color when using text output even when the current terminal does not support it (Default: false)
118119
--effective-time:: Run policy checks with the provided time. Useful for testing rules with
119120
effective dates in the future. The value can be "now" (default) - for
120121
current time, "attestation" - for time from the youngest attestation, or
@@ -131,9 +132,10 @@ a RFC3339 formatted value, e.g. 2022-11-18T00:00:00Z.
131132
violations, include the title and the description of the failed policy
132133
rule. (Default: false)
133134
-j, --json-input:: DEPRECATED - use --images: JSON representation of an ApplicationSnapshot Spec
135+
--no-color:: Disable color when using text output even when the current terminal supports it (Default: false)
134136
--output:: write output to a file in a specific format. Use empty string path for stdout.
135137
May be used multiple times. Possible formats are:
136-
json, yaml, appstudio, summary, summary-markdown, junit, data, attestation, policy-input, vsa.
138+
json, yaml, text, appstudio, summary, summary-markdown, junit, data, attestation, policy-input, vsa.
137139
(Default: [])
138140
-o, --output-file:: [DEPRECATED] write output to a file. Use empty string for stdout, default behavior
139141
-p, --policy:: Policy configuration as:

features/__snapshots__/validate_image.snap

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,7 +1721,54 @@ Error: success criteria not met
17211721
Error: success criteria not met
17221722

17231723
---
1724+
[detailed failures output:${TMPDIR}/output.txt - 1]
1725+
Name: Unnamed
1726+
ImageRef: ${REGISTRY}/acceptance/image@sha256:${REGISTRY_acceptance/image:latest_DIGEST}
1727+
Summary: Violations: 3, Warnings: 0, Successes: 4
1728+
Violations:
1729+
✕ main.reject_with_term
1730+
Fails always (term1)
1731+
Reject with term rule
1732+
Description: This rule will always fail. To exclude this rule add
1733+
"main.reject_with_term:term1" to the `exclude` section of the policy
1734+
configuration.
1735+
Solution: None
1736+
1737+
✕ main.reject_with_term
1738+
Fails always (term2)
1739+
Reject with term rule
1740+
Description: This rule will always fail. To exclude this rule add
1741+
"main.reject_with_term:term2" to the `exclude` section of the policy
1742+
configuration.
1743+
Solution: None
1744+
1745+
✕ main.rejector
1746+
Fails always
1747+
Reject rule
1748+
Description: This rule will always fail. To exclude this rule add
1749+
"main.rejector" to the `exclude` section of the policy configuration.
1750+
Solution: None
1751+
1752+
Successes:
1753+
✓ builtin.attestation.signature_check
1754+
Attestation signature check passed
1755+
Description: The attestation signature matches available signing materials.
1756+
1757+
✓ builtin.attestation.syntax_check
1758+
Attestation syntax check passed
1759+
Description: The attestation has correct syntax.
1760+
1761+
✓ builtin.image.signature_check
1762+
Image signature check passed
1763+
Description: The image signature matches available signing materials.
1764+
1765+
✓ main.acceptor
1766+
Allow rule
1767+
Description: This rule will never fail
17241768

1769+
1770+
1771+
---
17251772
[future failure is a deny when using effective-date flag:stdout - 1]
17261773
{
17271774
"success": false,

features/validate_image.feature

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,11 @@ Feature: evaluate enterprise contract
377377
]
378378
}
379379
"""
380-
When ec command is run with "validate image --image ${REGISTRY}/acceptance/image --policy acceptance/ec-policy --rekor-url ${REKOR} --public-key ${known_PUBLIC_KEY} --info --show-successes"
380+
When ec command is run with "validate image --image ${REGISTRY}/acceptance/image --policy acceptance/ec-policy --rekor-url ${REKOR} --public-key ${known_PUBLIC_KEY} --info --show-successes --output text=${TMPDIR}/output.txt --color --output json"
381381
Then the exit status should be 1
382382
Then the output should match the snapshot
383+
# Throw in some test coverage for `--output text` here
384+
And the "${TMPDIR}/output.txt" file should match the snapshot
383385

384386
Scenario: policy rule filtering
385387
Given a key pair named "known"

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ require (
221221
github.com/magiconair/properties v1.8.7 // indirect
222222
github.com/mailru/easyjson v0.7.7 // indirect
223223
github.com/maruel/natural v1.1.1 // indirect
224+
github.com/mattn/go-isatty v0.0.20 // indirect
224225
github.com/mattn/go-runewidth v0.0.15 // indirect
225226
github.com/miekg/pkcs11 v1.1.1 // indirect
226227
github.com/mitchellh/go-homedir v1.1.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
944944
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
945945
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
946946
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
947+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
948+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
947949
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
948950
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
949951
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=

internal/applicationsnapshot/report.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package applicationsnapshot
1818

1919
import (
2020
"bytes"
21+
"embed"
22+
_ "embed"
2123
"encoding/json"
2224
"encoding/xml"
2325
"fmt"
@@ -33,6 +35,7 @@ import (
3335
"github.com/enterprise-contract/ec-cli/internal/format"
3436
"github.com/enterprise-contract/ec-cli/internal/policy"
3537
"github.com/enterprise-contract/ec-cli/internal/signature"
38+
"github.com/enterprise-contract/ec-cli/internal/utils"
3639
"github.com/enterprise-contract/ec-cli/internal/version"
3740
)
3841

@@ -96,6 +99,7 @@ type TestReport struct {
9699
const (
97100
JSON = "json"
98101
YAML = "yaml"
102+
Text = "text"
99103
AppStudio = "appstudio"
100104
Summary = "summary"
101105
SummaryMarkdown = "summary-markdown"
@@ -111,6 +115,7 @@ const (
111115
var OutputFormats = []string{
112116
JSON,
113117
YAML,
118+
Text,
114119
AppStudio,
115120
Summary,
116121
SummaryMarkdown,
@@ -188,6 +193,8 @@ func (r *Report) toFormat(format string) (data []byte, err error) {
188193
data, err = json.Marshal(r)
189194
case YAML:
190195
data, err = yaml.Marshal(r)
196+
case Text:
197+
data, err = generateTextReport(r)
191198
case AppStudio, HACBS:
192199
data, err = json.Marshal(r.toAppstudioReport())
193200
case Summary:
@@ -299,6 +306,13 @@ func generateMarkdownSummary(r *Report) ([]byte, error) {
299306
return markdownBuffer.Bytes(), nil
300307
}
301308

309+
//go:embed templates/*.tmpl
310+
var efs embed.FS
311+
312+
func generateTextReport(r *Report) ([]byte, error) {
313+
return utils.RenderFromTemplatesWithMain(r, "text_report.tmpl", efs)
314+
}
315+
302316
func writeMarkdownField(buffer *bytes.Buffer, name string, value any, icon string) {
303317
valueStr := fmt.Sprintf("%v", value)
304318
buffer.WriteString(fmt.Sprintf("| %s | %s | %s |\n", name, valueStr, icon))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{{- $color := .Color -}}
2+
{{- $indent := 2 -}}
3+
{{- $wrap := 78 -}}
4+
{{- range .Results -}}
5+
{{- colorIndicator $color }} {{ colorText $color .Metadata.code }}
6+
{{ if and (ne .Message "Pass") .Message -}}
7+
{{- indentWrap $indent $wrap .Message }}
8+
{{ end -}}
9+
{{ if .Metadata.title -}}
10+
{{- indentWrap $indent $wrap .Metadata.title }}
11+
{{ end -}}
12+
{{ if .Metadata.description -}}
13+
{{- indentWrap $indent $wrap (printf "Description: %s" .Metadata.description) }}
14+
{{ end -}}
15+
{{ if and (ne .Message "Pass") .Metadata.solution -}}
16+
{{- indentWrap $indent $wrap (printf "Solution: %s" .Metadata.solution) }}
17+
{{ end }}
18+
{{ end -}}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{ range .Components -}}
2+
Name: {{ .Name }}
3+
ImageRef: {{ .ContainerImage }}
4+
Summary: Violations: {{ len .Violations }}, Warnings: {{ len .Warnings }}, Successes: {{ .SuccessCount }}
5+
{{ if gt (len .Violations) 0 -}}
6+
Violations:
7+
{{ template "_results.tmpl" (toMap "Results" .Violations "Color" "red") }}
8+
{{- end -}}
9+
{{ if gt (len .Warnings) 0 -}}
10+
Warnings:
11+
{{ template "_results.tmpl" (toMap "Results" .Warnings "Color" "yellow") }}
12+
{{- end -}}
13+
{{ if and (gt .SuccessCount 0) (.Successes) -}}
14+
Successes:
15+
{{ template "_results.tmpl" (toMap "Results" .Successes "Color" "green") }}
16+
{{- end -}}
17+
{{- end }}

internal/utils/helpers.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727
"unicode"
2828

29+
isatty "github.com/mattn/go-isatty"
2930
log "github.com/sirupsen/logrus"
3031
"github.com/spf13/afero"
3132
"sigs.k8s.io/yaml"
@@ -155,3 +156,31 @@ func HasSuffix(str string, extensions []string) bool {
155156
func HasJsonOrYamlExt(str string) bool {
156157
return HasSuffix(str, []string{".json", ".yaml", ".yml"})
157158
}
159+
160+
var ColorEnabled bool
161+
162+
func SetColorEnabled(flagNoColor, flagForceColor bool) {
163+
ColorEnabled = setColorEnabled(flagNoColor, flagForceColor)
164+
}
165+
166+
func setColorEnabled(flagNoColor, flagForceColor bool) bool {
167+
if flagNoColor || anyEnvSet([]string{"EC_NO_COLOR", "EC_NO_COLOUR", "NO_COLOR", "NO_COLOUR"}) {
168+
// Force no color
169+
return false
170+
}
171+
if flagForceColor || anyEnvSet([]string{"EC_FORCE_COLOR", "EC_FORCE_COLOUR", "FORCE_COLOR", "FORCE_COLOUR"}) {
172+
// Force color
173+
return true
174+
}
175+
// Use color if we're in a terminal that can presumably display it
176+
return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
177+
}
178+
179+
func anyEnvSet(varNames []string) bool {
180+
for _, varName := range varNames {
181+
if os.Getenv(varName) != "" {
182+
return true
183+
}
184+
}
185+
return false
186+
}

0 commit comments

Comments
 (0)