mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
Merge pull request #174 from nirmata/152_automate_testing
152 automate testing
This commit is contained in:
commit
f6ebb056a9
29 changed files with 986 additions and 0 deletions
21
examples/cli/testScenarios.yaml
Normal file
21
examples/cli/testScenarios.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# input
|
||||
policy: policy-deployment
|
||||
resource: nginx-deployment
|
||||
initResources:
|
||||
# expected
|
||||
mutation:
|
||||
mPatchedResource: nginx-deployment
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
||||
---
|
||||
# input
|
||||
policy: policy-deployment
|
||||
resource: ghost
|
||||
initResources:
|
||||
# expected
|
||||
mutation:
|
||||
mPatchedResource: ghost
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Failed
|
20
examples/generate/configMap_default.yaml
Normal file
20
examples/generate/configMap_default.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: config-template
|
||||
namespace: default
|
||||
labels:
|
||||
originalLabel : isHere
|
||||
data:
|
||||
ui.properties : |
|
||||
color.good=green
|
||||
color.bad=red
|
||||
|
||||
game.properties : |
|
||||
enemies=predators
|
||||
lives=3
|
||||
|
||||
configmap.data: |
|
||||
ns=default
|
||||
labels=originalLabel
|
||||
labelscount=1
|
36
examples/generate/policy_basic.yaml
Normal file
36
examples/generate/policy_basic.yaml
Normal file
|
@ -0,0 +1,36 @@
|
|||
apiVersion : kyverno.io/v1alpha1
|
||||
kind : Policy
|
||||
metadata :
|
||||
name : basic-policy
|
||||
spec :
|
||||
rules:
|
||||
- name: "Basic config generator for all namespaces"
|
||||
resource:
|
||||
kinds:
|
||||
- Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
generate:
|
||||
kind: ConfigMap
|
||||
name: default-config
|
||||
clone:
|
||||
namespace: default
|
||||
name: config-template
|
||||
- name: "Basic config generator for all namespaces"
|
||||
resource:
|
||||
kinds:
|
||||
- Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
generate:
|
||||
kind: Secret
|
||||
name: mongo-creds
|
||||
data:
|
||||
data:
|
||||
DB_USER: YWJyYWthZGFicmE=
|
||||
DB_PASSWORD: YXBwc3dvcmQ=
|
||||
metadata:
|
||||
labels:
|
||||
purpose: mongo
|
40
examples/generate/testScenarios.yaml
Normal file
40
examples/generate/testScenarios.yaml
Normal file
|
@ -0,0 +1,40 @@
|
|||
# input
|
||||
policy: basic-policy
|
||||
resource: ns2
|
||||
initResources:
|
||||
- default/config-template
|
||||
# expected
|
||||
generation:
|
||||
resource:
|
||||
- name: default-config
|
||||
namespace: ns2
|
||||
kind: ConfigMap
|
||||
- name: mongo-creds
|
||||
namespace: ns2
|
||||
kind: Secret
|
||||
---
|
||||
# input
|
||||
policy: zk-kafka-address
|
||||
resource: ns2
|
||||
initResources:
|
||||
- default/game-config
|
||||
# expected
|
||||
generation:
|
||||
resource:
|
||||
- name: copied-cm
|
||||
namespace: ns2
|
||||
kind: ConfigMap
|
||||
- name: zk-kafka-address
|
||||
namespace: ns2
|
||||
kind: ConfigMap
|
||||
---
|
||||
# input
|
||||
policy: default
|
||||
resource: ns2
|
||||
initResources:
|
||||
# expected
|
||||
generation:
|
||||
resource:
|
||||
- name: deny-all-traffic
|
||||
namespace: ns2
|
||||
kind: NetworkPolicy
|
|
@ -2,6 +2,7 @@ apiVersion: apps/v1
|
|||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
@ -11,6 +12,7 @@ spec:
|
|||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
|
10
examples/mutate/overlay/testScenarios.yaml
Normal file
10
examples/mutate/overlay/testScenarios.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
# input
|
||||
policy: set-image-pull-policy
|
||||
resource: nginx-deployment
|
||||
initResources:
|
||||
# expected
|
||||
mutation:
|
||||
mPatchedResource: nginx-deployment
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
21
examples/mutate/patches/testScenarios.yaml
Normal file
21
examples/mutate/patches/testScenarios.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# input
|
||||
policy: policy-endpoints
|
||||
resource: test-endpoint
|
||||
initResources:
|
||||
# expected
|
||||
mutation:
|
||||
mPatchedResource: test-endpoint
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
||||
---
|
||||
# input
|
||||
policy: policy-endpoints
|
||||
resource: test-endpoint
|
||||
initResources:
|
||||
# expected
|
||||
mutation:
|
||||
mPatchedResource: test-endpoint
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
|
@ -79,6 +79,7 @@ func (f *fixture) setupFixture() {
|
|||
if err != nil {
|
||||
f.t.Fatal(err)
|
||||
}
|
||||
|
||||
regresource := []schema.GroupVersionResource{
|
||||
schema.GroupVersionResource{Group: "kyverno.io",
|
||||
Version: "v1alpha1",
|
||||
|
|
|
@ -34,6 +34,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"},
|
||||
schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
||||
}
|
||||
|
||||
objects := []runtime.Object{newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||
|
@ -51,6 +52,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
// set discovery Client
|
||||
client.SetDiscovery(NewFakeDiscoveryClient(regResource))
|
||||
|
||||
|
||||
f := fixture{
|
||||
t: t,
|
||||
objects: objects,
|
||||
|
|
180
pkg/testrunner/test.go
Normal file
180
pkg/testrunner/test.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
package testrunner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/result"
|
||||
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(getResourceFromKind(t.tResource.gvk.Kind), "", obj, false)
|
||||
if err != nil {
|
||||
t.t.Errorf("error while creating namespace %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
// apply the policy engine
|
||||
pr, mResult, vResult, err := t.applyPolicy(t.policy, t.tResource, client)
|
||||
if err != nil {
|
||||
t.t.Error(err)
|
||||
return
|
||||
}
|
||||
// Expected Result
|
||||
t.checkMutationResult(pr, mResult)
|
||||
t.checkValidationResult(vResult)
|
||||
t.checkGenerationResult(client)
|
||||
}
|
||||
|
||||
func (t *test) checkMutationResult(pr *resourceInfo, result result.Result) {
|
||||
if t.testCase.Expected.Mutation == nil {
|
||||
glog.Info("No Mutation check defined")
|
||||
return
|
||||
}
|
||||
// patched resource
|
||||
if !compareResource(pr, t.patchedResource) {
|
||||
fmt.Printf("Expected Resource %s \n", string(t.patchedResource.rawResource))
|
||||
fmt.Printf("Patched Resource %s \n", string(pr.rawResource))
|
||||
glog.Warningf("Expected resource %s ", string(pr.rawResource))
|
||||
t.t.Error("Patched resources not as expected")
|
||||
}
|
||||
// reason
|
||||
reason := t.testCase.Expected.Mutation.Reason
|
||||
if len(reason) > 0 && result.GetReason().String() != reason {
|
||||
t.t.Error("Reason not matching")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *test) checkValidationResult(result result.Result) {
|
||||
if t.testCase.Expected.Validation == nil {
|
||||
glog.Info("No Validation check defined")
|
||||
return
|
||||
}
|
||||
// reason
|
||||
reason := t.testCase.Expected.Validation.Reason
|
||||
if len(reason) > 0 && result.GetReason().String() != reason {
|
||||
t.t.Error("Reason not matching")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *test) checkGenerationResult(client *client.Client) {
|
||||
if t.testCase.Expected.Generation == nil {
|
||||
glog.Info("No Generate check defined")
|
||||
return
|
||||
}
|
||||
if client == nil {
|
||||
glog.Info("client needs to be configured")
|
||||
}
|
||||
// check if the expected resources are generated
|
||||
for _, r := range t.genResources {
|
||||
n := ParseNameFromObject(r.rawResource)
|
||||
ns := ParseNamespaceFromObject(r.rawResource)
|
||||
_, err := client.GetResource(getResourceFromKind(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, result.Result, result.Result, error) {
|
||||
// apply policy on the trigger resource
|
||||
// Mutate
|
||||
var vResult result.Result
|
||||
var patchedResource []byte
|
||||
mPatches, mResult := engine.Mutate(*policy, tresource.rawResource, *tresource.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(tresource.rawResource, mPatches)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// Validate
|
||||
vResult = engine.Validate(*policy, patchedResource, *tresource.gvk)
|
||||
}
|
||||
// Generate
|
||||
if client != nil {
|
||||
engine.Generate(client, *policy, tresource.rawResource, *tresource.gvk)
|
||||
}
|
||||
// transform the patched Resource into resource Info
|
||||
ri, err := extractResourceRaw(patchedResource)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// return the results
|
||||
return ri, mResult, vResult, 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
|
||||
}
|
178
pkg/testrunner/testcase.go
Normal file
178
pkg/testrunner/testcase.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package testrunner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
ospath "path"
|
||||
|
||||
"github.com/golang/glog"
|
||||
pt "github.com/nirmata/kyverno/pkg/apis/policy/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"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Mutation *tMutation `yaml:"mutation,omitempty"`
|
||||
Validation *tValidation `yaml:"validation,omitempty"`
|
||||
Generation *tGeneration `yaml:"generation,omitempty"`
|
||||
}
|
||||
|
||||
type tMutation struct {
|
||||
Patched_Resource string `yaml:"patched_resource,omitempty"`
|
||||
tResult
|
||||
}
|
||||
|
||||
type tValidation struct {
|
||||
tResult
|
||||
}
|
||||
|
||||
type tGeneration struct {
|
||||
Resources []string `yaml:"resources"`
|
||||
}
|
||||
|
||||
type tResult struct {
|
||||
Reason string `yaml:"reason, omitempty"`
|
||||
}
|
||||
|
||||
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) 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.Patched_Resource)
|
||||
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) 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) (*pt.Policy, error) {
|
||||
p := &pt.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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
98
pkg/testrunner/testrunner.go
Normal file
98
pkg/testrunner/testrunner.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package testrunner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"os"
|
||||
ospath "path"
|
||||
|
||||
"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()
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
for _, tc := range sc.tcs {
|
||||
t, err := NewTest(sc.ap, sc.t, tc)
|
||||
if err != nil {
|
||||
sc.t.Error(err)
|
||||
continue
|
||||
}
|
||||
t.run()
|
||||
}
|
||||
}
|
7
pkg/testrunner/testrunner_test.go
Normal file
7
pkg/testrunner/testrunner_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package testrunner
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExamples(t *testing.T) {
|
||||
runner(t, "/test/scenarios")
|
||||
}
|
123
pkg/testrunner/utils.go
Normal file
123
pkg/testrunner/utils.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package testrunner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultYamlSeparator = "---"
|
||||
projectPath = "src/github.com/nirmata/kyverno"
|
||||
)
|
||||
|
||||
// LoadFile loads file in byte buffer
|
||||
func LoadFile(path string) ([]byte, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadFile(path)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// compare patched resources
|
||||
func compareResource(er *resourceInfo, pr *resourceInfo) bool {
|
||||
if !er.isSame(*pr) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func createClient(resources []*resourceInfo) (*client.Client, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
objects := []runtime.Object{}
|
||||
// registered group versions
|
||||
regResources := []schema.GroupVersionResource{}
|
||||
|
||||
for _, r := range resources {
|
||||
// registered gvr
|
||||
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)
|
||||
rdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to load resource. err %s", err)
|
||||
}
|
||||
unstr := unstructured.Unstructured{Object: rdata}
|
||||
objects = append(objects, &unstr)
|
||||
}
|
||||
// Mock Client
|
||||
c, err := client.NewMockClient(scheme, objects...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.SetDiscovery(client.NewFakeDiscoveryClient(regResources))
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
//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 ""
|
||||
}
|
16
test/output/cm_copied_cm.yaml
Normal file
16
test/output/cm_copied_cm.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
configmap.data: |
|
||||
ns=default
|
||||
labels=originalLabel
|
||||
labelscount=1
|
||||
game.properties: |
|
||||
enemies=predators
|
||||
lives=3
|
||||
ui.properties: "color.good=green\ncolor.bad=red \n"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
originalLabel: isHere
|
||||
name: copied-cm
|
||||
namespace: ns2
|
16
test/output/cm_default_config.yaml
Normal file
16
test/output/cm_default_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
configmap.data: |
|
||||
ns=default
|
||||
labels=originalLabel
|
||||
labelscount=1
|
||||
game.properties: |
|
||||
enemies=predators
|
||||
lives=3
|
||||
ui.properties: "color.good=green\ncolor.bad=red \n"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
originalLabel: isHere
|
||||
name: default-config
|
||||
namespace: ns2
|
8
test/output/cm_zk-kafka-address.yaml
Normal file
8
test/output/cm_zk-kafka-address.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
data:
|
||||
KAFKA_ADDRESS: 192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092
|
||||
ZK_ADDRESS: 192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: zk-kafka-address
|
||||
namespace: ns2
|
40
test/output/ghost.yaml
Normal file
40
test/output/ghost.yaml
Normal file
|
@ -0,0 +1,40 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: ghost
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx_is_mutated
|
||||
cli: test
|
||||
isMutated: 'true'
|
||||
nirmata.io/application.name: ghost
|
||||
nirmata.io/component: ghost
|
||||
nirmata.io/deployment.name: ghost
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
nirmata.io/application.name: ghost
|
||||
nirmata.io/component: ghost
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
labels:
|
||||
nirmata.io/application.name: ghost
|
||||
nirmata.io/component: ghost
|
||||
nirmata.io/deployment.name: ghost
|
||||
spec:
|
||||
containers:
|
||||
- name: ghost
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
resources: {}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
revisionHistoryLimit: 5
|
||||
status: {}
|
29
test/output/nginx.yaml
Normal file
29
test/output/nginx.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx_is_mutated
|
||||
cli: test
|
||||
isMutated: 'true'
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources: {}
|
||||
imagePullPolicy: Always
|
||||
strategy: {}
|
||||
status: {}
|
13
test/output/np_deny-all-traffic.yaml
Normal file
13
test/output/np_deny-all-traffic.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
policyname: default
|
||||
name: deny-all-traffic
|
||||
namespace: ns2
|
||||
podSelector:
|
||||
matchExpressions: []
|
||||
matchLabels: {}
|
||||
policyTypes: []
|
||||
spec:
|
||||
kind: NetworkPolicy
|
||||
apiVersion: extensions/v1beta1
|
31
test/output/op_overlay_nginx.yaml
Normal file
31
test/output/op_overlay_nginx.yaml
Normal file
|
@ -0,0 +1,31 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources: {}
|
||||
imagePullPolicy: Always
|
||||
- name: ghost
|
||||
image: ghost:latest
|
||||
resources: {}
|
||||
imagePullPolicy: Always
|
||||
strategy: {}
|
||||
status: {}
|
20
test/output/op_patches_endpoints.yaml
Normal file
20
test/output/op_patches_endpoints.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: test-endpoint
|
||||
creationTimestamp:
|
||||
labels:
|
||||
label: test
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 192.168.10.172
|
||||
ports:
|
||||
- name: load-balancer-connection
|
||||
port: 80
|
||||
protocol: UDP
|
||||
- addresses:
|
||||
- ip: 192.168.10.171
|
||||
ports:
|
||||
- name: secure-connection
|
||||
port: 9663
|
||||
protocol: TCP
|
8
test/output/sc_mongo_cred.yaml
Normal file
8
test/output/sc_mongo_cred.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
data:
|
||||
DB_PASSWORD: YXBwc3dvcmQ=
|
||||
DB_USER: YWJyYWthZGFicmE=
|
||||
metadata:
|
||||
name: mongo-creds
|
||||
namespace: ns2
|
||||
kind: Secret
|
||||
apiVersion: v1
|
20
test/scenarios/cli.yaml
Normal file
20
test/scenarios/cli.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: examples/cli/policy_deployment.yaml
|
||||
resource: examples/cli/nginx.yaml
|
||||
expected:
|
||||
mutation:
|
||||
patched_resource: test/output/nginx.yaml
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
||||
---
|
||||
input:
|
||||
policy: examples/cli/policy_deployment.yaml
|
||||
resource: examples/cli/ghost.yaml
|
||||
expected:
|
||||
mutation:
|
||||
patched_resource: test/output/ghost.yaml
|
||||
reason: Success
|
||||
validation:
|
||||
reason: Success
|
30
test/scenarios/generate.yaml
Normal file
30
test/scenarios/generate.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: examples/generate/policy_basic.yaml
|
||||
resource: examples/generate/namespace.yaml
|
||||
load_resources:
|
||||
- examples/generate/configMap_default.yaml
|
||||
expected:
|
||||
generation:
|
||||
resources:
|
||||
- test/output/cm_default_config.yaml
|
||||
- test/output/sc_mongo_cred.yaml
|
||||
---
|
||||
input:
|
||||
policy: examples/generate/policy_generate.yaml
|
||||
resource: examples/generate/namespace.yaml
|
||||
load_resources:
|
||||
- examples/generate/configMap.yaml
|
||||
expected:
|
||||
generation:
|
||||
resources:
|
||||
- test/output/cm_copied_cm.yaml
|
||||
- test/output/cm_zk-kafka-address.yaml
|
||||
---
|
||||
input:
|
||||
policy: examples/generate/policy_networkPolicy.yaml
|
||||
resource: examples/generate/namespace.yaml
|
||||
expected:
|
||||
generation:
|
||||
resources:
|
||||
- test/output/np_deny-all-traffic.yaml
|
8
test/scenarios/mutate/overlay.yaml
Normal file
8
test/scenarios/mutate/overlay.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: examples/mutate/overlay/policy_imagePullPolicy.yaml
|
||||
resource: examples/mutate/overlay/nginx.yaml
|
||||
expected:
|
||||
mutation:
|
||||
patched_resource: test/output/op_overlay_nginx.yaml
|
||||
reason: Success
|
8
test/scenarios/mutate/patches.yaml
Normal file
8
test/scenarios/mutate/patches.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# file path relative to project root
|
||||
input:
|
||||
policy: examples/mutate/patches/policy_endpoints.yaml
|
||||
resource: examples/mutate/patches/endpoints.yaml
|
||||
expected:
|
||||
mutation:
|
||||
patched_resource: test/output/op_patches_endpoints.yaml
|
||||
reason: Success
|
Loading…
Reference in a new issue