1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00
kyverno/pkg/testutils/testbundle.go
2019-06-14 15:20:05 -07:00

294 lines
8 KiB
Go

package testutils
import (
"bytes"
"fmt"
"os"
ospath "path"
"testing"
"github.com/golang/glog"
policytypes "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/result"
"gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
kscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func NewTestBundle(path string) *testBundle {
return &testBundle{
path: path,
policies: make(map[string]*policytypes.Policy),
resources: make(map[string]*resourceInfo),
output: make(map[string]*resourceInfo),
}
}
func loadResources(tbPath string, rs map[string]*resourceInfo, file string) {
path := ospath.Join(tbPath, file)
_, err := os.Stat(path)
if os.IsNotExist(err) {
glog.Warningf("%s directory not present at %s", file, tbPath)
return
}
// Load the resources from the output folder
yamls := getYAMLfiles(path)
if len(yamls) == 0 {
glog.Warningf("No resource yaml found at path %s", path)
return
}
for _, r := range yamls {
resources, err := extractResource(r)
if err != nil {
glog.Errorf("unable to extract resource: %s", err)
}
mergeResources(rs, resources)
}
}
func (tb *testBundle) loadOutput() {
// check if output folder is defined
opath := ospath.Join(tb.path, outputFolder)
_, err := os.Stat(opath)
if os.IsNotExist(err) {
glog.Warningf("Output directory not present at %s", tb.path)
return
}
// Load the resources from the output folder
oYAMLs := getYAMLfiles(opath)
if len(oYAMLs) == 0 {
glog.Warningf("No resource yaml found at path %s", opath)
return
}
for _, r := range oYAMLs {
resources, err := extractResource(r)
if err != nil {
glog.Errorf("unable to extract resource: %s", err)
}
mergeResources(tb.output, resources)
}
}
func loadScenarios(tbPath string, file string) ([]*tScenario, error) {
// check if scenario yaml is defined
spath := ospath.Join(tbPath, file)
_, err := os.Stat(spath)
if os.IsNotExist(err) {
return nil, fmt.Errorf("Scenario file %s not defined at %s", file, tbPath)
}
ts := []*tScenario{}
// read the file
data, err := loadFile(spath)
if err != nil {
glog.Warningf("Error while loading file: %v\n", err)
return nil, err
}
dd := bytes.Split(data, []byte(defaultYamlSeparator))
for _, d := range dd {
s := &tScenario{}
err := yaml.Unmarshal([]byte(d), s)
if err != nil {
glog.Warningf("Error while decoding YAML object, err: %s", err)
continue
}
ts = append(ts, s)
}
return ts, nil
}
// Load test structure folder
func (tb *testBundle) load() error {
// scenario file defines the mapping of resources and policies
scenarios, err := loadScenarios(tb.path, tScenarioFile)
if err != nil {
return err
}
tb.scenarios = scenarios
// check if there are any files
pYAMLs := getYAMLfiles(tb.path)
if len(pYAMLs) == 0 {
return fmt.Errorf("No policy yaml found at path %s", tb.path)
}
for _, p := range pYAMLs {
// extract policy
policy, err := extractPolicy(p)
if err != nil {
glog.Errorf("unable to extract policy: %s", err)
continue
}
tb.policies[policy.GetName()] = policy
}
// load trigger resources
loadResources(tb.path, tb.resources, resourcesFolder)
// load output resources
loadResources(tb.path, tb.output, outputFolder)
return nil
}
func mergeResources(rs map[string]*resourceInfo, other map[string]*resourceInfo) {
for k, v := range other {
if _, ok := rs[k]; ok {
glog.Infof("resource already defined %s ", k)
continue
}
rs[k] = v
}
}
type testBundle struct {
path string
policies map[string]*policytypes.Policy
resources map[string]*resourceInfo
output map[string]*resourceInfo
scenarios []*tScenario
}
func (tb *testBundle) createClient(t *testing.T, resources []string) *dclient.Client {
scheme := runtime.NewScheme()
objects := []runtime.Object{}
// registered group versions
regResources := []schema.GroupVersionResource{}
for _, resource := range resources {
// get resources
r, ok := tb.resources[resource]
if !ok {
glog.Warningf("Resource %s not found", resource)
continue
}
// get group version resource
gv := schema.GroupVersion{Group: r.gvk.Group, Version: r.gvk.Version}
gvr := gv.WithResource(getResourceFromKind(r.gvk.Kind))
regResources = append(regResources, gvr)
decode := kscheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(r.rawResource), nil, nil)
if err != nil {
glog.Warning("Unable to deocde")
continue
}
// create unstructured
rdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
if err != nil {
fmt.Println(err)
continue
}
unstr := unstructured.Unstructured{Object: rdata}
objects = append(objects, &unstr)
}
// new mock client
// Mock Client
c, err := dclient.NewMockClient(scheme, objects...)
if err != nil {
t.Error(err)
}
// set discovery Client
c.SetDiscovery(dclient.NewFakeDiscoveryClient(regResources))
return c
}
func (tb *testBundle) run(t *testing.T, testingapplyTest IApplyTest) {
glog.Infof("Start: test on test bundles %s", tb.path)
// run each scenario
for _, ts := range tb.scenarios {
// TODO create client only for generate
// If there are init resources defined then load them
c := tb.createClient(t, ts.InitResources)
// get policy
p, ok := tb.policies[ts.Policy]
if !ok {
glog.Warningf("Policy %s not found", ts.Policy)
continue
}
// get resources
r, ok := tb.resources[ts.Resource]
if !ok {
glog.Warningf("Resource %s not found", ts.Resource)
continue
}
// TODO: handle generate
if ts.Generation != nil {
// assuming its namespaces creation
decode := kscheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(r.rawResource), nil, nil)
_, err = c.CreateResource(getResourceFromKind(r.gvk.Kind), "", obj)
if err != nil {
t.Errorf("error while creating namespace %s", ts.Resource)
}
}
mPatchedResource, mResult, vResult, err := testingapplyTest.applyPolicy(p, r, c)
if err != nil {
t.Error(err)
}
// check the expected scenario
tb.checkMutationResult(t, ts.Mutation, mPatchedResource, mResult)
tb.checkValidationResult(t, ts.Validation, vResult)
tb.checkGeneration(t, ts.Generation, c)
}
glog.Infof("Done: test on test bundles %s", tb.path)
}
func (tb *testBundle) checkGeneration(t *testing.T, expect *tGeneration, c *dclient.Client) {
if expect == nil {
glog.Info("No Generate check defined")
return
}
// iterate throught the expected resources and check if the client has them
for _, r := range expect.Resources {
_, err := c.GetResource(getResourceFromKind(r.Kind), r.Namespace, r.Name)
if err != nil {
t.Errorf("Resource %s/%s of kind %s not found", r.Namespace, r.Name, r.Kind)
}
}
}
func (tb *testBundle) checkValidationResult(t *testing.T, expect *tValidation, vResult result.Result) {
if expect == nil {
glog.Info("No Validation check defined")
return
}
// compare reason
if len(expect.Reason) > 0 && expect.Reason != vResult.GetReason().String() {
t.Error("Reason not matching")
}
// compare message
if len(expect.Message) > 0 && expect.Message != vResult.String() {
t.Error(("Message not matching"))
}
}
func (tb *testBundle) checkMutationResult(t *testing.T, expect *tMutation, pr *resourceInfo, mResult result.Result) {
if expect == nil {
glog.Info("No Mutation check defined")
return
}
// get expected patched resource
er, ok := tb.output[expect.MPatchedResource]
if !ok {
glog.Warningf("Resource %s not found", expect.MPatchedResource)
return
}
// compare patched resources
if !checkMutationRPatches(pr, er) {
fmt.Printf("Expected Resource %s \n", string(er.rawResource))
fmt.Printf("Patched Resource %s \n", string(pr.rawResource))
glog.Warningf("Expected resource %s ", string(pr.rawResource))
t.Error("Patched resources not as expected")
}
// compare reason
if len(expect.Reason) > 0 && expect.Reason != mResult.GetReason().String() {
t.Error("Reason not matching")
}
// compare message
if len(expect.Message) > 0 && expect.Message != mResult.String() {
t.Error(("Message not matching"))
}
}