-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for "pretty" output (#195)
* feat: add pretty output format --------- Co-authored-by: William Yardley <[email protected]>
- Loading branch information
Showing
4 changed files
with
196 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |