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

310 lines
8 KiB
Go

package testutils
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
ospath "path"
"path/filepath"
"testing"
"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
}
func ParseApiVersionFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
if apiVersion, ok := objectJSON["apiVersion"].(string); ok {
return apiVersion
}
return ""
}
func ParseKindFromObject(bytes []byte) string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
if kind, ok := objectJSON["kind"].(string); ok {
return kind
}
return ""
}
//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 {
engine.Generate(client, *policy, resource.rawResource, *resource.gvk)
}
// 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"`
Generation *tGeneration `yaml:"generation,omitempty"`
}
type tGeneration struct {
Resources []tResource `yaml:"resource"`
}
type tResource struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace,omitempty"`
Kind string `yaml:"kind"`
}
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
}
ts = append(ts, s)
}
return ts, nil
}
// 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"
const outputFolder string = "output"
//LoadTestSuite reads the resource, policy and scenario files
func LoadTestSuite(t *testing.T, path string) *testSuite {
glog.Infof("loading test suites at %s", path)
// 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()
glog.Infof("done loading test suite at %s", path)
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 {
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
}
var kindToResource = map[string]string{
"ConfigMap": "configmaps",
"Endpoints": "endpoints",
"Namespace": "namespaces",
"Secret": "secrets",
"Deployment": "deployments",
"NetworkPolicy": "networkpolicies",
}
func getResourceFromKind(kind string) string {
if resource, ok := kindToResource[kind]; ok {
return resource
}
return ""
}