1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

initial commit

This commit is contained in:
shivdudhani 2019-06-12 16:18:31 -07:00
parent 3db58d76d2
commit 4692058a4a
7 changed files with 540 additions and 0 deletions

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Endpoints
metadata:
name: test-endpoint
labels:
label : test
subsets:
- addresses:
- ip: 192.168.10.171
ports:
- name: secure-connection
port: 443
protocol: TCP

View file

@ -0,0 +1,15 @@
# input
policy: policy-endpoints
resource: test-endpoint
initResources:
# expected
mutation:
mPatchedResource: test-endpoint
reason: Success
validation:
reason: Success
---
policy: policy-endpoints
resource: test-endpoint
initResources:
mPatchedResource: test-endpoint

156
pkg/testutils/testbundle.go Normal file
View file

@ -0,0 +1,156 @@
package testutils
import (
"fmt"
"os"
ospath "path"
"testing"
"github.com/golang/glog"
policytypes "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
)
func NewTestBundle(path string) *testBundle {
return &testBundle{
path: path,
policies: make(map[string]*policytypes.Policy),
resources: make(map[string]*resourceInfo),
initResources: make(map[string]*resourceInfo),
}
}
func (tb *testBundle) load() error {
// check if resources folder is defined
rpath := ospath.Join(tb.path, resourcesFolder)
_, err := os.Stat(rpath)
if os.IsNotExist(err) {
glog.Warningf("Resources directory not present at %s", tb.path)
return fmt.Errorf("Resources directory not present at %s", tb.path)
}
// check if scenario yaml is defined
spath := ospath.Join(tb.path, tScenarioFile)
_, err = os.Stat(spath)
if os.IsNotExist(err) {
return fmt.Errorf("Scenario file %s not defined at %s", tScenarioFile, tb.path)
}
tb.scenarios, err = LoadScenarios(spath)
if err != nil {
return err
}
// 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
}
// extract resources
rYAMLs := getYAMLfiles(rpath)
if len(rYAMLs) == 0 {
return fmt.Errorf("No resource yaml found at path %s", rpath)
}
for _, r := range rYAMLs {
resources, err := extractResource(r)
if err != nil {
glog.Errorf("unable to extract resource: %s", err)
}
tb.mergeResources(resources)
}
return nil
}
func (tb *testBundle) mergeResources(rs map[string]*resourceInfo) {
for k, v := range rs {
if _, ok := tb.resources[k]; ok {
glog.Infof("resource already defined %s ", k)
continue
}
tb.resources[k] = v
}
}
type testBundle struct {
path string
policies map[string]*policytypes.Policy
resources map[string]*resourceInfo
initResources map[string]*resourceInfo
scenarios []*tScenario
}
func (tb *testBundle) run(t *testing.T, testingapplyTest IApplyTest) {
// run each scenario
for _, ts := range tb.scenarios {
// 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
mPatchedResource, mResult, vResult, err := testingapplyTest.applyPolicy(p, r, nil)
if err != nil {
t.Fatal(err)
}
// check the expected scenario
tb.checkMutationResult(t, ts.Mutation, mPatchedResource, mResult)
tb.checkValidationResult(t, ts.Validation, vResult)
}
}
func (tb *testBundle) checkValidationResult(t *testing.T, expect *tValidation, vResult result.Result) {
if expect == nil {
glog.Info("No Validation check defined")
return
}
// compare result
// 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
pr, ok := tb.resources[expect.MPatchedResource]
if !ok {
glog.Warningf("Resource %s not found", expect.MPatchedResource)
return
}
// compare patched resources
if !checkMutationRPatches(pr, pr) {
t.Error("Patched resources not as expected")
}
// compare result
// 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"))
}
}

View file

