This commit is contained in:
Yann Hamon 2020-11-01 13:00:02 +01:00
parent 939b44e3ca
commit 1bc9283240
15 changed files with 241 additions and 458 deletions

View file

@ -3,10 +3,11 @@ package output
import (
"encoding/json"
"fmt"
"github.com/yannh/kubeconform/pkg/validator"
"io"
)
type result struct {
type oresult struct {
Filename string `json:"filename"`
Kind string `json:"kind"`
Name string `json:"name"`
@ -19,7 +20,7 @@ type jsono struct {
w io.Writer
withSummary bool
verbose bool
results []result
results []oresult
nValid, nInvalid, nErrors, nSkipped int
}
@ -29,7 +30,7 @@ func jsonOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
w: w,
withSummary: withSummary,
verbose: verbose,
results: []result{},
results: []oresult{},
nValid: 0,
nInvalid: 0,
nErrors: 0,
@ -38,31 +39,30 @@ func jsonOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
}
// JSON.Write will only write when JSON.Flush has been called
func (o *jsono) Write(filename, kind, name, version string, err error, skipped bool) error {
func (o *jsono) Write(result validator.Result) error {
msg, st := "", ""
s := status(kind, name, err, skipped)
switch s {
case statusValid:
switch result.Status {
case validator.Valid:
st = "statusValid"
o.nValid++
case statusInvalid:
case validator.Invalid:
st = "statusInvalid"
msg = err.Error()
msg = result.Err.Error()
o.nInvalid++
case statusError:
case validator.Error:
st = "statusError"
msg = err.Error()
msg = result.Err.Error()
o.nErrors++
case statusSkipped:
case validator.Skipped:
st = "statusSkipped"
o.nSkipped++
case statusEmpty:
case validator.Empty:
}
if o.verbose || (s != statusValid && s != statusSkipped && s != statusEmpty) {
o.results = append(o.results, result{Filename: filename, Kind: kind, Name: name, Version: version, Status: st, Msg: msg})
if o.verbose || (result.Status != validator.Valid && result.Status != validator.Skipped && result.Status != validator.Empty) {
sig, _ := result.Resource.Signature()
o.results = append(o.results, oresult{Filename: result.Resource.Path, Kind: sig.Kind, Name: sig.Name, Version: sig.Version, Status: st, Msg: msg})
}
return nil
@ -75,7 +75,7 @@ func (o *jsono) Flush() error {
if o.withSummary {
jsonObj := struct {
Resources []result `json:"resources"`
Resources []oresult `json:"resources"`
Summary struct {
Valid int `json:"valid"`
Invalid int `json:"invalid"`
@ -100,7 +100,7 @@ func (o *jsono) Flush() error {
res, err = json.MarshalIndent(jsonObj, "", " ")
} else {
jsonObj := struct {
Resources []result `json:"resources"`
Resources []oresult `json:"resources"`
}{
Resources: o.results,
}

View file

@ -1,114 +0,0 @@
package output
import (
"bytes"
"testing"
)
func TestJSONWrite(t *testing.T) {
type result struct {
fileName, kind, name, version string
err error
skipped bool
}
for _, testCase := range []struct {
name string
withSummary bool
verbose bool
res []result
expect string
}{
{
"a single deployment, no summary, no verbose",
false,
false,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
`{
"resources": []
}
`,
},
{
"a single deployment, summary, no verbose",
true,
false,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
`{
"resources": [],
"summary": {
"valid": 1,
"invalid": 0,
"errors": 0,
"skipped": 0
}
}
`,
},
{
"a single deployment, verbose, with summary",
true,
true,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
`{
"resources": [
{
"filename": "deployment.yml",
"kind": "Deployment",
"name": "my-app",
"version": "apps/v1",
"status": "statusValid",
"msg": ""
}
],
"summary": {
"valid": 1,
"invalid": 0,
"errors": 0,
"skipped": 0
}
}
`,
},
} {
w := new(bytes.Buffer)
o := jsonOutput(w, testCase.withSummary, false, testCase.verbose)
for _, res := range testCase.res {
o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped)
}
o.Flush()
if w.String() != testCase.expect {
t.Fatalf("%s - expected %s, got %s", testCase.name, testCase.expect, w)
}
}
}

View file

@ -6,17 +6,8 @@ import (
"os"
)
const (
_ = iota
statusValid
statusInvalid
statusError
statusSkipped
statusEmpty
)
type Output interface {
Write(filename, kind, name, version string, err error, skipped bool) error
Write(validator.Result) error
Flush() error
}
@ -32,22 +23,3 @@ func New(outputFormat string, printSummary, isStdin, verbose bool) (Output, erro
return nil, fmt.Errorf("`outputFormat` must be 'text' or 'json'")
}
}
func status(kind, name string, err error, skipped bool) int {
if name == "" && kind == "" && err == nil && skipped == false {
return statusEmpty
}
if skipped {
return statusSkipped
}
if err != nil {
if _, ok := err.(validator.InvalidResourceError); ok {
return statusInvalid
}
return statusError
}
return statusValid
}

View file

@ -2,6 +2,7 @@ package output
import (
"fmt"
"github.com/yannh/kubeconform/pkg/validator"
"io"
"sync"
)
@ -31,35 +32,37 @@ func textOutput(w io.Writer, withSummary, isStdin, verbose bool) Output {
}
}
func (o *texto) Write(filename, kind, name, version string, reserr error, skipped bool) error {
func (o *texto) Write(result validator.Result) error {
o.Lock()
defer o.Unlock()
var err error
o.files[filename] = true
switch status(kind, name, reserr, skipped) {
case statusValid:
sig, _ := result.Resource.Signature()
o.files[result.Resource.Path] = true
switch result.Status {
case validator.Valid:
if o.verbose {
_, err = fmt.Fprintf(o.w, "%s - %s %s is valid\n", filename, kind, name)
_, err = fmt.Fprintf(o.w, "%s - %s %s is valid\n", result.Resource.Path, sig.Kind, sig.Name)
}
o.nValid++
case statusInvalid:
_, err = fmt.Fprintf(o.w, "%s - %s %s is invalid: %s\n", filename, kind, name, reserr)
case validator.Invalid:
_, err = fmt.Fprintf(o.w, "%s - %s %s is invalid: %s\n", result.Resource.Path, sig.Kind, sig.Name, result.Err)
o.nInvalid++
case statusError:
if kind != "" && name != "" {
_, err = fmt.Fprintf(o.w, "%s - %s %s failed validation: %s\n", filename, kind, name, reserr)
case validator.Error:
if sig.Kind != "" && sig.Name != "" {
_, err = fmt.Fprintf(o.w, "%s - %s %s failed validation: %s\n", result.Resource.Path, sig.Kind, sig.Name, result.Err)
} else {
_, err = fmt.Fprintf(o.w, "%s - failed validation: %s\n", filename, reserr)
_, err = fmt.Fprintf(o.w, "%s - failed validation: %s\n", result.Resource.Path, result.Err)
}
o.nErrors++
case statusSkipped:
case validator.Skipped:
if o.verbose {
_, err = fmt.Fprintf(o.w, "%s - %s %s skipped\n", filename, name, kind)
_, err = fmt.Fprintf(o.w, "%s - %s %s skipped\n", result.Resource.Path, sig.Name, sig.Kind)
}
o.nSkipped++
case statusEmpty: // sent to ensure we count the filename as parsed
case validator.Empty: // sent to ensure we count the filename as parsed
}
return err

View file

@ -1,86 +0,0 @@
package output
import (
"bytes"
"testing"
)
func TestTextWrite(t *testing.T) {
type result struct {
fileName, kind, name, version string
err error
skipped bool
}
for _, testCase := range []struct {
name string
withSummary bool
verbose bool
res []result
expect string
}{
{
"a single deployment, no summary, no verbose",
false,
false,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
"",
},
{
"a single deployment, summary, no verbose",
true,
false,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
"Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0\n",
},
{
"a single deployment, verbose, with summary",
true,
true,
[]result{
{
"deployment.yml",
"Deployment",
"my-app",
"apps/v1",
nil,
false,
},
},
`deployment.yml - Deployment my-app is valid
Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0
`,
},
} {
w := new(bytes.Buffer)
o := textOutput(w, testCase.withSummary, false, testCase.verbose)
for _, res := range testCase.res {
o.Write(res.fileName, res.kind, res.name, res.version, res.err, res.skipped)
}
o.Flush()
if w.String() != testCase.expect {
t.Errorf("%s - expected: %s, got: %s", testCase.name, testCase.expect, w)
}
}
}