mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-22 11:17:01 +00:00
Add support for passing manifests via stdin
This commit is contained in:
parent
42949e20b2
commit
7402d0fed8
7 changed files with 86 additions and 41 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 ]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue