package testrunner import ( "fmt" "strconv" "testing" ospath "path" "github.com/golang/glog" pt "github.com/nirmata/kyverno/pkg/apis/policy/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 *pt.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) } } // 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") } // 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) 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 rule == nil { 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 { for _, r := range ruleInfos { if r.Name == name { return r } } return nil } 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) } 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 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 *pt.Policy, tresource *resourceInfo, client *client.Client) (*resourceInfo, *info.PolicyInfo, error) { // apply policy on the trigger resource // Mutate 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.Mode) // Apply Mutation Rules patches, ruleInfos := engine.Mutate(*policy, rawResource, *tresource.gvk) policyInfo.AddRuleInfos(ruleInfos) // TODO: only validate if there are no errors in mutate, why? if policyInfo.IsSuccessful() { if len(patches) != 0 { rawResource, err = engine.ApplyPatches(rawResource, patches) if err != nil { return nil, nil, err } } } // Validate ruleInfos, err = engine.Validate(*policy, rawResource, *tresource.gvk) policyInfo.AddRuleInfos(ruleInfos) if err != nil { return nil, nil, err } if rkind == "Namespace" { if client != nil { ruleInfos := engine.Generate(client, *policy, rawResource, *tresource.gvk, false) policyInfo.AddRuleInfos(ruleInfos) } } // Generate // transform the patched Resource into resource Info ri, err := extractResourceRaw(rawResource) if err != nil { return nil, nil, 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 } 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 }