Add support for passing manifests via stdin

This commit is contained in:
Yann Hamon 2020-10-31 14:02:15 +01:00
parent 42949e20b2
commit 7402d0fed8
7 changed files with 86 additions and 41 deletions

View file

@ -101,6 +101,12 @@ $ echo $?
1 1
``` ```
* Passing manifests via Stdin
```
cat fixtures/valid.yaml | ./bin/kubeconform -summary -
Summary: 1 resource found parsing stdin - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0
```
* Validating a folder, increasing the number of parallel workers * Validating a folder, increasing the number of parallel workers
``` ```
$ ./bin/kubeconform -summary -n 16 fixtures $ ./bin/kubeconform -summary -n 16 fixtures

View file

@ -125,3 +125,20 @@
run bin/kubeconform -kubernetes-version 3.8.0 -schema-location 'https://raw.githubusercontent.com/garethr/openshift-json-schema/master/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}.json' -summary fixtures/invalid.yaml run bin/kubeconform -kubernetes-version 3.8.0 -schema-location 'https://raw.githubusercontent.com/garethr/openshift-json-schema/master/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}.json' -summary fixtures/invalid.yaml
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
} }
@test "Pass when parsing a valid Kubernetes config YAML file on stdin" {
run bash -c "cat fixtures/valid.yaml | bin/kubeconform -summary"
[ "$status" -eq 0 ]
[ "$output" = "Summary: 1 resource found parsing stdin - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0" ]
}
@test "Pass when parsing a valid Kubernetes config YAML file explicitly on stdin" {
run bash -c "cat fixtures/valid.yaml | bin/kubeconform -summary"
[ "$status" -eq 0 ]
[ "$output" = "Summary: 1 resource found parsing stdin - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0" ]
}
@test "Fail when parsing an invalid Kubernetes config file on stdin" {
run bash -c "cat fixtures/invalid.yaml | bin/kubeconform -"
[ "$status" -eq 1 ]
}

View file

@ -4,14 +4,13 @@ import (
"bytes" "bytes"
"flag" "flag"
"fmt" "fmt"
"github.com/xeipuuv/gojsonschema"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"sync" "sync"
"github.com/xeipuuv/gojsonschema"
"github.com/yannh/kubeconform/pkg/cache" "github.com/yannh/kubeconform/pkg/cache"
"github.com/yannh/kubeconform/pkg/fsutils" "github.com/yannh/kubeconform/pkg/fsutils"
"github.com/yannh/kubeconform/pkg/output" "github.com/yannh/kubeconform/pkg/output"
@ -136,14 +135,14 @@ func (ap *arrayParam) Set(value string) error {
return nil return nil
} }
func getLogger(outputFormat string, printSummary, verbose bool) (output.Output, error) { func getLogger(outputFormat string, printSummary, isStdin, verbose bool) (output.Output, error) {
w := os.Stdout w := os.Stdout
switch { switch {
case outputFormat == "text": case outputFormat == "text":
return output.Text(w, printSummary, verbose), nil return output.Text(w, printSummary, isStdin, verbose), nil
case outputFormat == "json": case outputFormat == "json":
return output.JSON(w, printSummary, verbose), nil return output.JSON(w, printSummary, isStdin, verbose), nil
default: default:
return nil, fmt.Errorf("-output must be text or json") return nil, fmt.Errorf("-output must be text or json")
} }
@ -244,10 +243,18 @@ func realMain() int {
return 1 return 1
} }
// Detect whether we have data being piped through stdin
stat, _ := os.Stdin.Stat()
isStdin := (stat.Mode() & os.ModeCharDevice) == 0
skipKinds := skipKindsMap(skipKindsCSV) skipKinds := skipKindsMap(skipKindsCSV)
for _, file := range flag.Args() { if len(flag.Args()) == 1 && flag.Args()[0] == "-" {
files = append(files, file) isStdin = true
} else {
for _, file := range flag.Args() {
files = append(files, file)
}
} }
filter := func(signature resource.Signature) bool { filter := func(signature resource.Signature) bool {
@ -273,6 +280,7 @@ func realMain() int {
} }
validationResults := make(chan []validationResult) validationResults := make(chan []validationResult)
c := cache.New()
fileBatches := make(chan []string) fileBatches := make(chan []string)
go func() { go func() {
@ -281,7 +289,7 @@ func realMain() int {
}() }()
var o output.Output var o output.Output
if o, err = getLogger(outputFormat, summary, verbose); err != nil { if o, err = getLogger(outputFormat, summary, isStdin, verbose); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
return 1 return 1
} }
@ -289,38 +297,46 @@ func realMain() int {
res := make(chan bool) res := make(chan bool)
go processResults(o, validationResults, res) go processResults(o, validationResults, res)
c := cache.New() if isStdin {
var wg sync.WaitGroup res := ValidateStream(os.Stdin, registries, k8sVersion, c, filter, ignoreMissingSchemas)
for i := 0; i < nWorkers; i++ { for i := range res {
wg.Add(1) res[i].filename = "stdin"
go func() { }
defer wg.Done() validationResults <- res
} else {
var wg sync.WaitGroup
for i := 0; i < nWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for fileBatch := range fileBatches { for fileBatch := range fileBatches {
for _, filename := range fileBatch { for _, filename := range fileBatch {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
validationResults <- []validationResult{{ validationResults <- []validationResult{{
filename: filename, filename: filename,
err: err, err: err,
skipped: true, skipped: true,
}} }}
continue continue
}
res := ValidateStream(f, registries, k8sVersion, c, filter, ignoreMissingSchemas)
f.Close()
for i := range res {
res[i].filename = filename
}
validationResults <- res
} }
res := ValidateStream(f, registries, k8sVersion, c, filter, ignoreMissingSchemas)
f.Close()
for i := range res {
res[i].filename = filename
}
validationResults <- res
} }
} }()
}() }
wg.Wait()
} }
wg.Wait()
close(validationResults) close(validationResults)
success := <-res success := <-res
o.Flush() o.Flush()

