Skip to content

Commit

Permalink
Allow structured errors in rules (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpreese authored Feb 19, 2020
1 parent 974580e commit 93ee280
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 101 deletions.
2 changes: 1 addition & 1 deletion acceptance.bats
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
@test "Output results only once" {
run ./conftest test -p examples/kubernetes/policy examples/kubernetes/deployment.yaml
count="${#lines[@]}"
[ "$count" -eq 4 ]
[ "$count" -eq 5 ]
}

@test "Can verify rego tests" {
Expand Down
10 changes: 10 additions & 0 deletions examples/kubernetes/policy/violation.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import data.kubernetes

name = input.metadata.name

violation[{"msg": msg, "details": {}}] {
kubernetes.is_deployment
msg = sprintf("Found deployment %s but deployments are not allowed", [name])
}
1 change: 0 additions & 1 deletion examples/kubernetes/policy/warn.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import data.kubernetes


name = input.metadata.name

warn[msg] {
Expand Down
32 changes: 14 additions & 18 deletions internal/commands/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (s *stdOutputManager) Put(cr CheckResult) error {
}

printResults := func(r Result, prefix string, color aurora.Color) {
s.logger.Print(s.color.Colorize(prefix, color), indicator, r.Message)
s.logger.Print(s.color.Colorize(prefix, color), indicator, r.Info["msg"])
for _, t := range r.Traces {
s.logger.Print(s.color.Colorize("TRAC", aurora.BlueFg), indicator, t)
}
Expand All @@ -111,8 +111,8 @@ func (s *stdOutputManager) Flush() error {
}

type jsonResult struct {
Message string `json:"message"`
Traces []string `json:"traces,omitempty"`
Info map[string]interface{} `json:"info"`
Traces []string `json:"traces,omitempty"`
}

type jsonCheckResult struct {
Expand Down Expand Up @@ -151,7 +151,6 @@ func errsToStrings(errs []error) []string {
}

func (j *jsonOutputManager) Put(cr CheckResult) error {

if cr.FileName == "-" {
cr.FileName = ""
}
Expand All @@ -166,38 +165,38 @@ func (j *jsonOutputManager) Put(cr CheckResult) error {
for _, warning := range cr.Warnings {
if len(warning.Traces) > 0 {
result.Warnings = append(result.Warnings, jsonResult{
Message: warning.Message.Error(),
Traces: errsToStrings(warning.Traces),
Info: warning.Info,
Traces: errsToStrings(warning.Traces),
})
} else {
result.Warnings = append(result.Warnings, jsonResult{
Message: warning.Message.Error(),
Info: warning.Info,
})
}
}

for _, failure := range cr.Failures {
if len(failure.Traces) > 0 {
result.Failures = append(result.Failures, jsonResult{
Message: failure.Message.Error(),
Traces: errsToStrings(failure.Traces),
Info: failure.Info,
Traces: errsToStrings(failure.Traces),
})
} else {
result.Failures = append(result.Failures, jsonResult{
Message: failure.Message.Error(),
Info: failure.Info,
})
}
}

for _, successes := range cr.Successes {
if len(successes.Traces) > 0 {
result.Successes = append(result.Successes, jsonResult{
Message: successes.Message.Error(),
Traces: errsToStrings(successes.Traces),
Info: successes.Info,
Traces: errsToStrings(successes.Traces),
})
} else {
result.Successes = append(result.Successes, jsonResult{
Message: successes.Message.Error(),
Info: successes.Info,
})
}
}
Expand All @@ -223,7 +222,6 @@ func (j *jsonOutputManager) Flush() error {
return nil
}

// tapOutputManager reports `conftest` results to stdout.
type tapOutputManager struct {
logger *log.Logger
}
Expand All @@ -243,7 +241,6 @@ func NewTAPOutputManager(l *log.Logger) *tapOutputManager {
}

func (s *tapOutputManager) Put(cr CheckResult) error {

var indicator string
if cr.FileName == "-" {
indicator = " - "
Expand All @@ -252,7 +249,7 @@ func (s *tapOutputManager) Put(cr CheckResult) error {
}

printResults := func(r Result, prefix string, counter int) {
s.logger.Print(prefix, counter, indicator, r.Message)
s.logger.Print(prefix, counter, indicator, r.Info["msg"])
if len(r.Traces) > 0 {
s.logger.Print("# Traces")
for j, t := range r.Traces {
Expand Down Expand Up @@ -311,9 +308,8 @@ func NewTableOutputManager(w io.Writer) *tableOutputManager {
}

func (s *tableOutputManager) Put(cr CheckResult) error {

printResults := func(r Result, prefix string, filename string) {
d := []string{prefix, filename, r.Message.Error()}
d := []string{prefix, filename, r.Error()}
s.table.Append(d)
for _, t := range r.Traces {
dt := []string{"trace", filename, t.Error()}
Expand Down
90 changes: 29 additions & 61 deletions internal/commands/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package commands

import (
"bytes"
"errors"
"log"
"reflect"
"strings"
Expand All @@ -27,14 +26,8 @@ func Test_stdOutputManager_put(t *testing.T) {
args: args{
cr: CheckResult{
FileName: "foo.yaml",
Warnings: []Result{
Result{
Message: errors.New("first warning"),
}},
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Warnings: []Result{NewResult("first warning", []error{})},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: []string{"WARN - foo.yaml - first warning", "FAIL - foo.yaml - first failure"},
Expand All @@ -44,14 +37,8 @@ func Test_stdOutputManager_put(t *testing.T) {
args: args{
cr: CheckResult{
FileName: "-",
Warnings: []Result{
Result{
Message: errors.New("first warning"),
}},
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Warnings: []Result{NewResult("first warning", []error{})},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: []string{"WARN - first warning", "FAIL - first failure"},
Expand Down Expand Up @@ -106,27 +93,25 @@ func Test_jsonOutputManager_put(t *testing.T) {
args: args{
crs: []CheckResult{{
FileName: "examples/kubernetes/service.yaml",
Warnings: []Result{
Result{
Message: errors.New("first warning"),
}},
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Warnings: []Result{NewResult("first warning", []error{})},
Failures: []Result{NewResult("first failure", []error{})},
}},
},
exp: `[
{
"filename": "examples/kubernetes/service.yaml",
"warnings": [
{
"message": "first warning"
"info": {
"msg": "first warning"
}
}
],
"failures": [
{
"message": "first failure"
"info": {
"msg": "first failure"
}
}
],
"successes": []
Expand All @@ -139,10 +124,7 @@ func Test_jsonOutputManager_put(t *testing.T) {
args: args{
crs: []CheckResult{{
FileName: "examples/kubernetes/service.yaml",
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Failures: []Result{NewResult("first failure", []error{})},
}},
},
exp: `[
Expand All @@ -151,7 +133,9 @@ func Test_jsonOutputManager_put(t *testing.T) {
"warnings": [],
"failures": [
{
"message": "first failure"
"info": {
"msg": "first failure"
}
}
],
"successes": []
Expand All @@ -164,18 +148,18 @@ func Test_jsonOutputManager_put(t *testing.T) {
args: args{
fileName: "-",
crs: []CheckResult{{
Failures: []Result{
Result{
Message: errors.New("first failure"),
}}}},
Failures: []Result{NewResult("first failure", []error{})}},
},
},
exp: `[
{
"filename": "",
"warnings": [],
"failures": [
{
"message": "first failure"
"info": {
"msg": "first failure"
}
}
],
"successes": []
Expand Down Expand Up @@ -303,14 +287,8 @@ func Test_tapOutputManager_put(t *testing.T) {
args: args{
cr: CheckResult{
FileName: "examples/kubernetes/service.yaml",
Warnings: []Result{
Result{
Message: errors.New("first warning"),
}},
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Warnings: []Result{NewResult("first warning", []error{})},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: `1..2
Expand All @@ -324,10 +302,8 @@ not ok 2 - examples/kubernetes/service.yaml - first warning
args: args{
cr: CheckResult{
FileName: "examples/kubernetes/service.yaml",
Failures: []Result{
Result{
Message: errors.New("first failure"),
}}},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: `1..1
not ok 1 - examples/kubernetes/service.yaml - first failure
Expand All @@ -338,10 +314,8 @@ not ok 1 - examples/kubernetes/service.yaml - first failure
args: args{
cr: CheckResult{
FileName: "-",
Failures: []Result{
Result{
Message: errors.New("first failure"),
}}},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: `1..1
not ok 1 - first failure
Expand Down Expand Up @@ -395,14 +369,8 @@ func Test_tableOutputManager_put(t *testing.T) {
args: args{
cr: CheckResult{
FileName: "examples/kubernetes/service.yaml",
Warnings: []Result{
Result{
Message: errors.New("first warning"),
}},
Failures: []Result{
Result{
Message: errors.New("first failure"),
}},
Warnings: []Result{NewResult("first warning", []error{})},
Failures: []Result{NewResult("first failure", []error{})},
},
},
exp: `+---------+----------------------------------+---------------+
Expand Down
43 changes: 33 additions & 10 deletions internal/commands/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,12 @@ var (

// Result describes the result of a single rule evaluation.
type Result struct {
Message error
Traces []error
Info map[string]interface{}
Traces []error
}

func (r Result) Error() string {
return r.Info["msg"].(string)
}

// CheckResult describes the result of a conftest evaluation.
Expand All @@ -94,6 +98,15 @@ type CheckResult struct {
Successes []Result
}

func NewResult(message string, traces []error) Result {
result := Result{
Info: map[string]interface{}{"msg": message},
Traces: traces,
}

return result
}

// NewTestCommand creates a new test command
func NewTestCommand(ctx context.Context) *cobra.Command {
cmd := cobra.Command{
Expand Down Expand Up @@ -359,16 +372,26 @@ func runQuery(ctx context.Context, query string, input interface{}, compiler *as
value := expression.Value
if hasResults(value) {
for _, v := range value.([]interface{}) {
errs = append(errs, Result{
Message: errors.New(v.(string)),
Traces: traces,
})
switch val := v.(type) {
case string:
errs = append(errs, NewResult(val, traces))
case map[string]interface{}:
if _, ok := val["msg"]; !ok {
return nil, nil, fmt.Errorf("rule missing msg field: %v", val)
}
if _, ok := val["msg"].(string); !ok {
return nil, nil, fmt.Errorf("msg field must be string: %v", val)
}

result := NewResult(val["msg"].(string), traces)
for k, v := range val {
result.Info[k] = v
}
errs = append(errs, result)
}
}
} else {
successes = append(successes, Result{
Message: errors.New(expression.Text),
Traces: traces,
})
successes = append(successes, NewResult(expression.Text, traces))
}
}
}
Expand Down
Loading

0 comments on commit 93ee280

Please sign in to comment.