Skip to content

Commit

Permalink
Run test cases
Browse files Browse the repository at this point in the history
Add logic for running individual test cases. Now that we've compiled the
constraints, all we need to do is Review() the specified objects and
compare the Results with what the user specified.

This PR does not add Assertion functionality - we'll do that for alpha,
not pre-alpha.

Also add unit tests that verify we are, in fact, running the test cases.

Signed-off-by: Will Beason <willbeason@google.com>
  • Loading branch information
Will Beason committed Jul 20, 2021
1 parent 812b7c6 commit 5f3234c
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 19 deletions.
8 changes: 8 additions & 0 deletions pkg/gktest/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ var (
// ErrCreatingClient indicates an error instantiating the Client which compiles
// Constraints and runs validation.
ErrCreatingClient = errors.New("creating client")
// ErrInvalidCase indicates a Case cannot be run due to not being configured properly.
ErrInvalidCase = errors.New("invalid Case")
// ErrUnexpectedAllow indicates a Case failed because it was expected to get
// violations, but did not get any.
ErrUnexpectedAllow = errors.New("got no violations")
// ErrUnexpectedDeny indicates a Case failed because it got violations, but
// was not expected to get any.
ErrUnexpectedDeny = errors.New("got violations")
)
26 changes: 15 additions & 11 deletions pkg/gktest/read_constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ func init() {
_ = apis.AddToScheme(scheme)
}

func readUnstructured(bytes []byte) (*unstructured.Unstructured, error) {
u := &unstructured.Unstructured{
Object: make(map[string]interface{}),
}
err := yaml.Unmarshal(bytes, u.Object)
if err != nil {
return nil, err
}
return u, nil
}

// readTemplate reads the contents of the path and returns the
// ConstraintTemplate it defines. Returns an error if the file does not define
// a ConstraintTemplate.
Expand All @@ -29,10 +40,7 @@ func readTemplate(f fs.FS, path string) (*templates.ConstraintTemplate, error) {
return nil, fmt.Errorf("reading ConstraintTemplate from %q: %w", path, err)
}

u := unstructured.Unstructured{
Object: make(map[string]interface{}),
}
err = yaml.Unmarshal(bytes, u.Object)
u, err := readUnstructured(bytes)
if err != nil {
return nil, fmt.Errorf("%w: parsing ConstraintTemplate YAML from %q: %v", ErrAddingTemplate, path, err)
}
Expand Down Expand Up @@ -78,19 +86,15 @@ func readConstraint(f fs.FS, path string) (*unstructured.Unstructured, error) {
return nil, fmt.Errorf("reading Constraint from %q: %w", path, err)
}

c := &unstructured.Unstructured{
Object: make(map[string]interface{}),
}

err = yaml.Unmarshal(bytes, c.Object)
u, err := readUnstructured(bytes)
if err != nil {
return nil, fmt.Errorf("%w: parsing Constraint from %q: %v", ErrAddingConstraint, path, err)
}

gvk := c.GroupVersionKind()
gvk := u.GroupVersionKind()
if gvk.Group != "constraints.gatekeeper.sh" {
return nil, ErrNotAConstraint
}

return c, nil
return u, nil
}
57 changes: 56 additions & 1 deletion pkg/gktest/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"
"io/fs"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// Runner defines logic independent of how tests are run and the results are
Expand Down Expand Up @@ -74,7 +76,60 @@ func (r *Runner) runTest(ctx context.Context, filter Filter, t Test) TestResult
return TestResult{CaseResults: results}
}

func readCase(f fs.FS, path string) (*unstructured.Unstructured, error) {
bytes, err := fs.ReadFile(f, path)
if err != nil {
return nil, err
}

return readUnstructured(bytes)
}

func runAllow(ctx context.Context, client Client, f fs.FS, path string) CaseResult {
u, err := readCase(f, path)
if err != nil {
return CaseResult{Error: err}
}

result, err := client.Review(ctx, u)
if err != nil {
return CaseResult{Error: err}
}

results := result.Results()
if len(results) > 0 {
return CaseResult{Error: fmt.Errorf("%w: %v", ErrUnexpectedDeny, results)}
}
return CaseResult{}
}

func runDeny(ctx context.Context, client Client, f fs.FS, path string, assertions []Assertion) CaseResult {
u, err := readCase(f, path)
if err != nil {
return CaseResult{Error: err}
}

result, err := client.Review(ctx, u)
if err != nil {
return CaseResult{Error: err}
}

results := result.Results()
if len(results) == 0 {
return CaseResult{Error: ErrUnexpectedAllow}
}

return CaseResult{}
}

// RunCase executes a Case and returns the result of the run.
func (r *Runner) runCase(ctx context.Context, client Client, c Case) CaseResult {
return CaseResult{}
switch {
case c.Allow != "" && c.Deny == "":
return runAllow(ctx, client, r.FS, c.Allow)
case c.Allow == "" && c.Deny != "":
return runDeny(ctx, client, r.FS, c.Deny, c.Assertions)
default:
return CaseResult{Error: fmt.Errorf("%w: must define exactly one of allow and deny", ErrInvalidCase)}
}
}

0 comments on commit 5f3234c

Please sign in to comment.