View file

@ -24,7 +24,7 @@ type jsono struct {
} }
// JSON will output the results of the validation as a JSON // JSON will output the results of the validation as a JSON
func JSON(w io.Writer, withSummary bool, verbose bool) Output { func JSON(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
return &jsono{ return &jsono{
w: w, w: w,
withSummary: withSummary, withSummary: withSummary,

View file

@ -100,7 +100,7 @@ func TestJSONWrite(t *testing.T) {
}, },
} { } {
w := new(bytes.Buffer) w := new(bytes.Buffer)
o := JSON(w, testCase.withSummary, testCase.verbose) o := JSON(w, testCase.withSummary, false, testCase.verbose)
for _, res := range testCase.res { for _, res := range testCase.res {
o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped) o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped)

View file

@ -10,16 +10,18 @@ type text struct {
sync.Mutex sync.Mutex
w io.Writer w io.Writer
withSummary bool withSummary bool
isStdin bool
verbose bool verbose bool
files map[string]bool files map[string]bool
nValid, nInvalid, nErrors, nSkipped int nValid, nInvalid, nErrors, nSkipped int
} }
// Text will output the results of the validation as a text // Text will output the results of the validation as a text
func Text(w io.Writer, withSummary, verbose bool) Output { func Text(w io.Writer, withSummary, isStdin, verbose bool) Output {
return &text{ return &text{
w: w, w: w,
withSummary: withSummary, withSummary: withSummary,
isStdin: isStdin,
verbose: verbose, verbose: verbose,
files: map[string]bool{}, files: map[string]bool{},
nValid: 0, nValid: 0,
@ -76,7 +78,11 @@ func (o *text) Flush() error {
if nFiles > 1 { if nFiles > 1 {
filesPlural = "s" filesPlural = "s"
} }
_, 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) 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 return err

View file

@ -72,7 +72,7 @@ Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0
}, },
} { } {
w := new(bytes.Buffer) w := new(bytes.Buffer)
o := Text(w, testCase.withSummary, testCase.verbose) o := Text(w, testCase.withSummary, false, testCase.verbose)
for _, res := range testCase.res { for _, res := range testCase.res {
o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped) o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped)