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

Merge pull request from NoSkillGirl/feat/support_mutate_in_cli

Kyverno CLI | Support mutate policies for `test` command
This commit is contained in:
Pooja Singh 2021-10-05 21:27:31 +05:30 committed by GitHub
commit ca62172b6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 789 additions and 59 deletions

View file

@ -202,6 +202,7 @@ test-e2e-local:
#Test TestCmd Policy
test-cmd: cli
$(PWD)/$(CLI_PATH)/kyverno test https://github.com/kyverno/policies/main
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-mutate
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-policy && exit 1 || exit 0
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-rule && exit 1 || exit 0

View file

@ -75,10 +75,10 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR
logger := log.Log.WithName("Generate").WithValues("policy", policy.Name,
"kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err != nil {
if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err != nil {
// if the oldResource matched, return "false" to delete GR for it
if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err == nil {
if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err == nil {
return &response.RuleResponse{
Name: rule.Name,
Type: "Generation",

View file

@ -65,7 +65,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
excludeResource = policyContext.ExcludeGroupRole
}
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels); err != nil {
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.Namespace); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
continue
}

View file

@ -256,13 +256,16 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User
}
//MatchesResourceDescription checks if the resource matches resource description of the rule or not
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string) error {
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string, policyNamespace string) error {
rule := ruleRef.DeepCopy()
resource := *resourceRef.DeepCopy()
admissionInfo := *admissionInfoRef.DeepCopy()
var reasonsForFailure []error
if policyNamespace != "" && policyNamespace != resourceRef.GetNamespace() {
return errors.New(" The policy and resource namespace are different. Therefore, policy skip this resource.")
}
if len(rule.MatchResources.Any) > 0 {
// include object if ANY of the criteria match
// so if one matches then break from loop

View file

@ -898,7 +898,7 @@ func TestMatchesResourceDescription(t *testing.T) {
resource, _ := utils.ConvertToUnstructured(tc.Resource)
for _, rule := range policy.Spec.Rules {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil)
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "")
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v", i+1, err)
@ -966,7 +966,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
@ -1027,7 +1027,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1087,7 +1087,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1155,7 +1155,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1224,7 +1224,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1304,7 +1304,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err == nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err == nil {
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
}
}

View file

@ -3,13 +3,14 @@ package engine
import (
"encoding/json"
"fmt"
"github.com/kyverno/kyverno/pkg/engine/common"
"github.com/pkg/errors"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"reflect"
"strings"
"time"
"github.com/kyverno/kyverno/pkg/engine/common"
"github.com/pkg/errors"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
@ -419,13 +420,13 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool {
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(logger logr.Logger, rule *kyverno.Rule, ctx *PolicyContext) bool {
err := MatchesResourceDescription(ctx.NewResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
err := MatchesResourceDescription(ctx.NewResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "")
if err == nil {
return true
}
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(ctx.OldResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
err := MatchesResourceDescription(ctx.OldResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "")
if err == nil {
return true
}

View file

@ -286,7 +286,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
}
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc)
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc, true)
if err != nil {
return rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}

View file

@ -524,16 +524,16 @@ func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts) (*response.EngineResponse, policyreport.Info, error) {
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts, printPatchResource bool) ([]*response.EngineResponse, policyreport.Info, error) {
var engineResponses []*response.EngineResponse
namespaceLabels := make(map[string]string)
operationIsDelete := false
if variables["request.operation"] == "DELETE" {
operationIsDelete = true
}
namespaceLabels := make(map[string]string)
policyWithNamespaceSelector := false
for _, p := range policy.Spec.Rules {
if p.MatchResources.ResourceDescription.NamespaceSelector != nil ||
@ -547,7 +547,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
resourceNamespace := resource.GetNamespace()
namespaceLabels = namespaceSelectorMap[resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
return engineResponses, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
}
}
@ -575,10 +575,14 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
}
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx, NamespaceLabels: namespaceLabels})
err = processMutateEngineResponse(policy, mutateResponse, resPath, rc, mutateLogPath, stdin, mutateLogPathIsDir, resource.GetName())
if mutateResponse != nil {
engineResponses = append(engineResponses, mutateResponse)
}
err = processMutateEngineResponse(policy, mutateResponse, resPath, rc, mutateLogPath, stdin, mutateLogPathIsDir, resource.GetName(), printPatchResource)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err)
return engineResponses, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err)
}
}
@ -604,6 +608,9 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
validateResponse = engine.Validate(policyCtx)
info = ProcessValidateEngineResponse(policy, validateResponse, resPath, rc, policyReport)
}
if validateResponse != nil {
engineResponses = append(engineResponses, validateResponse)
}
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
@ -624,10 +631,13 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
NamespaceLabels: namespaceLabels,
}
generateResponse := engine.Generate(policyContext)
if generateResponse != nil {
engineResponses = append(engineResponses, generateResponse)
}
processGenerateEngineResponse(policy, generateResponse, resPath, rc)
}
return validateResponse, info, nil
return engineResponses, info, nil
}
// PrintMutatedOutput - function to print output in provided file or directory
@ -892,7 +902,7 @@ func SetInStoreContext(mutatedPolicies []*v1.ClusterPolicy, variables map[string
return variables
}
func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *response.EngineResponse, resPath string, rc *ResultCounts, mutateLogPath string, stdin bool, mutateLogPathIsDir bool, resourceName string) error {
func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *response.EngineResponse, resPath string, rc *ResultCounts, mutateLogPath string, stdin bool, mutateLogPathIsDir bool, resourceName string, printPatchResource bool) error {
var policyHasMutate bool
for _, rule := range policy.Spec.Rules {
if rule.HasMutate() {
@ -929,7 +939,7 @@ func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *respo
}
}
if printMutatedRes {
if printMutatedRes && printPatchResource {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
return sanitizederror.NewWithError("failed to marshal", err)
@ -941,8 +951,7 @@ func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *respo
if !stdin {
fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
}
fmt.Printf("\n" + mutatedResource)
fmt.Printf("\n")
fmt.Printf("\n" + mutatedResource + "\n")
}
} else {
err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resourceName+"-mutated")
@ -1009,3 +1018,34 @@ func GetKindsFromPolicy(policy *v1.ClusterPolicy) map[string]struct{} {
}
return kindOnwhichPolicyIsApplied
}
//GetPatchedResourceFromPath - get patchedResource from given path
func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (unstructured.Unstructured, error) {
var patchedResourceBytes []byte
var patchedResource unstructured.Unstructured
var err error
if isGit {
if len(path) > 0 {
filep, err := fs.Open(filepath.Join(policyResourcePath, path))
if err != nil {
fmt.Printf("Unable to open patchedResource file: %s. \nerror: %s", path, err)
}
patchedResourceBytes, err = ioutil.ReadAll(filep)
}
} else {
patchedResourceBytes, err = getFileBytes(path)
}
if err != nil {
fmt.Printf("\n----------------------------------------------------------------------\nfailed to load patchedResource: %s. \nerror: %s\n----------------------------------------------------------------------\n", path, err)
return patchedResource, err
}
patchedResource, err = GetPatchedResource(patchedResourceBytes)
if err != nil {
return patchedResource, err
}
return patchedResource, nil
}

View file

@ -98,7 +98,7 @@ func Test_NamespaceSelector(t *testing.T) {
for _, tc := range testcases {
policyArray, _ := ut.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, rc)
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, rc, false)
assert.Assert(t, int64(rc.Pass) == int64(tc.result.Pass))
assert.Assert(t, int64(rc.Fail) == int64(tc.result.Fail))
assert.Assert(t, int64(rc.Skip) == int64(tc.result.Skip))

View file

@ -275,6 +275,14 @@ func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructu
return resource, nil
}
// GetPatchedResource converts raw bytes to unstructured object
func GetPatchedResource(patchResourceBytes []byte) (patchedResource unstructured.Unstructured, err error) {
getPatchedResource, err := GetResource(patchResourceBytes)
patchedResource = *getPatchedResource[0]
return patchedResource, nil
}
// getKindsFromPolicy will return the kinds from policy match block
func getKindsFromPolicy(rule v1.Rule) map[string]bool {
var resourceTypesMap = make(map[string]bool)

View file

@ -7,6 +7,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@ -14,11 +15,13 @@ import (
"github.com/fatih/color"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-logr/logr"
"github.com/kataras/tablewriter"
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/generate"
"github.com/kyverno/kyverno/pkg/kyverno/common"
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
"github.com/kyverno/kyverno/pkg/kyverno/store"
@ -30,17 +33,97 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
log "sigs.k8s.io/controller-runtime/pkg/log"
)
var longHelp = `
Test command provides a facility to test policies on resources. User should provide the path of the folder containing test.yaml file.
kyverno test <path_to_folder_Contaning_test.yamls>
or
kyverno test <path_to_githubRepository>
The test.yaml have 4 parts:
"policies" --> list of policies which are applied
"resources" --> list of resources on which the policies are applied
"variables" --> variable file path (this is an optinal parameter)
"results" --> list of result expected on applying the policies on the resources
`
var exampleHelp = `
test.yaml format:
For Validate Policy
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
result: <pass/fail/skip>
For Mutate Policy
1) Policy (Namespaced-policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <policy_namespace>/<policy_name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
2) ClusterPolicy(Cluster-wide policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
Result description:
pass --> patched Resource generated from engine equals to patched Resource provided by the user.
fail --> patched Resource generated from engine is not equals to patched provided by the user.
skip --> rule is not applied.
For more visit --> https://kyverno.io/docs/kyverno-cli/#test
`
// Command returns version command
func Command() *cobra.Command {
var cmd *cobra.Command
var valuesFile, fileName string
cmd = &cobra.Command{
Use: "test",
Short: "run tests from directory",
Use: "test",
Short: "run tests from directory",
Long: longHelp,
Example: exampleHelp,
RunE: func(cmd *cobra.Command, dirPath []string) (err error) {
defer func() {
if err != nil {
@ -78,6 +161,9 @@ type TestResults struct {
Result report.PolicyResult `json:"result"`
Status report.PolicyResult `json:"status"`
Resource string `json:"resource"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
PatchedResource string `json:"patchedResource"`
AutoGeneratedRule string `json:"auto_generated_rule"`
}
@ -239,13 +325,16 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName, valuesFile string
return errors
}
func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResults, infos []policyreport.Info) (map[string]report.PolicyReportResult, []TestResults) {
func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResults, infos []policyreport.Info, policyResourcePath string, fs billy.Filesystem, isGit bool) (map[string]report.PolicyReportResult, []TestResults) {
results := make(map[string]report.PolicyReportResult)
now := metav1.Timestamp{Seconds: time.Now().Unix()}
for _, resp := range resps {
policyName := resp.PolicyResponse.Policy.Name
resourceName := resp.PolicyResponse.Resource.Name
resourceKind := resp.PolicyResponse.Resource.Kind
resourceNamespace := resp.PolicyResponse.Resource.Namespace
policyNamespace := resp.PolicyResponse.Policy.Namespace
var rules []string
for _, rule := range resp.PolicyResponse.Rules {
@ -262,27 +351,81 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
Message: buildMessage(resp),
}
var patcheResourcePath []string
for i, test := range testResults {
var userDefinedPolicyNamespace string
var userDefinedPolicyName string
found, err := isNamespacedPolicy(test.Policy)
if err != nil {
log.Log.V(3).Info("error while checking the policy is namespaced or not", "policy: ", test.Policy, "error: ", err)
continue
}
if found {
userDefinedPolicyNamespace, userDefinedPolicyName = getUserDefinedPolicyNameAndNamespace(test.Policy)
test.Policy = userDefinedPolicyName
}
if test.Policy == policyName && test.Resource == resourceName {
var resultsKey string
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
if !util.ContainsString(rules, test.Rule) {
if !util.ContainsString(rules, "autogen-"+test.Rule) {
if !util.ContainsString(rules, "autogen-cronjob-"+test.Rule) {
result.Result = report.StatusSkip
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
}
} else {
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
}
if results[resultsKey].Result == "" {
result.Result = report.StatusSkip
results[resultsKey] = result
}
}
resultsKey := fmt.Sprintf("%s-%s-%s", test.Policy, test.Rule, test.Resource)
patcheResourcePath = append(patcheResourcePath, test.PatchedResource)
if _, ok := results[resultsKey]; !ok {
results[resultsKey] = result
}
}
}
for _, rule := range resp.PolicyResponse.Rules {
if rule.Type != utils.Mutation.String() {
continue
}
var resultsKey []string
var resultKey string
var result report.PolicyReportResult
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name, resourceNamespace, resourceKind, resourceName)
for _, resultK := range resultsKey {
if val, ok := results[resultK]; ok {
result = val
resultKey = resultK
} else {
continue
}
var x string
for _, path := range patcheResourcePath {
result.Result = report.StatusFail
x = getAndComparePatchedResource(path, resp.PatchedResource, isGit, policyResourcePath, fs)
if x == "pass" {
result.Result = report.StatusPass
break
}
}
results[resultKey] = result
}
}
}
@ -294,18 +437,23 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
}
var result report.PolicyReportResult
resultsKey := fmt.Sprintf("%s-%s-%s", info.PolicyName, rule.Name, infoResult.Resource.Name)
if val, ok := results[resultsKey]; ok {
result = val
} else {
continue
var resultsKey []string
var resultKey string
resultsKey = GetAllPossibleResultsKey("", info.PolicyName, rule.Name, infoResult.Resource.Namespace, infoResult.Resource.Kind, infoResult.Resource.Name)
for _, resultK := range resultsKey {
if val, ok := results[resultK]; ok {
result = val
resultKey = resultK
} else {
continue
}
}
result.Rule = rule.Name
result.Result = report.PolicyResult(rule.Status)
result.Source = policyreport.SourceValue
result.Timestamp = now
results[resultsKey] = result
results[resultKey] = result
}
}
}
@ -313,6 +461,64 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
return results, testResults
}
func GetAllPossibleResultsKey(policyNs, policy, rule, resourceNsnamespace, kind, resource string) []string {
var resultsKey []string
resultKey1 := fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
resultKey2 := fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNsnamespace, kind, resource)
resultKey3 := fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
resultKey4 := fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNsnamespace, kind, resource)
resultsKey = append(resultsKey, resultKey1, resultKey2, resultKey3, resultKey4)
return resultsKey
}
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string) string {
var resultKey string
resultKey = fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
if policyNs != "" && resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNs, kind, resource)
} else if policyNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
} else if resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNs, kind, resource)
}
return resultKey
}
func isNamespacedPolicy(policyNames string) (bool, error) {
return regexp.MatchString("^[a-z]*/[a-z]*", policyNames)
}
func getUserDefinedPolicyNameAndNamespace(policyName string) (string, string) {
if strings.Contains(policyName, "/") {
policy_n_ns := strings.Split(policyName, "/")
namespace := policy_n_ns[0]
policy := policy_n_ns[1]
return namespace, policy
}
return "", policyName
}
// getAndComparePatchedResource --> Get the patchedResource from the path provided by user
// And compare this patchedResource with engine generated patcheResource.
func getAndComparePatchedResource(path string, enginePatchedResource unstructured.Unstructured, isGit bool, policyResourcePath string, fs billy.Filesystem) string {
var status string
patchedResources, err := common.GetPatchedResourceFromPath(fs, path, isGit, policyResourcePath)
if err != nil {
os.Exit(1)
}
var log logr.Logger
matched, err := generate.ValidateResourceWithPattern(log, enginePatchedResource.UnstructuredContent(), patchedResources.UnstructuredContent())
if err != nil {
status = "fail"
}
if matched == "" {
status = "pass"
}
return status
}
func buildMessage(resp *response.EngineResponse) string {
var bldr strings.Builder
for _, ruleResp := range resp.PolicyResponse.Rules {
@ -323,20 +529,22 @@ func buildMessage(resp *response.EngineResponse) string {
return bldr.String()
}
func getPolicyResourceFullPath(path []string, policyResourcePath string, isGit bool) []string {
var pol []string
func getFullPath(paths []string, policyResourcePath string, isGit bool) []string {
var pols []string
var pol string
if !isGit {
for _, p := range path {
pol = append(pol, filepath.Join(policyResourcePath, p))
for _, path := range paths {
pol = filepath.Join(policyResourcePath, path)
pols = append(pols, pol)
}
return pol
return pols
}
return path
return paths
}
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool, policyResourcePath string, rc *resultCounts) (err error) {
openAPIController, err := openapi.NewOpenAPIController()
validateEngineResponses := make([]*response.EngineResponse, 0)
engineResponses := make([]*response.EngineResponse, 0)
var dClient *client.Client
values := &Test{}
var variablesString string
@ -358,10 +566,16 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return err
}
fullPolicyPath := getPolicyResourceFullPath(values.Policies, policyResourcePath, isGit)
fullResourcePath := getPolicyResourceFullPath(values.Resources, policyResourcePath, isGit)
policyFullPath := getFullPath(values.Policies, policyResourcePath, isGit)
resourceFullPath := getFullPath(values.Resources, policyResourcePath, isGit)
policies, err := common.GetPoliciesFromPaths(fs, fullPolicyPath, isGit, policyResourcePath)
for i, result := range values.Results {
arrPatchedResource := []string{result.PatchedResource}
patchedResourceFullPath := getFullPath(arrPatchedResource, policyResourcePath, isGit)
values.Results[i].PatchedResource = patchedResourceFullPath[0]
}
policies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1)
@ -379,7 +593,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError("failed to print mutated policy", err)
}
resources, err := common.GetResourceAccordingToResourcePath(fs, fullResourcePath, false, mutatedPolicies, dClient, "", false, isGit, policyResourcePath)
resources, err := common.GetResourceAccordingToResourcePath(fs, resourceFullPath, false, mutatedPolicies, dClient, "", false, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1)
@ -426,16 +640,15 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
}
validateErs, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, &resultCounts)
ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, &resultCounts, false)
if err != nil {
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}
validateEngineResponses = append(validateEngineResponses, validateErs)
engineResponses = append(engineResponses, ers...)
pvInfos = append(pvInfos, info)
}
}
resultsMap, testResults := buildPolicyResults(validateEngineResponses, values.Results, pvInfos)
resultsMap, testResults := buildPolicyResults(engineResponses, values.Results, pvInfos, policyResourcePath, fs, isGit)
resultErr := printTestResult(resultsMap, testResults, rc)
if resultErr != nil {
return sanitizederror.NewWithError("failed to print test result:", resultErr)
@ -451,13 +664,19 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
boldRed := color.New(color.FgRed).Add(color.Bold)
boldYellow := color.New(color.FgYellow).Add(color.Bold)
boldFgCyan := color.New(color.FgCyan).Add(color.Bold)
for i, v := range testResults {
res := new(Table)
res.ID = i + 1
res.Policy = boldFgCyan.Sprintf(v.Policy)
res.Rule = boldFgCyan.Sprintf(v.Rule)
res.Resource = boldFgCyan.Sprintf(v.Resource)
namespace := "default"
if v.Namespace != "" {
namespace = v.Namespace
}
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
var ruleNameInResultKey string
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
@ -465,7 +684,20 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
ruleNameInResultKey = v.Rule
}
resultKey := fmt.Sprintf("%s-%s-%s", v.Policy, ruleNameInResultKey, v.Resource)
resultKey := fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
found, _ := isNamespacedPolicy(v.Policy)
var ns string
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
if found && v.Namespace != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
} else if found {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
res.Policy = boldFgCyan.Sprintf(ns) + "/" + boldFgCyan.Sprintf(v.Policy)
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
} else if v.Namespace != "" {
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
}
var testRes report.PolicyReportResult
if val, ok := resps[resultKey]; ok {
@ -476,26 +708,28 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
table = append(table, res)
continue
}
if v.Result == "" && v.Status != "" {
v.Result = v.Status
}
if testRes.Result == v.Result {
res.Result = boldGreen.Sprintf("Pass")
if testRes.Result == report.StatusSkip {
res.Result = boldGreen.Sprintf("Pass")
rc.Skip++
} else {
res.Result = boldGreen.Sprintf("Pass")
rc.Pass++
}
} else {
fmt.Printf("test failed for policy=%s, rule=%s, resource=%s, expected=%s, received=%s \n",
v.Policy, v.Rule, v.Resource, v.Result, testRes.Result)
fmt.Printf("%s \n", testRes.Message)
res.Result = boldRed.Sprintf("Fail")
rc.Fail++
}
table = append(table, res)
}
printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
printer.CenterSeparator = "│"
printer.ColumnSeparator = "│"
@ -504,8 +738,10 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
printer.RowLengthTitle = func(rowsLength int) bool {
return rowsLength > 10
}
printer.HeaderBgColor = tablewriter.BgBlackColor
printer.HeaderFgColor = tablewriter.FgGreenColor
fmt.Printf("\n")
printer.Print(table)
return nil
}

View file

@ -40,6 +40,12 @@ func GetPolicy(bytes []byte) (clusterPolicies []*v1.ClusterPolicy, err error) {
return nil, fmt.Errorf(msg)
}
if policy.Namespace != "" || (policy.Namespace == "" && policy.Kind == "Policy") {
if policy.Namespace == "" {
policy.Namespace = "default"
}
policy.Kind = "ClusterPolicy"
}
clusterPolicies = append(clusterPolicies, policy)
}

View file

@ -7,4 +7,5 @@ results:
- policy: missing
rule: validate-image-tag
resource: test
kind: Pod
result: pass

View file

@ -7,4 +7,5 @@ results:
- policy: disallow-latest-tag
rule: validate-image-tag
resource: missing
kind: Pod
result: pass

View file

@ -7,4 +7,5 @@ results:
- policy: disallow-latest-tag
rule: missing
resource: test
kind: Pod
status: pass

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeploy
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
color: orange
namespace: testing
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
color: orange
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: mydeploy
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
color: orange
spec:
containers:
- image: nginx:1.14.2
name: nginx
ports:
- containerPort: 80

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: same-name-but-diff-kind
spec:
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30007
type: NodePort

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
color: orange
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
name: same-name-but-diff-namespace
namespace: testing
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,62 @@
# Below there are both type of olicies: ClusterPolicy and Policy(Namespaced-Policy)
#ClusterPolicy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-label
annotations:
policies.kyverno.io/title: Add nodeSelector
policies.kyverno.io/category: Sample
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Labels are used as an important source of metadata describing objects in various ways
or triggering other functionality. Labels are also a very basic concept and should be
used throughout Kubernetes. This policy performs a simple mutation which adds a label
`color=orange` to Pods, Services, ConfigMaps, and Secrets.
spec:
background: false
validationFailureAction:
rules:
- name: add-label
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
metadata:
labels:
color: orange
---
# Policy ( In testing namespace )
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: add-ndots
namespace: testing
annotations:
policies.kyverno.io/title: Add ndots
policies.kyverno.io/category: Sample
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
The ndots value controls where DNS lookups are first performed in a cluster
and needs to be set to a lower value than the default of 5 in some cases.
This policy mutates all Pods to add the ndots option with a value of 1.
spec:
background: false
rules:
- name: add-ndots
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,100 @@
# resource == patchedResource
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
---
# Resource with same name and diff. namespace
# Same namespace as namespaced-policy
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: testing
spec:
containers:
- name: nginx
image: nginx:latest
---
# Resource with same name and diff. namespace
# Namespace differ from namespaced-policy
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest
---
# Deployment in default namespace
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeploy
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
---
# Resource (Service) with same name but different kind
apiVersion: v1
kind: Service
metadata:
name: same-name-but-diff-kind
spec:
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30007
type: NodePort
---
# Resource (Pod) with same name but different kind
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,86 @@
name: add-nodeselector
policies:
- policy.yaml
resources:
- resource.yaml
results:
- policy: add-label
rule: add-label
resource: resource-equal-to-patch-res-for-cp
patchedResource: patchedResource1.yaml
kind: Pod
namespace: practice
result: skip
- policy: add-label
rule: add-label
resource: same-name-but-diff-namespace
patchedResource: patchedResource2.yaml
kind: Pod
namespace: testing
result: pass
- policy: add-label
rule: add-label
resource: same-name-but-diff-namespace
patchedResource: patchedResource3.yaml
kind: Pod
namespace: production
result: pass
- policy: add-label
rule: add-label
resource: mydeploy
patchedResource: patchedResource4.yaml
kind: Deployment
result: pass
- policy: add-label
rule: add-label
resource: same-name-but-diff-kind
patchedResource: patchedResource5.yaml
kind: Service
result: skip
- policy: add-label
rule: add-label
resource: same-name-but-diff-kind
patchedResource: patchedResource6.yaml
kind: Pod
result: pass
- policy: testing/add-ndots
rule: add-ndots
resource: resource-equal-to-patch-res-for-cp
namespace: practice
patchedResource: patchedResource7.yaml
kind: Pod
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-namespace
patchedResource: patchedResource8.yaml
namespace: testing
kind: Pod
result: pass
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-namespace
patchedResource: patchedResource9.yaml
kind: Pod
namespace: production
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: mydeploy
patchedResource: patchedResource10.yaml
kind: Deployment
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-kind
patchedResource: patchedResource5.yaml
kind: Service
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-kind
patchedResource: patchedResource11.yaml
kind: Pod
result: skip

View file

@ -7,46 +7,54 @@ results:
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: Pod
resource: pod-with-labels
# TEST: Pod Missing Labels Should Fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: Pod
resource: pod-missing-labels
# TEST: Deployment with Labels Should Pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: Deployment
resource: deployment-with-labels
# TEST: Deployment with Labels Should Fail
- policy: require-common-labels
rule: check-for-labels
result: skip
kind: Deployment
resource: deployment-missing-labels
# TEST: StatefulSet with Labels Should Pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: StatefulSet
resource: StatefulSet-with-labels
# TEST: StatefulSet with Labels Should fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: StatefulSet
resource: StatefulSet-without-labels
# TEST: Cronjob with Labels Should pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: CronJob
resource: cronjob-with-labels
# TEST: Cronjob without Labels Should fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: CronJob
resource: cronjob-without-labels

View file

@ -7,20 +7,25 @@ results:
- policy: disallow-latest-tag
rule: require-image-tag
resource: test-require-image-tag-pass
kind: Pod
status: pass
- policy: disallow-latest-tag
rule: require-image-tag
resource: test-require-image-tag-fail
kind: Pod
status: fail
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-ignore
kind: Pod
status: skip
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-fail
kind: Pod
status: fail
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-pass
kind: Pod
status: pass

View file

@ -11,32 +11,40 @@ results:
- policy: cm-variable-example
rule: example-configmap-lookup
resource: test-env-test
kind: Pod
result: pass
- policy: cm-variable-example
rule: example-configmap-lookup
resource: test-env-dev
kind: Pod
result: fail
- policy: cm-array-example
rule: validate-role-annotation
resource: test-web
kind: Pod
result: fail
- policy: cm-array-example
rule: validate-role-annotation
resource: test-app
kind: Pod
result: pass
- policy: cm-blk-scalar-example
rule: validate-blk-role-annotation
resource: test-blk-web
kind: Pod
result: fail
- policy: cm-blk-scalar-example
rule: validate-blk-role-annotation
resource: test-blk-app
kind: Pod
result: pass
- policy: cm-globalval-example
rule: validate-mode
resource: test-global-dev
kind: Pod
result: pass
- policy: cm-globalval-example
rule: validate-mode
resource: test-global-prod
kind: Pod
result: fail