Skip to content

Commit

Permalink
create assertion top level package
Browse files Browse the repository at this point in the history
  • Loading branch information
gcheadle-vmware committed Apr 25, 2022
1 parent a0097f7 commit b343b57
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 63 deletions.
73 changes: 32 additions & 41 deletions pkg/workspace/assert.go → pkg/assertions/assert.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

package workspace
package assertions

import (
"fmt"

"github.com/k14s/starlark-go/starlark"
"github.com/vmware-tanzu/carvel-ytt/pkg/template"
"github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta"
Expand All @@ -16,28 +15,28 @@ const (
AnnotationAssertValidate template.AnnotationName = "assert/validate"
)

// TODO: update go docs
// ProcessAndRunValidations takes a root Node, and threadName, and traverses the tree checking for assert annotations.
// Validations are processed and executed using the value of the annotated node as the parameter to the assertions.
// ProcessAndRunValidations takes a root Node, and threadName, and validates each Node in the tree.
// Assert annotations are stored on the Node as Validations, which are then executed using the
// value of the annotated node as the parameter to the assertions.
//
// When the assertions have violations, the errors are collected and returned in an AssertCheck.
// Otherwise, returns empty AssertCheck and nil.
// When a Node's value is invalid, the errors are collected and returned in an AssertCheck.
// Otherwise, returns empty AssertCheck and nil error.
func ProcessAndRunValidations(n yamlmeta.Node, threadName string) (AssertCheck, error) {
if n == nil {
return AssertCheck{}, nil
}
err := yamlmeta.Walk(n, &assertGetter{})
err := yamlmeta.Walk(n, &convertAssertAnnsToValidations{})
if err != nil {
return AssertCheck{}, err
}

assertionRunner := newAssertRunner(threadName)
err = yamlmeta.Walk(n, assertionRunner)
validationRunner := newValidationRunner(threadName)
err = yamlmeta.Walk(n, validationRunner)
if err != nil {
return AssertCheck{}, err
}

return assertionRunner.chk, nil
return validationRunner.chk, nil
}

// AssertCheck holds the resulting violations from executing Validations on a node.
Expand All @@ -63,10 +62,14 @@ func (ac *AssertCheck) HasViolations() bool {
return len(ac.Violations) > 0
}

type assertGetter struct {}
type convertAssertAnnsToValidations struct{}

// TODO: update go docs
func (a *assertGetter) Visit(node yamlmeta.Node) error {
// Visit if `node` is annotated with `@assert/validate` (AnnotationAssertValidate).
// Checks annotation, and stores validation Rules on Node's validations meta.
//
// This visitor returns and error if any assert annotation is not well-formed,
// otherwise, returns nil.
func (a *convertAssertAnnsToValidations) Visit(node yamlmeta.Node) error {
nodeAnnotations := template.NewAnnotations(node)
if !nodeAnnotations.Has(AnnotationAssertValidate) {
return nil
Expand All @@ -79,16 +82,15 @@ func (a *assertGetter) Visit(node yamlmeta.Node) error {
if syntaxErr != nil {
return syntaxErr
}
//store rules in node's meta
// TODO: not overriding any metas previously set
node.SetMeta("validations", rules)
// store rules in node's validations meta without overriding any existing rules
AddValidations(node, rules)
}

return nil
}

func newRulesFromAssertValidateAnnotation(annotation template.NodeAnnotation) ([]yamlmeta.Rule, error) {
var rules []yamlmeta.Rule
func newRulesFromAssertValidateAnnotation(annotation template.NodeAnnotation) ([]Rule, error) {
var rules []Rule

if len(annotation.Kwargs) != 0 {
return nil, fmt.Errorf("Invalid @%s annotation - expected @%s to have 2-tuple as argument(s), but found keyword argument (by %s)", AnnotationAssertValidate, AnnotationAssertValidate, annotation.Position.AsCompactString())
Expand All @@ -112,7 +114,7 @@ func newRulesFromAssertValidateAnnotation(annotation template.NodeAnnotation) ([
if !ok {
return nil, fmt.Errorf("Invalid @%s annotation - expected second item in the 2-tuple to be an assertion function, but was %s (at %s)", AnnotationAssertValidate, ruleTuple[1].Type(), annotation.Position.AsCompactString())
}
rules = append(rules, yamlmeta.Rule{
rules = append(rules, Rule{
Msg: message.GoString(),
Assertion: lambda,
Position: annotation.Position,
Expand All @@ -122,35 +124,25 @@ func newRulesFromAssertValidateAnnotation(annotation template.NodeAnnotation) ([
return rules, nil
}


type assertRunner struct {
type validationRunner struct {
thread *starlark.Thread
chk AssertCheck
}

func newAssertRunner(threadName string) *assertRunner {
return &assertRunner{thread: &starlark.Thread{Name: threadName}, chk: AssertCheck{[]error{}}}
func newValidationRunner(threadName string) *validationRunner {
return &validationRunner{thread: &starlark.Thread{Name: threadName}, chk: AssertCheck{[]error{}}}
}

// TODO: update go docs
// Visit if `node` is annotated with `@assert/validate` (AnnotationAssertValidate).
// Checks, validates, and runs the validation Rules, any violations from running the assertions are collected.
// Visit if `node` is has validations in its meta.
// Runs the validation Rules, any violations from running the assertions are collected.
//
// This visitor returns and error if any assertion is not well-formed,
// otherwise, returns nil.
func (a *assertRunner) Visit(node yamlmeta.Node) error {

//get rules in node's meta
metas := node.GetMeta("validations")
if metas == nil {
return nil
}

rules, ok := metas.([]yamlmeta.Rule)
if !ok {
// This visitor stores error(violations) in the validationRunner and returns nil.
func (a *validationRunner) Visit(node yamlmeta.Node) error {
// get rules in node's meta
rules := GetValidations(node)
if rules == nil {
return nil
}

for _, rule := range rules {
err := rule.Validate(node, a.thread)
if err != nil {
Expand All @@ -160,4 +152,3 @@ func (a *assertRunner) Visit(node yamlmeta.Node) error {

return nil
}

30 changes: 15 additions & 15 deletions pkg/yamlmeta/validate.go → pkg/assertions/validate.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package yamlmeta
package assertions

import (
"fmt"

"github.com/k14s/starlark-go/starlark"
"github.com/vmware-tanzu/carvel-ytt/pkg/filepos"
"github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta"
"github.com/vmware-tanzu/carvel-ytt/pkg/yamltemplate"
)

// A Rule represents an argument to an @assert/validate annotation;
// A Rule represents a validation attached to a Node via an annotation;
// it contains a string description of what constitutes a valid value,
// and a function that asserts the rule against an actual value.
// One @assert/validate annotation can have multiple Rules.
type Rule struct {
Msg string
Assertion starlark.Callable
Expand All @@ -21,22 +22,21 @@ type Rule struct {
//
// Returns an error if the assertion returns False (not-None), or assert.fail()s.
// Otherwise, returns nil.
func (r Rule) Validate(node Node, thread *starlark.Thread) error {
func (r Rule) Validate(node yamlmeta.Node, thread *starlark.Thread) error {
var key string
var nodeValue starlark.Value
switch typedNode := node.(type) {
case *DocumentSet, *Array, *Map:
// TODO: is this the correct place for as specific of an error
panic(fmt.Sprintf("@%s annotation at %s - not supported on %s at %s", "assert/validate", r.Position.AsCompactString(), TypeName(node), node.GetPosition().AsCompactString()))
case *MapItem:
case *yamlmeta.DocumentSet, *yamlmeta.Array, *yamlmeta.Map:
panic(fmt.Sprintf("validation at %s - not supported on %s at %s", r.Position.AsCompactString(), yamlmeta.TypeName(node), node.GetPosition().AsCompactString()))
case *yamlmeta.MapItem:
key = fmt.Sprintf("%q", typedNode.Key)
nodeValue = NewGoValueFromAST(typedNode.Value).AsStarlarkValue()
case *ArrayItem:
key = TypeName(typedNode)
nodeValue = NewGoValueFromAST(typedNode.Value).AsStarlarkValue()
case *Document:
key = TypeName(typedNode)
nodeValue = NewGoValueFromAST(typedNode.Value).AsStarlarkValue()
nodeValue = yamltemplate.NewGoValueWithYAML(typedNode.Value).AsStarlarkValue()
case *yamlmeta.ArrayItem:
key = yamlmeta.TypeName(typedNode)
nodeValue = yamltemplate.NewGoValueWithYAML(typedNode.Value).AsStarlarkValue()
case *yamlmeta.Document:
key = yamlmeta.TypeName(typedNode)
nodeValue = yamltemplate.NewGoValueWithYAML(typedNode.Value).AsStarlarkValue()
}

result, err := starlark.Call(thread, r.Assertion, starlark.Tuple{nodeValue}, []starlark.Tuple{})
Expand Down
23 changes: 23 additions & 0 deletions pkg/assertions/yamlmeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package assertions

import "github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta"

func AddValidations(node yamlmeta.Node, rules []Rule) {
metas := node.GetMeta("validations")
if currRules, ok := metas.([]Rule); ok {
rules = append(currRules, rules...)
}
SetValidations(node, rules)
}

func SetValidations(node yamlmeta.Node, rules []Rule) {
node.SetMeta("validations", rules)
}

func GetValidations(node yamlmeta.Node) []Rule {
metas := node.GetMeta("validations")
if rules, ok := metas.([]Rule); ok {
return rules
}
return nil
}
3 changes: 2 additions & 1 deletion pkg/workspace/library_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/k14s/starlark-go/starlark"
"github.com/vmware-tanzu/carvel-ytt/pkg/assertions"
"github.com/vmware-tanzu/carvel-ytt/pkg/cmd/ui"
"github.com/vmware-tanzu/carvel-ytt/pkg/files"
"github.com/vmware-tanzu/carvel-ytt/pkg/template"
Expand Down Expand Up @@ -100,7 +101,7 @@ func (ll *LibraryExecution) Values(valuesOverlays []*datavalues.Envelope, schema
// Returns an error if the arguments to an @assert/validate are invalid,
// otherwise, checks the AssertCheck for violations, and returns nil if there are no violations.
func (ll *LibraryExecution) validateValues(values *datavalues.Envelope) error {
assertCheck, err := ProcessAndRunValidations(values.Doc, "assert-data-values")
assertCheck, err := assertions.ProcessAndRunValidations(values.Doc, "assert-data-values")
if err != nil {
return err
}
Expand Down
6 changes: 0 additions & 6 deletions pkg/yamlmeta/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/vmware-tanzu/carvel-ytt/pkg/filepos"
"github.com/vmware-tanzu/carvel-ytt/pkg/orderedmap"
tplcore "github.com/vmware-tanzu/carvel-ytt/pkg/template/core"
"github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta/internal/yaml.v2"
)

Expand All @@ -28,11 +27,6 @@ func NewGoFromAST(val interface{}) interface{} {
return convertToGo(val)
}

//TODO: how else can we provide the RIGHT type of value
func NewGoValueFromAST(val interface{}) tplcore.GoValue {
return tplcore.NewGoValue(convertToGo(val))
}

func convertToLowYAML(val interface{}) interface{} {
switch typedVal := val.(type) {
case map[interface{}]interface{}:
Expand Down

0 comments on commit b343b57

Please sign in to comment.