Skip to content

Commit

Permalink
Add support for "pretty" output (#195)
Browse files Browse the repository at this point in the history
* feat: add pretty output format

---------

Co-authored-by: William Yardley <[email protected]>
  • Loading branch information
yannh and wyardley authored Apr 22, 2023
1 parent 9294e94 commit 8bc9f42
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func FromFlags(progName string, args []string) (Config, string, error) {
flags.BoolVar(&c.Summary, "summary", false, "print a summary at the end (ignored for junit output)")
flags.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently")
flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema or duplicated keys")
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, tap, text")
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, pretty, tap, text")
flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)")
flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure")
flags.StringVar(&c.Cache, "cache", "", "cache schemas downloaded via HTTP to this folder")
Expand Down
2 changes: 2 additions & 0 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ func New(outputFormat string, printSummary, isStdin, verbose bool) (Output, erro
return jsonOutput(w, printSummary, isStdin, verbose), nil
case outputFormat == "junit":
return junitOutput(w, printSummary, isStdin, verbose), nil
case outputFormat == "pretty":
return prettyOutput(w, printSummary, isStdin, verbose), nil
case outputFormat == "tap":
return tapOutput(w, printSummary, isStdin, verbose), nil
case outputFormat == "text":
Expand Down
109 changes: 109 additions & 0 deletions pkg/output/pretty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package output

import (
"fmt"
"io"
"sync"

"github.com/yannh/kubeconform/pkg/validator"
)

type prettyo struct {
sync.Mutex
w io.Writer
withSummary bool
isStdin bool
verbose bool
files map[string]bool
nValid, nInvalid, nErrors, nSkipped int
}

// Text will output the results of the validation as a texto
func prettyOutput(w io.Writer, withSummary, isStdin, verbose bool) Output {
return &prettyo{
w: w,
withSummary: withSummary,
isStdin: isStdin,
verbose: verbose,
files: map[string]bool{},
nValid: 0,
nInvalid: 0,
nErrors: 0,
nSkipped: 0,
}
}

func (o *prettyo) Write(result validator.Result) error {
checkmark := "\u2714"
multiplicationSign := "\u2716"
reset := "\033[0m"
cRed := "\033[31m"
cGreen := "\033[32m"
cYellow := "\033[33m"

o.Lock()
defer o.Unlock()

var err error

sig, _ := result.Resource.Signature()

o.files[result.Resource.Path] = true
switch result.Status {
case validator.Valid:
if o.verbose {
fmt.Fprintf(o.w, "%s%s%s %s: %s%s %s is valid%s\n", cGreen, checkmark, reset, result.Resource.Path, cGreen, sig.Kind, sig.Name, reset)
}
o.nValid++
case validator.Invalid:
fmt.Fprintf(o.w, "%s%s%s %s: %s%s %s is invalid: %s%s\n", cRed, multiplicationSign, reset, result.Resource.Path, cRed, sig.Kind, sig.Name, result.Err.Error(), reset)

o.nInvalid++
case validator.Error:
fmt.Fprintf(o.w, "%s%s%s %s: ", cRed, multiplicationSign, reset, result.Resource.Path)
if sig.Kind != "" && sig.Name != "" {
fmt.Fprintf(o.w, "%s%s failed validation: %s %s%s\n", cRed, sig.Kind, sig.Name, result.Err.Error(), reset)
} else {
fmt.Fprintf(o.w, "%sfailed validation: %s %s%s\n", cRed, sig.Name, result.Err.Error(), reset)
}
o.nErrors++
case validator.Skipped:
if o.verbose {
fmt.Fprintf(o.w, "%s-%s %s: ", cYellow, reset, result.Resource.Path)
if sig.Kind != "" && sig.Name != "" {
fmt.Fprintf(o.w, "%s%s %s skipped%s\n", cYellow, sig.Kind, sig.Name, reset)
} else if sig.Kind != "" {
fmt.Fprintf(o.w, "%s%s skipped%s\n", cYellow, sig.Kind, reset)
} else {
fmt.Fprintf(o.w, "%sskipped%s\n", cYellow, reset)
}
}
o.nSkipped++
case validator.Empty: // sent to ensure we count the filename as parsed
}

return err
}

func (o *prettyo) Flush() error {
var err error
if o.withSummary {
nFiles := len(o.files)
nResources := o.nValid + o.nInvalid + o.nErrors + o.nSkipped
resourcesPlural := ""
if nResources > 1 {
resourcesPlural = "s"
}
filesPlural := ""
if nFiles > 1 {
filesPlural = "s"
}
if o.isStdin {
_, err = fmt.Fprintf(o.w, "Summary: %d resource%s found parsing stdin - Valid: %d, Invalid: %d, Errors: %d, Skipped: %d\n", nResources, resourcesPlural, o.nValid, o.nInvalid, o.nErrors, o.nSkipped)
} else {
_, err = fmt.Fprintf(o.w, "Summary: %d resource%s found in %d file%s - Valid: %d, Invalid: %d, Errors: %d, Skipped: %d\n", nResources, resourcesPlural, nFiles, filesPlural, o.nValid, o.nInvalid, o.nErrors, o.nSkipped)
}
}

return err
}
84 changes: 84 additions & 0 deletions pkg/output/pretty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package output

import (
"bytes"
"testing"

"github.com/yannh/kubeconform/pkg/resource"
"github.com/yannh/kubeconform/pkg/validator"
)

func TestPrettyTextWrite(t *testing.T) {
for _, testCase := range []struct {
name string
withSummary bool
isStdin bool
verbose bool
results []validator.Result
expect string
}{
{
"a single deployment, no summary, no verbose",
false,
false,
false,
[]validator.Result{},
"",
},
{
"a single deployment, summary, no verbose",
true,
false,
false,
[]validator.Result{
{
Resource: resource.Resource{
Path: "deployment.yml",
Bytes: []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: "my-app"
`),
},
Status: validator.Valid,
Err: nil,
},
},
"Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0\n",
},
{
"a single deployment, verbose, with summary",
true,
false,
true,
[]validator.Result{
{
Resource: resource.Resource{
Path: "deployment.yml",
Bytes: []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: "my-app"
`),
},
Status: validator.Valid,
Err: nil,
},
},
"\033[32m✔\033[0m deployment.yml: \033[32mDeployment my-app is valid\033[0m\n" +
"Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0\n",
},
} {
w := new(bytes.Buffer)
o := prettyOutput(w, testCase.withSummary, testCase.isStdin, testCase.verbose)

for _, res := range testCase.results {
o.Write(res)
}
o.Flush()

if w.String() != testCase.expect {
t.Errorf("%s - expected, but got:\n%s\n%s\n", testCase.name, testCase.expect, w)
}
}
}

0 comments on commit 8bc9f42

Please sign in to comment.