mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-11 05:59:22 +00:00
Add support for "pretty" output (#195)
* feat: add pretty output format --------- Co-authored-by: William Yardley <wyardley@users.noreply.github.com>
This commit is contained in:
parent
9294e94a8d
commit
8bc9f42f39
4 changed files with 196 additions and 1 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
109
pkg/output/pretty.go
Normal file
109
pkg/output/pretty.go
Normal file
|
|
@ -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
pkg/output/pretty_test.go
Normal file
84
pkg/output/pretty_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue