1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

add validate helper functions

This commit is contained in:
shivkumar dudhani 2019-08-29 11:44:50 -07:00
parent 116203282d
commit 20e2f639eb
7 changed files with 925 additions and 504 deletions

View file

@ -18,13 +18,13 @@ type EngineResponseNew struct {
//PolicyResponse policy application response
type PolicyResponse struct {
// policy name
Policy string
Policy string `json:"policy"`
// resource details
Resource ResourceSpec
Resource ResourceSpec `json:"resource"`
// policy statistics
PolicyStats
// rule response
Rules []RuleResponse
Rules []RuleResponse `json:"rules"`
// ValidationFailureAction: audit,enforce(default)
ValidationFailureAction string
}
@ -32,32 +32,32 @@ type PolicyResponse struct {
//ResourceSpec resource action applied on
type ResourceSpec struct {
//TODO: support ApiVersion
Kind string
APIVersion string
Namespace string
Name string
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}
//PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// time required to process the policy rules on a resource
ProcessingTime time.Duration
ProcessingTime time.Duration `json:"processingTime"`
// Count of rules that were applied succesfully
RulesAppliedCount int
RulesAppliedCount int `json:"rulesAppliedCount"`
}
//RuleResponse details for each rule applicatino
type RuleResponse struct {
// rule name specified in policy
Name string
Name string `json:"name"`
// rule type (Mutation,Generation,Validation) for Kyverno Policy
Type string
Type string `json:"type"`
// message response from the rule application
Message string
Message string `json:"message"`
// JSON patches, for mutation rules
Patches [][]byte
Patches [][]byte `json:"patches,omitempty"`
// success/fail
Success bool
Success bool `json:"success"`
// statistics
RuleStats
}
@ -70,7 +70,7 @@ func (rr RuleResponse) ToString() string {
//RuleStats stores the statisctis for the single rule application
type RuleStats struct {
// time required to appliy the rule on the resource
ProcessingTime time.Duration
ProcessingTime time.Duration `json:"processingTime"`
}
//IsSuccesful checks if any rule has failed or not

391
pkg/testrunner/scenario.go Normal file
View file

@ -0,0 +1,391 @@
package testrunner
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
ospath "path"
"path/filepath"
"reflect"
"testing"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
apiyaml "k8s.io/apimachinery/pkg/util/yaml"
)
type scenarioT struct {
testCases []scaseT
}
//scase defines input and output for a case
type scaseT struct {
Input sInput `yaml:"input"`
Expected sExpected `yaml:"expected"`
}
//sInput defines input for a test scenario
type sInput struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
LoadResources []string `yaml:"loadresources,omitempty"`
}
type sExpected struct {
Mutation sMutation `yaml:"mutation,omitempty"`
Validation sValidation `yaml:"validation,omitempty"`
// Generation sGeneration `yaml:"generation,omitempty"`
}
type sMutation struct {
// path to the patched resource to be compared with
PatchedResource string `yaml:"patchedresource,omitempty"`
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
}
type sValidation struct {
// expected respone from the policy engine
PolicyResponse engine.PolicyResponse `yaml:"policyresponse"`
}
//getRelativePath expects a path relative to project and builds the complete path
func getRelativePath(path string) string {
gp := os.Getenv("GOPATH")
ap := ospath.Join(gp, projectPath)
return ospath.Join(ap, path)
}
func loadScenario(t *testing.T, path string) (*scenarioT, error) {
fileBytes, err := loadFile(t, path)
if err != nil {
return nil, err
}
var testCases []scaseT
// load test cases seperated by '---'
// each test case defines an input & expected result
scenariosBytes := bytes.Split(fileBytes, []byte("---"))
for _, scenarioBytes := range scenariosBytes {
tc := scaseT{}
if err := yaml.Unmarshal([]byte(scenarioBytes), &tc); err != nil {
t.Errorf("failed to decode test case YAML: %v", err)
continue
}
testCases = append(testCases, tc)
}
scenario := scenarioT{
testCases: testCases,
}
return &scenario, nil
}
// loadFile loads file in byte buffer
func loadFile(t *testing.T, path string) ([]byte, error) {
path = getRelativePath(path)
t.Logf("reading file %s", path)
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
return ioutil.ReadFile(path)
}
//getFiles loads all scneario files in specified folder path
func getFiles(t *testing.T, folder string) ([]string, error) {
t.Logf("loading scneario files for folder %s", folder)
files, err := ioutil.ReadDir(folder)
if err != nil {
glog.Error(err)
return nil, err
}
var yamls []string
for _, file := range files {
if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
yamls = append(yamls, ospath.Join(folder, file.Name()))
}
}
return yamls, nil
}
func runScenario(t *testing.T, s *scenarioT) bool {
for _, tc := range s.testCases {
runTestCase(t, tc)
}
return true
}
func runTestCase(t *testing.T, tc scaseT) bool {
// var client *client.Client
// client, err := getClient(tc.Input.LoadResources)
// generate mock client if resources are to be loaded
// - create mock client
// - load resources
client := getClient(t, tc.Input.LoadResources)
t.Log(client)
// apply policy
// convert policy -> kyverno.Policy
policy := loadPolicy(t, tc.Input.Policy)
fmt.Println(policy)
// convert resource -> unstructured.Unstructured
resource := loadPolicyResource(t, tc.Input.Resource)
glog.Info(resource)
var er engine.EngineResponseNew
// Mutation
er = engine.MutateNew(*policy, *resource)
func() {
if data, err := json.Marshal(er.PolicyResponse); err == nil {
t.Log(string(data))
fmt.Println(string(data))
for _, r := range er.PolicyResponse.Rules {
for _, p := range r.Patches {
fmt.Println(string(p))
}
}
}
}()
validateResource(t, er.PatchedResource, tc.Expected.Mutation.PatchedResource)
validateResponse(t, er.PolicyResponse, tc.Expected.Mutation.PolicyResponse)
// Validation
// only compare the parametes specified ?
return true
}
func validateResource(t *testing.T, responseResource unstructured.Unstructured, expectedResourceFile string) {
if expectedResourceFile == "" {
t.Log("expected resource file not specified, wont compare resources")
return
}
// load expected resource
expectedResource := loadPolicyResource(t, expectedResourceFile)
if expectedResource == nil {
t.Log("failed to get the expected resource")
return
}
resourcePrint := func(obj unstructured.Unstructured) {
if data, err := obj.MarshalJSON(); err == nil {
fmt.Println(string(data))
}
}
resourcePrint(responseResource)
resourcePrint(*expectedResource)
// compare the resources
if !reflect.DeepEqual(responseResource, *expectedResource) {
t.Log("failed: response resource returned does not match expected resource")
}
t.Log("success: response resource returned matches expected resource")
}
func validateResponse(t *testing.T, er engine.PolicyResponse, expected engine.PolicyResponse) {
// cant do deepEquals and the stats will be different, or we nil those fields and then do a comparison
// forcing only the fields that are specified to be comprared
// doing a field by fields comparsion will allow us to provied more details logs and granular error reporting
// check policy name is same :P
if er.Policy != expected.Policy {
t.Log("Policy: error")
}
// compare resource spec
if er.Resource != expected.Resource {
t.Log("Resource: error")
}
// stats
if er.RulesAppliedCount != expected.RulesAppliedCount {
t.Log("RulesAppliedCount: error")
}
// rules
if len(er.Rules) != len(er.Rules) {
t.Log("rule count: error")
}
if len(expected.Rules) == len(expected.Rules) {
// if there are rules being applied then we compare the rule response
// as the rules are applied in the order defined, the comparions of rules will be in order
for index, r := range expected.Rules {
compareRules(t, r, expected.Rules[index])
}
}
}
func compareResourceSpec(t *testing.T, resource engine.ResourceSpec, expectedResource engine.ResourceSpec) {
// kind
if resource.Kind != expectedResource.Kind {
t.Error("error: kind")
}
// apiVersion
if resource.APIVersion != expectedResource.APIVersion {
t.Error("error: apiVersion")
}
// namespace
if resource.Namespace != expectedResource.Namespace {
t.Error("error: namespace")
}
// name
if resource.Name != expectedResource.Name {
t.Error("error: name")
}
}
func compareRules(t *testing.T, rule engine.RuleResponse, expectedRule engine.RuleResponse) {
// name
if rule.Name != expectedRule.Name {
t.Logf("error rule %s: name", rule.Name)
// as the rule names dont match no need to compare the rest of the information
return
}
// type
if rule.Type != expectedRule.Type {
t.Log("error: typw")
}
// message
if rule.Message != expectedRule.Message {
t.Log("error: message")
}
// // patches
// if reflect.DeepEqual(rule.Patches, expectedRule.Patches) {
// t.Log("error: patches")
// }
// success
if rule.Success != expectedRule.Success {
t.Log("error: success")
}
// statistics
}
func loadPolicyResource(t *testing.T, file string) *unstructured.Unstructured {
// expect only one resource to be specified in the YAML
resources := loadResource(t, file)
if resources == nil {
t.Log("no resource specified")
return nil
}
if len(resources) > 1 {
t.Logf("more than one resource specified in the file %s", file)
t.Log("considering the first one for policy application")
}
return resources[0]
}
func getClient(t *testing.T, files []string) *client.Client {
if files == nil {
t.Log("no resources to load, not createing mock client")
return nil
}
var objects []runtime.Object
if files != nil {
glog.V(4).Infof("loading resources:")
for _, file := range files {
objects = loadObjects(t, file)
}
}
// create mock client
scheme := runtime.NewScheme()
// mock client expects the resource to be as runtime.Object
c, err := client.NewMockClient(scheme, objects...)
if err != nil {
t.Errorf("failed to create client. %v", err)
return nil
}
t.Log("created mock client with pre-loaded resources")
return c
}
func loadResource(t *testing.T, path string) []*unstructured.Unstructured {
var unstrResources []*unstructured.Unstructured
t.Logf("loading resource from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
rBytes := bytes.Split(data, []byte("---"))
for _, r := range rBytes {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(r, nil, nil)
if err != nil {
t.Logf("failed to decode resource: %v", err)
continue
}
glog.Info(gvk)
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
if err != nil {
t.Logf("failed to unmarshall resource. %v", err)
continue
}
unstr := unstructured.Unstructured{Object: data}
t.Logf("loaded resource %s/%s/%s", unstr.GetKind(), unstr.GetNamespace(), unstr.GetName())
unstrResources = append(unstrResources, &unstr)
}
return unstrResources
}
func loadObjects(t *testing.T, path string) []runtime.Object {
var resources []runtime.Object
t.Logf("loading objects from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
rBytes := bytes.Split(data, []byte("---"))
for _, r := range rBytes {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(r, nil, nil)
if err != nil {
t.Logf("failed to decode resource: %v", err)
continue
}
t.Log(gvk)
//TODO: add more details
t.Logf("loaded object %s", gvk.Kind)
resources = append(resources, obj)
}
return resources
}
func loadPolicy(t *testing.T, path string) *kyverno.Policy {
t.Logf("loading policy from %s", path)
data, err := loadFile(t, path)
if err != nil {
return nil
}
var policies []*kyverno.Policy
pBytes := bytes.Split(data, []byte("---"))
for _, p := range pBytes {
policy := kyverno.Policy{}
pBytes, err := apiyaml.ToJSON(p)
if err != nil {
glog.Error(err)
continue
}
if err := json.Unmarshal(pBytes, &policy); err != nil {
t.Logf("failed to marshall polic. %v", err)
continue
}
t.Logf("loaded policy %s", policy.Name)
policies = append(policies, &policy)
}
if len(policies) == 0 {
t.Log("no policies loaded")
return nil
}
if len(policies) > 1 {
t.Log("more than one policy defined, considering first for processing")
}
return policies[0]
}

View file

@ -1,258 +1,258 @@
package testrunner
import (
"fmt"
"reflect"
"strconv"
"testing"
// import (
// "fmt"
// "reflect"
// "strconv"
// "testing"
ospath "path"
// ospath "path"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info"
kscheme "k8s.io/client-go/kubernetes/scheme"
)
// "github.com/golang/glog"
// kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// client "github.com/nirmata/kyverno/pkg/dclient"
// "github.com/nirmata/kyverno/pkg/engine"
// // "github.com/nirmata/kyverno/pkg/info"
// kscheme "k8s.io/client-go/kubernetes/scheme"
// )
type test struct {
ap string
t *testing.T
testCase *testCase
// input
policy *kyverno.Policy
tResource *resourceInfo
loadResources []*resourceInfo
// expected
genResources []*resourceInfo
patchedResource *resourceInfo
}
// type test struct {
// ap string
// t *testing.T
// testCase *testCase
// // input
// policy *kyverno.Policy
// tResource *resourceInfo
// loadResources []*resourceInfo
// // expected
// genResources []*resourceInfo
// patchedResource *resourceInfo
// }
func (t *test) run() {
var client *client.Client
var err error
//mock client is used if generate is defined
if t.testCase.Expected.Generation != nil {
// create mock client & load resources
client, err = createClient(t.loadResources)
if err != nil {
t.t.Errorf("Unable to create client. err %s", err)
}
// TODO: handle generate
// assuming its namespaces creation
decode := kscheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(t.tResource.rawResource), nil, nil)
_, err = client.CreateResource(t.tResource.gvk.Kind, "", obj, false)
if err != nil {
t.t.Errorf("error while creating namespace %s", err)
}
// func (t *test) run() {
// var client *client.Client
// var err error
// //mock client is used if generate is defined
// if t.testCase.Expected.Generation != nil {
// // create mock client & load resources
// client, err = createClient(t.loadResources)
// if err != nil {
// t.t.Errorf("Unable to create client. err %s", err)
// }
// // TODO: handle generate
// // assuming its namespaces creation
// decode := kscheme.Codecs.UniversalDeserializer().Decode
// obj, _, err := decode([]byte(t.tResource.rawResource), nil, nil)
// _, err = client.CreateResource(t.tResource.gvk.Kind, "", obj, false)
// if err != nil {
// t.t.Errorf("error while creating namespace %s", err)
// }
}
// apply the policy engine
pr, policyInfo, err := t.applyPolicy(t.policy, t.tResource, client)
if err != nil {
t.t.Error(err)
return
}
// Expected Result
// Test succesfuly ?
t.overAllPass(policyInfo.IsSuccessful(), t.testCase.Expected.Passes)
t.checkMutationResult(pr, policyInfo)
t.checkValidationResult(policyInfo)
t.checkGenerationResult(client, policyInfo)
}
// }
// // apply the policy engine
// pr, policyInfo, err := t.applyPolicy(t.policy, t.tResource, client)
// if err != nil {
// t.t.Error(err)
// return
// }
// // Expected Result
// // Test succesfuly ?
// t.overAllPass(policyInfo.IsSuccessful(), t.testCase.Expected.Passes)
// t.checkMutationResult(pr, policyInfo)
// t.checkValidationResult(policyInfo)
// t.checkGenerationResult(client, policyInfo)
// }
func (t *test) checkMutationResult(pr *resourceInfo, policyInfo info.PolicyInfo) {
if t.testCase.Expected.Mutation == nil {
glog.Info("No Mutation check defined")
return
}
// patched resource
if !compareResource(pr, t.patchedResource) {
fmt.Println(string(t.patchedResource.rawResource))
fmt.Println(string(pr.rawResource))
glog.Warningf("Expected resource %s ", string(pr.rawResource))
t.t.Error("Patched resources not as expected")
}
// func (t *test) checkMutationResult(pr *resourceInfo, policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Mutation == nil {
// glog.Info("No Mutation check defined")
// return
// }
// // patched resource
// if !compareResource(pr, t.patchedResource) {
// fmt.Println(string(t.patchedResource.rawResource))
// fmt.Println(string(pr.rawResource))
// glog.Warningf("Expected resource %s ", string(pr.rawResource))
// t.t.Error("Patched resources not as expected")
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Mutation.Rules)
}
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Mutation.Rules)
// }
func (t *test) overAllPass(result bool, expected string) {
b, err := strconv.ParseBool(expected)
if err != nil {
t.t.Error(err)
}
if result != b {
t.t.Errorf("Expected value %v and actual value %v dont match", expected, result)
}
}
// func (t *test) overAllPass(result bool, expected string) {
// b, err := strconv.ParseBool(expected)
// if err != nil {
// t.t.Error(err)
// }
// if result != b {
// t.t.Errorf("Expected value %v and actual value %v dont match", expected, result)
// }
// }
func (t *test) compareRules(ruleInfos []info.RuleInfo, rules []tRules) {
// Compare the rules specified in the expected against the actual rule info returned by the apply policy
for _, eRule := range rules {
// Look-up the rule from the policy info
rule := lookUpRule(eRule.Name, ruleInfos)
if reflect.DeepEqual(rule, info.RuleInfo{}) {
t.t.Errorf("Rule with name %s not found", eRule.Name)
continue
}
// get the corresponding rule
if rule.Name != eRule.Name {
t.t.Errorf("Rule Name not matching!. expected %s , actual %s", eRule.Name, rule.Name)
}
if rule.RuleType.String() != eRule.Type {
t.t.Errorf("Rule type mismatch!. expected %s, actual %s", eRule.Type, rule.RuleType.String())
}
if len(eRule.Messages) != len(rule.Msgs) {
t.t.Errorf("Number of rule messages not same. expected %d, actual %d", len(eRule.Messages), len(rule.Msgs))
}
for i, msg := range eRule.Messages {
if msg != rule.Msgs[i] {
t.t.Errorf("Messges dont match!. expected %s, actual %s", msg, rule.Msgs[i])
}
}
}
}
// func (t *test) compareRules(ruleInfos []info.RuleInfo, rules []tRules) {
// // Compare the rules specified in the expected against the actual rule info returned by the apply policy
// for _, eRule := range rules {
// // Look-up the rule from the policy info
// rule := lookUpRule(eRule.Name, ruleInfos)
// if reflect.DeepEqual(rule, info.RuleInfo{}) {
// t.t.Errorf("Rule with name %s not found", eRule.Name)
// continue
// }
// // get the corresponding rule
// if rule.Name != eRule.Name {
// t.t.Errorf("Rule Name not matching!. expected %s , actual %s", eRule.Name, rule.Name)
// }
// if rule.RuleType.String() != eRule.Type {
// t.t.Errorf("Rule type mismatch!. expected %s, actual %s", eRule.Type, rule.RuleType.String())
// }
// if len(eRule.Messages) != len(rule.Msgs) {
// t.t.Errorf("Number of rule messages not same. expected %d, actual %d", len(eRule.Messages), len(rule.Msgs))
// }
// for i, msg := range eRule.Messages {
// if msg != rule.Msgs[i] {
// t.t.Errorf("Messges dont match!. expected %s, actual %s", msg, rule.Msgs[i])
// }
// }
// }
// }
func lookUpRule(name string, ruleInfos []info.RuleInfo) info.RuleInfo {
// func lookUpRule(name string, ruleInfos []info.RuleInfo) info.RuleInfo {
for _, r := range ruleInfos {
if r.Name == name {
return r
}
}
return info.RuleInfo{}
}
// for _, r := range ruleInfos {
// if r.Name == name {
// return r
// }
// }
// return info.RuleInfo{}
// }
func (t *test) checkValidationResult(policyInfo info.PolicyInfo) {
if t.testCase.Expected.Validation == nil {
glog.Info("No Validation check defined")
return
}
// func (t *test) checkValidationResult(policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Validation == nil {
// glog.Info("No Validation check defined")
// return
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Validation.Rules)
}
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Validation.Rules)
// }
func (t *test) checkGenerationResult(client *client.Client, policyInfo info.PolicyInfo) {
if t.testCase.Expected.Generation == nil {
glog.Info("No Generate check defined")
return
}
if client == nil {
t.t.Error("client needs to be configured")
}
// func (t *test) checkGenerationResult(client *client.Client, policyInfo info.PolicyInfo) {
// if t.testCase.Expected.Generation == nil {
// glog.Info("No Generate check defined")
// return
// }
// if client == nil {
// t.t.Error("client needs to be configured")
// }
// check if rules match
t.compareRules(policyInfo.Rules, t.testCase.Expected.Generation.Rules)
// // check if rules match
// t.compareRules(policyInfo.Rules, t.testCase.Expected.Generation.Rules)
// check if the expected resources are generated
for _, r := range t.genResources {
n := ParseNameFromObject(r.rawResource)
ns := ParseNamespaceFromObject(r.rawResource)
_, err := client.GetResource(r.gvk.Kind, ns, n)
if err != nil {
t.t.Errorf("Resource %s/%s of kinf %s not found", ns, n, r.gvk.Kind)
}
// compare if the resources are same
//TODO: comapre []bytes vs unstrcutured resource
}
}
// // check if the expected resources are generated
// for _, r := range t.genResources {
// n := ParseNameFromObject(r.rawResource)
// ns := ParseNamespaceFromObject(r.rawResource)
// _, err := client.GetResource(r.gvk.Kind, ns, n)
// if err != nil {
// t.t.Errorf("Resource %s/%s of kinf %s not found", ns, n, r.gvk.Kind)
// }
// // compare if the resources are same
// //TODO: comapre []bytes vs unstrcutured resource
// }
// }
func (t *test) applyPolicy(policy *kyverno.Policy,
tresource *resourceInfo,
client *client.Client) (*resourceInfo, info.PolicyInfo, error) {
// apply policy on the trigger resource
// Mutate
var zeroPolicyInfo info.PolicyInfo
var err error
rawResource := tresource.rawResource
rname := engine.ParseNameFromObject(rawResource)
rns := engine.ParseNamespaceFromObject(rawResource)
rkind := engine.ParseKindFromObject(rawResource)
policyInfo := info.NewPolicyInfo(policy.Name,
rkind,
rname,
rns,
policy.Spec.ValidationFailureAction)
// func (t *test) applyPolicy(policy *kyverno.Policy,
// tresource *resourceInfo,
// client *client.Client) (*resourceInfo, info.PolicyInfo, error) {
// // apply policy on the trigger resource
// // Mutate
// var zeroPolicyInfo info.PolicyInfo
// var err error
// rawResource := tresource.rawResource
// rname := engine.ParseNameFromObject(rawResource)
// rns := engine.ParseNamespaceFromObject(rawResource)
// rkind := engine.ParseKindFromObject(rawResource)
// policyInfo := info.NewPolicyInfo(policy.Name,
// rkind,
// rname,
// rns,
// policy.Spec.ValidationFailureAction)
resource, err := ConvertToUnstructured(rawResource)
if err != nil {
return nil, zeroPolicyInfo, err
}
// resource, err := ConvertToUnstructured(rawResource)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// Apply Mutation Rules
engineResponse := engine.Mutate(*policy, *resource)
// patches, ruleInfos := engine.Mutate(*policy, rawResource, *tresource.gvk)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// TODO: only validate if there are no errors in mutate, why?
if policyInfo.IsSuccessful() {
if len(engineResponse.Patches) != 0 {
rawResource, err = engine.ApplyPatches(rawResource, engineResponse.Patches)
if err != nil {
return nil, zeroPolicyInfo, err
}
}
}
// Validate
engineResponse = engine.Validate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if err != nil {
return nil, zeroPolicyInfo, err
}
// // Apply Mutation Rules
// engineResponse := engine.Mutate(*policy, *resource)
// // patches, ruleInfos := engine.Mutate(*policy, rawResource, *tresource.gvk)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// // TODO: only validate if there are no errors in mutate, why?
// if policyInfo.IsSuccessful() {
// if len(engineResponse.Patches) != 0 {
// rawResource, err = engine.ApplyPatches(rawResource, engineResponse.Patches)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// }
// }
// // Validate
// engineResponse = engine.Validate(*policy, *resource)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
if rkind == "Namespace" {
if client != nil {
engineResponse := engine.Generate(client, *policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
}
}
// Generate
// transform the patched Resource into resource Info
ri, err := extractResourceRaw(rawResource)
if err != nil {
return nil, zeroPolicyInfo, err
}
// return the results
return ri, policyInfo, nil
}
// if rkind == "Namespace" {
// if client != nil {
// engineResponse := engine.Generate(client, *policy, *resource)
// policyInfo.AddRuleInfos(engineResponse.RuleInfos)
// }
// }
// // Generate
// // transform the patched Resource into resource Info
// ri, err := extractResourceRaw(rawResource)
// if err != nil {
// return nil, zeroPolicyInfo, err
// }
// // return the results
// return ri, policyInfo, nil
// }
func NewTest(ap string, t *testing.T, tc *testCase) (*test, error) {
//---INPUT---
p, err := tc.loadPolicy(ospath.Join(ap, tc.Input.Policy))
if err != nil {
return nil, err
}
r, err := tc.loadTriggerResource(ap)
if err != nil {
return nil, err
}
// func NewTest(ap string, t *testing.T, tc *testCase) (*test, error) {
// //---INPUT---
// p, err := tc.loadPolicy(ospath.Join(ap, tc.Input.Policy))
// if err != nil {
// return nil, err
// }
// r, err := tc.loadTriggerResource(ap)
// if err != nil {
// return nil, err
// }
lr, err := tc.loadPreloadedResources(ap)
if err != nil {
return nil, err
}
// lr, err := tc.loadPreloadedResources(ap)
// if err != nil {
// return nil, err
// }
//---EXPECTED---
pr, err := tc.loadPatchedResource(ap)
if err != nil {
return nil, err
}
gr, err := tc.loadGeneratedResources(ap)
if err != nil {
return nil, err
}
return &test{
ap: ap,
t: t,
testCase: tc,
policy: p,
tResource: r,
loadResources: lr,
genResources: gr,
patchedResource: pr,
}, nil
}
// //---EXPECTED---
// pr, err := tc.loadPatchedResource(ap)
// if err != nil {
// return nil, err
// }
// gr, err := tc.loadGeneratedResources(ap)
// if err != nil {
// return nil, err
// }
// return &test{
// ap: ap,
// t: t,
// testCase: tc,
// policy: p,
// tResource: r,
// loadResources: lr,
// genResources: gr,
// patchedResource: pr,
// }, nil
// }

View file

@ -1,186 +1,186 @@
package testrunner
import (
"bytes"
"encoding/json"
"fmt"
ospath "path"
// import (
// "bytes"
// "encoding/json"
// "fmt"
// ospath "path"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes/scheme"
)
// "github.com/golang/glog"
// kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/runtime"
// yaml "k8s.io/apimachinery/pkg/util/yaml"
// "k8s.io/client-go/kubernetes/scheme"
// )
//testCase defines the input and the expected result
// it stores the path to the files that are to be loaded
// for references
type testCase struct {
Input *tInput `yaml:"input"`
Expected *tExpected `yaml:"expected"`
}
// //testCase defines the input and the expected result
// // it stores the path to the files that are to be loaded
// // for references
// type testCase struct {
// Input *tInput `yaml:"input"`
// Expected *tExpected `yaml:"expected"`
// }
// load resources store the resources that are pre-requisite
// for the test case and are pre-loaded in the client before
/// test case in evaluated
type tInput struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
LoadResources []string `yaml:"load_resources,omitempty"`
}
// // load resources store the resources that are pre-requisite
// // for the test case and are pre-loaded in the client before
// /// test case in evaluated
// type tInput struct {
// Policy string `yaml:"policy"`
// Resource string `yaml:"resource"`
// LoadResources []string `yaml:"load_resources,omitempty"`
// }
type tExpected struct {
Passes string `yaml:"passes"`
Mutation *tMutation `yaml:"mutation,omitempty"`
Validation *tValidation `yaml:"validation,omitempty"`
Generation *tGeneration `yaml:"generation,omitempty"`
}
// type tExpected struct {
// Passes string `yaml:"passes"`
// Mutation *tMutation `yaml:"mutation,omitempty"`
// Validation *tValidation `yaml:"validation,omitempty"`
// Generation *tGeneration `yaml:"generation,omitempty"`
// }
type tMutation struct {
PatchedResource string `yaml:"patched_resource,omitempty"`
Rules []tRules `yaml:"rules"`
}
// type tMutation struct {
// PatchedResource string `yaml:"patched_resource,omitempty"`
// Rules []tRules `yaml:"rules"`
// }
type tValidation struct {
Rules []tRules `yaml:"rules"`
}
// type tValidation struct {
// Rules []tRules `yaml:"rules"`
// }
type tGeneration struct {
Resources []string `yaml:"resources"`
Rules []tRules `yaml:"rules"`
}
// type tGeneration struct {
// Resources []string `yaml:"resources"`
// Rules []tRules `yaml:"rules"`
// }
type tRules struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Messages []string `yaml:"messages"`
}
// type tRules struct {
// Name string `yaml:"name"`
// Type string `yaml:"type"`
// Messages []string `yaml:"messages"`
// }
type tResult struct {
Reason string `yaml:"reason, omitempty"`
}
// type tResult struct {
// Reason string `yaml:"reason, omitempty"`
// }
func (tc *testCase) policyEngineTest() {
// func (tc *testCase) policyEngineTest() {
}
func (tc *testCase) loadPreloadedResources(ap string) ([]*resourceInfo, error) {
return loadResources(ap, tc.Input.LoadResources...)
// return loadResources(ap, tc.Input.LoadResources...)
}
// }
// func (tc *testCase) loadPreloadedResources(ap string) ([]*resourceInfo, error) {
// return loadResources(ap, tc.Input.LoadResources...)
// // return loadResources(ap, tc.Input.LoadResources...)
// }
func (tc *testCase) loadGeneratedResources(ap string) ([]*resourceInfo, error) {
if tc.Expected.Generation == nil {
return nil, nil
}
return loadResources(ap, tc.Expected.Generation.Resources...)
}
// func (tc *testCase) loadGeneratedResources(ap string) ([]*resourceInfo, error) {
// if tc.Expected.Generation == nil {
// return nil, nil
// }
// return loadResources(ap, tc.Expected.Generation.Resources...)
// }
func (tc *testCase) loadPatchedResource(ap string) (*resourceInfo, error) {
if tc.Expected.Mutation == nil {
return nil, nil
}
rs, err := loadResources(ap, tc.Expected.Mutation.PatchedResource)
if err != nil {
return nil, err
}
if len(rs) != 1 {
glog.Warning("expects single resource mutation but multiple defined, will use first one")
}
return rs[0], nil
// func (tc *testCase) loadPatchedResource(ap string) (*resourceInfo, error) {
// if tc.Expected.Mutation == nil {
// return nil, nil
// }
// rs, err := loadResources(ap, tc.Expected.Mutation.PatchedResource)
// if err != nil {
// return nil, err
// }
// if len(rs) != 1 {
// glog.Warning("expects single resource mutation but multiple defined, will use first one")
// }
// return rs[0], nil
}
func (tc *testCase) loadResources(files []string) ([]*resourceInfo, error) {
lr := []*resourceInfo{}
for _, r := range files {
rs, err := loadResources(r)
if err != nil {
// return as test case will be invalid if a resource cannot be loaded
return nil, err
}
lr = append(lr, rs...)
}
return lr, nil
}
// }
// func (tc *testCase) loadResources(files []string) ([]*resourceInfo, error) {
// lr := []*resourceInfo{}
// for _, r := range files {
// rs, err := loadResources(r)
// if err != nil {
// // return as test case will be invalid if a resource cannot be loaded
// return nil, err
// }
// lr = append(lr, rs...)
// }
// return lr, nil
// }
func (tc *testCase) loadTriggerResource(ap string) (*resourceInfo, error) {
rs, err := loadResources(ap, tc.Input.Resource)
if err != nil {
return nil, err
}
if len(rs) != 1 {
glog.Warning("expects single resource trigger but multiple defined, will use first one")
}
return rs[0], nil
// func (tc *testCase) loadTriggerResource(ap string) (*resourceInfo, error) {
// rs, err := loadResources(ap, tc.Input.Resource)
// if err != nil {
// return nil, err
// }
// if len(rs) != 1 {
// glog.Warning("expects single resource trigger but multiple defined, will use first one")
// }
// return rs[0], nil
}
// }
// Loads a single policy
func (tc *testCase) loadPolicy(file string) (*kyverno.Policy, error) {
p := &kyverno.Policy{}
data, err := LoadFile(file)
if err != nil {
return nil, err
}
pBytes, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
if err := json.Unmarshal(pBytes, p); err != nil {
return nil, err
}
if p.TypeMeta.Kind != "Policy" {
return nil, fmt.Errorf("failed to parse policy")
}
return p, nil
}
// // Loads a single policy
// func (tc *testCase) loadPolicy(file string) (*kyverno.Policy, error) {
// p := &kyverno.Policy{}
// data, err := LoadFile(file)
// if err != nil {
// return nil, err
// }
// pBytes, err := yaml.ToJSON(data)
// if err != nil {
// return nil, err
// }
// if err := json.Unmarshal(pBytes, p); err != nil {
// return nil, err
// }
// if p.TypeMeta.Kind != "Policy" {
// return nil, fmt.Errorf("failed to parse policy")
// }
// return p, nil
// }
// loads multiple resources
func loadResources(ap string, args ...string) ([]*resourceInfo, error) {
ris := []*resourceInfo{}
for _, file := range args {
data, err := LoadFile(ospath.Join(ap, file))
if err != nil {
return nil, err
}
dd := bytes.Split(data, []byte(defaultYamlSeparator))
// resources seperated by yaml seperator
for _, d := range dd {
ri, err := extractResourceRaw(d)
if err != nil {
glog.Errorf("unable to load resource. err: %s ", err)
continue
}
ris = append(ris, ri)
}
}
return ris, nil
}
// // loads multiple resources
// func loadResources(ap string, args ...string) ([]*resourceInfo, error) {
// ris := []*resourceInfo{}
// for _, file := range args {
// data, err := LoadFile(ospath.Join(ap, file))
// if err != nil {
// return nil, err
// }
// dd := bytes.Split(data, []byte(defaultYamlSeparator))
// // resources seperated by yaml seperator
// for _, d := range dd {
// ri, err := extractResourceRaw(d)
// if err != nil {
// glog.Errorf("unable to load resource. err: %s ", err)
// continue
// }
// ris = append(ris, ri)
// }
// }
// return ris, nil
// }
func extractResourceRaw(d []byte) (*resourceInfo, error) {
// decode := scheme.Codecs.UniversalDeserializer().Decode
// obj, gvk, err := decode(d, nil, nil)
// if err != nil {
// return nil, err
// }
obj, gvk, err := extractResourceUnMarshalled(d)
// runtime.object to JSON
raw, err := json.Marshal(obj)
if err != nil {
return nil, err
}
return &resourceInfo{rawResource: raw,
gvk: gvk}, nil
}
// func extractResourceRaw(d []byte) (*resourceInfo, error) {
// // decode := scheme.Codecs.UniversalDeserializer().Decode
// // obj, gvk, err := decode(d, nil, nil)
// // if err != nil {
// // return nil, err
// // }
// obj, gvk, err := extractResourceUnMarshalled(d)
// // runtime.object to JSON
// raw, err := json.Marshal(obj)
// if err != nil {
// return nil, err
// }
// return &resourceInfo{rawResource: raw,
// gvk: gvk}, nil
// }
func extractResourceUnMarshalled(d []byte) (runtime.Object, *metav1.GroupVersionKind, error) {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode(d, nil, nil)
if err != nil {
return nil, nil, err
}
return obj, &metav1.GroupVersionKind{Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind}, nil
}
// func extractResourceUnMarshalled(d []byte) (runtime.Object, *metav1.GroupVersionKind, error) {
// decode := scheme.Codecs.UniversalDeserializer().Decode
// obj, gvk, err := decode(d, nil, nil)
// if err != nil {
// return nil, nil, err
// }
// return obj, &metav1.GroupVersionKind{Group: gvk.Group,
// Version: gvk.Version,
// Kind: gvk.Kind}, nil
// }