@ -0,0 +1,66 @@
package testutils
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/golang/glog"
)
//NewTestSuite returns new test suite
func NewTestSuite(t *testing.T, path string) *testSuite {
return &testSuite{
t: t,
path: path,
tb: []*testBundle{},
}
}
func (ts *testSuite) runTests() {
//TODO : make sure the implementation the interface is pointing to is not nil
if ts.applyTest == nil {
glog.Error("Apply Test set for the test suite")
return
}
// for each test bundle run the test scenario
for _, tb := range ts.tb {
tb.run(ts.t, ts.applyTest)
}
}
func (ts *testSuite) setApplyTest(applyTest IApplyTest) {
ts.applyTest = applyTest
}
type testSuite struct {
t *testing.T
path string
tb []*testBundle
applyTest IApplyTest
}
func (ts *testSuite) buildTestSuite() error {
// loading test bundles for test suite
fmt.Println(ts.path)
err := filepath.Walk(ts.path, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
// check if there are resources dir and policies yaml
tb := NewTestBundle(path)
if tb != nil {
// load resources
err := tb.load()
if err != nil {
// glog.Error(err)
return nil
}
ts.tb = append(ts.tb, tb)
// fmt.Println(path)
}
}
return nil
})
if err != nil {
ts.t.Fatal(err)
}
return nil
}

View file

@ -0,0 +1,54 @@
package testutils
import (
"fmt"
"io/ioutil"
ospath "path"
"path/filepath"
"testing"
)
// Load policy & resource files
// engine pass the (policy, resource)
// check the expected response
const examplesPath string = "examples"
const resourcesFolder string = "resources"
const tScenarioFile string = "testScenarios.yaml"
//LoadTestSuite reads the resource, policy and scenario files
func LoadTestSuite(t *testing.T, path string) *testSuite {
// gp := os.Getenv("GOPATH")
// ap := ospath.Join(gp, "src/github.com/nirmata/kyverno")
// build test suite
// each suite contains test bundles for test sceanrios
// ts := NewTestSuite(t, ospath.Join(ap, examplesPath))
ts := NewTestSuite(t, path)
ts.buildTestSuite()
return ts
}
func checkMutationRPatches(er *resourceInfo, pr *resourceInfo) bool {
if !er.isSame(*pr) {
getResourceYAML(pr.rawResource)
return false
}
return true
}
func getYAMLfiles(path string) (yamls []string) {
fileInfo, err := ioutil.ReadDir(path)
if err != nil {
return nil
}
for _, file := range fileInfo {
fmt.Println(filepath.Ext(file.Name()))
if file.Name() == tScenarioFile {
continue
}
if filepath.Ext(file.Name()) == ".yml" || filepath.Ext(file.Name()) == ".yaml" {
yamls = append(yamls, ospath.Join(path, file.Name()))
}
}
return yamls
}

View file

@ -0,0 +1,20 @@
package testutils
import (
"fmt"
"testing"
)
func TestUtils(t *testing.T) {
file := "/Users/shiv/nirmata/code/go/src/github.com/nirmata/kyverno/examples/mutate/patches"
ts := LoadTestSuite(t, file)
// policy application logic
tp := &testPolicy{}
ts.setApplyTest(tp)
// run the tests for each test bundle
ts.runTests()
if ts != nil {
fmt.Println("Done building the test bundles")
}
// run the tests against the policy engine
}

216
pkg/testutils/utils.go Normal file
View file

