Skip to content

Commit

Permalink
Added TAP output format
Browse files Browse the repository at this point in the history
  • Loading branch information
garethr committed Aug 10, 2019
1 parent 4ab8594 commit 4e5d0f6
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 5 deletions.
10 changes: 10 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ The file chart/templates/primary.yaml contains a valid ReplicationControlle

- Plaintext `--output=stdout`
- JSON: `--output=json`
- TAP: `--output=tap`

### Example Output

Expand Down Expand Up @@ -135,6 +136,15 @@ The document my-invalid-rc.yaml contains an invalid ReplicationController
]
```

#### TAP

```console
$ kubeval fixtures/invalid.yaml -o tap
1..1
not ok 1 - fixtures/invalid.yaml (ReplicationController) - spec.replicas: Invalid type. Expected: [integer,null], given: string
```


## Full usage instructions

```console
Expand Down
84 changes: 81 additions & 3 deletions kubeval/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kubeval
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"

Expand All @@ -24,12 +25,14 @@ type outputManager interface {
const (
outputSTD = "stdout"
outputJSON = "json"
outputTAP = "tap"
)

func validOutputs() []string {
return []string{
outputSTD,
outputJSON,
outputTAP,
}
}

Expand All @@ -39,6 +42,8 @@ func GetOutputManager(outFmt string) outputManager {
return newSTDOutputManager()
case outputJSON:
return newDefaultJSONOutputManager()
case outputTAP:
return newDefaultTAPOutputManager()
default:
return newSTDOutputManager()
}
Expand Down Expand Up @@ -83,7 +88,7 @@ const (
statusSkipped = "skipped"
)

type jsonEvalResult struct {
type dataEvalResult struct {
Filename string `json:"filename"`
Kind string `json:"kind"`
Status status `json:"status"`
Expand All @@ -94,7 +99,7 @@ type jsonEvalResult struct {
type jsonOutputManager struct {
logger *log.Logger

data []jsonEvalResult
data []dataEvalResult
}

func newDefaultJSONOutputManager() *jsonOutputManager {
Expand Down Expand Up @@ -132,7 +137,7 @@ func (j *jsonOutputManager) Put(r ValidationResult) error {
errs = append(errs, e.String())
}

j.data = append(j.data, jsonEvalResult{
j.data = append(j.data, dataEvalResult{
Filename: r.FileName,
Kind: r.Kind,
Status: getStatus(r),
Expand All @@ -157,3 +162,76 @@ func (j *jsonOutputManager) Flush() error {
j.logger.Print(out.String())
return nil
}

// tapOutputManager reports `conftest` results to stdout.
type tapOutputManager struct {
logger *log.Logger

data []dataEvalResult
}

// newDefaultTapOutManager instantiates a new instance of tapOutputManager
// using the default logger.
func newDefaultTAPOutputManager() *tapOutputManager {
return newTAPOutputManager(log.New(os.Stdout, "", 0))
}

// newTapOutputManager constructs an instance of tapOutputManager given a
// logger instance.
func newTAPOutputManager(l *log.Logger) *tapOutputManager {
return &tapOutputManager{
logger: l,
}
}

func (j *tapOutputManager) Put(r ValidationResult) error {
errs := make([]string, 0, len(r.Errors))
for _, e := range r.Errors {
errs = append(errs, e.String())
}

j.data = append(j.data, dataEvalResult{
Filename: r.FileName,
Kind: r.Kind,
Status: getStatus(r),
Errors: errs,
})

return nil
}

func (j *tapOutputManager) Flush() error {
issues := len(j.data)
if issues > 0 {
total := 0
for _, r := range j.data {
if len(r.Errors) > 0 {
total = total + len(r.Errors)
} else {
total = total + 1
}
}
j.logger.Print(fmt.Sprintf("1..%d", total))
count := 0
for _, r := range j.data {
count = count + 1
var kindMarker string
if r.Kind == "" {
kindMarker = ""
} else {
kindMarker = fmt.Sprintf(" (%s)", r.Kind)
}
if r.Status == "valid" {
j.logger.Print("ok ", count, " - ", r.Filename, kindMarker)
} else if r.Status == "skipped" {
j.logger.Print("ok ", count, " #skip - ", r.Filename, kindMarker)
} else if r.Status == "invalid" {
for _, e := range r.Errors {
j.logger.Print("not ok ", count, " - ", r.Filename, kindMarker, " - ", e)
count = count + 1
}
}
}
}
return nil
}
70 changes: 68 additions & 2 deletions kubeval/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,79 @@ func Test_jsonOutputManager_put(t *testing.T) {
s := newJSONOutputManager(log.New(buf, "", 0))

// record results
err := s.put(tt.args.vr)
err := s.Put(tt.args.vr)
if err != nil {
assert.Equal(t, tt.expErr, err)
}

// flush final buffer
err = s.flush()
err = s.Flush()
if err != nil {
assert.Equal(t, tt.expErr, err)
}

assert.Equal(t, tt.exp, buf.String())
})
}
}

func Test_tapOutputManager_put(t *testing.T) {
type args struct {
vr ValidationResult
}

tests := []struct {
msg string
args args
exp string
expErr error
}{
{
msg: "file with no errors",
args: args{
vr: ValidationResult{
FileName: "deployment.yaml",
Kind: "Deployment",
ValidatedAgainstSchema: true,
Errors: nil,
},
},
exp: `1..1
ok 1 - deployment.yaml (Deployment)
`,
},
{
msg: "file with errors",
args: args{
vr: ValidationResult{
FileName: "service.yaml",
Kind: "Service",
ValidatedAgainstSchema: true,
Errors: newResultErrors([]string{
"i am a error",
"i am another error",
}),
},
},
exp: `1..2
not ok 1 - service.yaml (Service) - error: i am a error
not ok 2 - service.yaml (Service) - error: i am another error
`,
},
}
for _, tt := range tests {
t.Run(tt.msg, func(t *testing.T) {
buf := new(bytes.Buffer)
s := newTAPOutputManager(log.New(buf, "", 0))

// record results
err := s.Put(tt.args.vr)
if err != nil {
assert.Equal(t, tt.expErr, err)
}

// flush final buffer
err = s.Flush()
if err != nil {
assert.Equal(t, tt.expErr, err)
}
Expand Down

0 comments on commit 4e5d0f6

Please sign in to comment.