View file

@ -1,98 +1,98 @@
package testrunner
import (
"bytes"
"io/ioutil"
"path/filepath"
"testing"
// import (
// "bytes"
// "io/ioutil"
// "path/filepath"
// "testing"
"os"
ospath "path"
// "os"
// ospath "path"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
)
// "github.com/golang/glog"
// "gopkg.in/yaml.v2"
// )
func runner(t *testing.T, relpath string) {
gp := os.Getenv("GOPATH")
ap := ospath.Join(gp, projectPath)
// build load scenarios
path := ospath.Join(ap, relpath)
// Load the scenario files
scenarioFiles := getYAMLfiles(path)
for _, secenarioFile := range scenarioFiles {
sc := newScenario(t, ap, secenarioFile)
if err := sc.load(); err != nil {
t.Error(err)
return
}
// run test cases
sc.run()
}
}
// func runner(t *testing.T, relpath string) {
// gp := os.Getenv("GOPATH")
// ap := ospath.Join(gp, projectPath)
// // build load scenarios
// path := ospath.Join(ap, relpath)
// // Load the scenario files
// scenarioFiles := getYAMLfiles(path)
// for _, secenarioFile := range scenarioFiles {
// sc := newScenario(t, ap, secenarioFile)
// if err := sc.load(); err != nil {
// t.Error(err)
// return
// }
// // run test cases
// sc.run()
// }
// }
type scenario struct {
ap string
t *testing.T
path string
tcs []*testCase
}
// type scenario struct {
// ap string
// t *testing.T
// path string
// tcs []*testCase
// }
func newScenario(t *testing.T, ap string, path string) *scenario {
return &scenario{
ap: ap,
t: t,
path: path,
}
}
// func newScenario(t *testing.T, ap string, path string) *scenario {
// return &scenario{
// ap: ap,
// t: t,
// path: path,
// }
// }
func getYAMLfiles(path string) (yamls []string) {
fileInfo, err := ioutil.ReadDir(path)
if err != nil {
return nil
}
for _, file := range fileInfo {
if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
yamls = append(yamls, ospath.Join(path, file.Name()))
}
}
return yamls
}
func (sc *scenario) load() error {
// read file
data, err := LoadFile(sc.path)
if err != nil {
return err
}
tcs := []*testCase{}
// load test cases seperated by '---'
// each test case defines an input & expected result
dd := bytes.Split(data, []byte(defaultYamlSeparator))
for _, d := range dd {
tc := &testCase{}
err := yaml.Unmarshal([]byte(d), tc)
if err != nil {
glog.Warningf("Error while decoding YAML object, err: %s", err)
continue
}
tcs = append(tcs, tc)
}
sc.tcs = tcs
return nil
}
// func getYAMLfiles(path string) (yamls []string) {
// fileInfo, err := ioutil.ReadDir(path)
// if err != nil {
// return nil
// }
// for _, file := range fileInfo {
// if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
// yamls = append(yamls, ospath.Join(path, file.Name()))
// }
// }
// return yamls
// }
// func (sc *scenario) load() error {
// // read file
// data, err := LoadFile(sc.path)
// if err != nil {
// return err
// }
// tcs := []*testCase{}
// // load test cases seperated by '---'
// // each test case defines an input & expected result
// dd := bytes.Split(data, []byte(defaultYamlSeparator))
// for _, d := range dd {
// tc := &testCase{}
// err := yaml.Unmarshal([]byte(d), tc)
// if err != nil {
// glog.Warningf("Error while decoding YAML object, err: %s", err)
// continue
// }
// tcs = append(tcs, tc)
// }
// sc.tcs = tcs
// return nil
// }
func (sc *scenario) run() {
if len(sc.tcs) == 0 {
sc.t.Error("No test cases to load")
return
}
// func (sc *scenario) run() {
// if len(sc.tcs) == 0 {
// sc.t.Error("No test cases to load")
// return
// }
for _, tc := range sc.tcs {
t, err := NewTest(sc.ap, sc.t, tc)
if err != nil {
sc.t.Error(err)
continue
}
t.run()
}
}
// for _, tc := range sc.tcs {
// t, err := NewTest(sc.ap, sc.t, tc)
// if err != nil {
// sc.t.Error(err)
// continue
// }
// t.run()
// }
// }