@ -0,0 +1,216 @@
package testutils
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
"github.com/golang/glog"
policytypes "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/result"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes/scheme"
)
const (
defaultYamlSeparator = "---"
)
func loadFile(fileDir string) ([]byte, error) {
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
return nil, err
}
return ioutil.ReadFile(fileDir)
}
func extractPolicy(fileDir string) (*policytypes.Policy, error) {
policy := &policytypes.Policy{}
file, err := loadFile(fileDir)
if err != nil {
return nil, fmt.Errorf("failed to load file: %v", err)
}
policyBytes, err := kyaml.ToJSON(file)
if err != nil {
return nil, err
}
if err := json.Unmarshal(policyBytes, policy); err != nil {
return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err)
}
if policy.TypeMeta.Kind != "Policy" {
return nil, fmt.Errorf("failed to parse policy")
}
return policy, nil
}
type resourceInfo struct {
rawResource []byte
gvk *metav1.GroupVersionKind
}
func (ri resourceInfo) isSame(other resourceInfo) bool {
// compare gvk
if ri.gvk != other.gvk {
return false
}
// compare rawResource
return bytes.Equal(ri.rawResource, other.rawResource)
}
func getResourceYAML(d []byte) {
fmt.Println(string(d))
// convert json to yaml
// print the result for reference
// can be used as a dry run the get the expected result
}
func extractResourceRaw(d []byte) (string, *resourceInfo) {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, gvk, err := decode([]byte(d), nil, nil)
if err != nil {
glog.Warningf("Error while decoding YAML object, err: %s\n", err)
return "", nil
}
raw, err := json.Marshal(obj)
if err != nil {
glog.Warningf("Error while marshalling manifest, err: %v\n", err)
return "", nil
}
gvkInfo := &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}
rn := ParseNameFromObject(raw)
rns := ParseNamespaceFromObject(raw)
if rns != "" {
rn = rns + "/" + rn
}
return rn, &resourceInfo{rawResource: raw, gvk: gvkInfo}
}
func extractResource(resource string) (map[string]*resourceInfo, error) {
resources := make(map[string]*resourceInfo)
data, err := loadFile(resource)
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 {
rn, r := extractResourceRaw(d)
resources[rn] = r
}
return resources, nil
}
//ParseNameFromObject extracts resource name from JSON obj
func ParseNameFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
meta := objectJSON["metadata"].(map[string]interface{})
if name, ok := meta["name"].(string); ok {
return name
}
return ""
}
// ParseNamespaceFromObject extracts the namespace from the JSON obj
func ParseNamespaceFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
meta := objectJSON["metadata"].(map[string]interface{})
if namespace, ok := meta["namespace"].(string); ok {
return namespace
}
return ""
}
type IApplyTest interface {
applyPolicy(policy *policytypes.Policy, resource *resourceInfo, client *client.Client) (*resourceInfo, result.Result, result.Result, error)
}
type testPolicy struct {
}
func (tp *testPolicy) applyPolicy(policy *policytypes.Policy, resource *resourceInfo, client *client.Client) (*resourceInfo, result.Result, result.Result, error) {
// apply policy on the trigger resource
// Mutate
var vResult result.Result
var patchedResource []byte
mPatches, mResult := engine.Mutate(*policy, resource.rawResource, *resource.gvk)
// TODO: only validate if there are no errors in mutate, why?
err := mResult.ToError()
if err == nil && len(mPatches) != 0 {
patchedResource, err = engine.ApplyPatches(resource.rawResource, mPatches)
if err != nil {
return nil, nil, nil, err
}
// Validate
vResult = engine.Validate(*policy, patchedResource, *resource.gvk)
}
// Generate
if client == nil {
glog.Warning("Client is required to test generate")
}
// transform the patched Resource into resource Info
_, ri := extractResourceRaw(patchedResource)
// return the results
return ri, mResult, vResult, nil
// TODO: merge the results for mutation and validation
}
type tScenario struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
InitResources []string `yaml:"initResources,omitempty"`
Mutation *tMutation `yaml:"mutation,omitempty"`
Validation *tValidation `yaml:"validation,omitempty"`
}
type tValidation struct {
Reason string `yaml:"reason,omitempty"`
Message string `yaml:"message,omitempty"`
Error string `yaml:"error,omitempty"`
}
type tMutation struct {
MPatchedResource string `yaml:"mPatchedResource,omitempty"`
Reason string `yaml:"reason,omitempty"`
Message string `yaml:"message,omitempty"`
Error string `yaml:"error,omitempty"`
}
func LoadScenarios(file string) ([]*tScenario, error) {
ts := []*tScenario{}
// read the file
data, err := loadFile(file)
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
}
fmt.Println(s.Policy)
fmt.Println(s.Resource)
ts = append(ts, s)
}
return ts, nil
}