View file

@ -2,8 +2,17 @@ package testrunner
import "testing"
func TestCLI(t *testing.T) {
//https://github.com/nirmata/kyverno/issues/301
t.Skip("skipping testrunner as this needs a re-design")
runner(t, "/test/scenarios/cli")
// func TestCLI(t *testing.T) {
// //https://github.com/nirmata/kyverno/issues/301
// runner(t, "/test/scenarios/cli")
// }
func Test_Devlop(t *testing.T) {
//load scenario
scenario, err := loadScenario(t, "/test/scenarios/test/s1.yaml")
if err != nil {
t.Error(err)
}
runScenario(t, scenario)
}

View file

@ -0,0 +1,21 @@
# file path relative to project root
input:
policy: examples/cli/policy_deployment.yaml
resource: examples/cli/nginx.yaml
expected:
mutation:
patchedresource: test/output/nginx.yaml
policyresponse:
policy: policy-deployment
resource:
kind: Deployment
apiVersion: 'apps/v1'
namespace: ''
name: nginx-deployment
rules:
- name: add-label
type: Mutation
success: true
message: succesfully process JSON patches
# patches: `[{"path":"/metadata/labels/isMutated","op":"add","value":"true"},
# {"path":"/metadata/labels/app","op":"replace","value":"nginx_is_mutated"}]`