mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge pull request #2462 from NoSkillGirl/feat/support_mutate_in_cli
Kyverno CLI | Support mutate policies for `test` command
This commit is contained in:
commit
ca62172b6f
32 changed files with 789 additions and 59 deletions
Makefile
pkg
engine
kyverno
utils
test/cli
1
Makefile
1
Makefile
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,4 +7,5 @@ results:
|
|||
- policy: missing
|
||||
rule: validate-image-tag
|
||||
resource: test
|
||||
kind: Pod
|
||||
result: pass
|
||||
|
|
|
@ -7,4 +7,5 @@ results:
|
|||
- policy: disallow-latest-tag
|
||||
rule: validate-image-tag
|
||||
resource: missing
|
||||
kind: Pod
|
||||
result: pass
|
||||
|
|
|
@ -7,4 +7,5 @@ results:
|
|||
- policy: disallow-latest-tag
|
||||
rule: missing
|
||||
resource: test
|
||||
kind: Pod
|
||||
status: pass
|
||||
|
|
16
test/cli/test-mutate/patchedResource1.yaml
Normal file
16
test/cli/test-mutate/patchedResource1.yaml
Normal 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"
|
25
test/cli/test-mutate/patchedResource10.yaml
Normal file
25
test/cli/test-mutate/patchedResource10.yaml
Normal 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"
|
10
test/cli/test-mutate/patchedResource11.yaml
Normal file
10
test/cli/test-mutate/patchedResource11.yaml
Normal 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
|
12
test/cli/test-mutate/patchedResource2.yaml
Normal file
12
test/cli/test-mutate/patchedResource2.yaml
Normal 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
|
12
test/cli/test-mutate/patchedResource3.yaml
Normal file
12
test/cli/test-mutate/patchedResource3.yaml
Normal 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
|
23
test/cli/test-mutate/patchedResource4.yaml
Normal file
23
test/cli/test-mutate/patchedResource4.yaml
Normal 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
|
||||
|
12
test/cli/test-mutate/patchedResource5.yaml
Normal file
12
test/cli/test-mutate/patchedResource5.yaml
Normal 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
|
11
test/cli/test-mutate/patchedResource6.yaml
Normal file
11
test/cli/test-mutate/patchedResource6.yaml
Normal 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
|
16
test/cli/test-mutate/patchedResource7.yaml
Normal file
16
test/cli/test-mutate/patchedResource7.yaml
Normal 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"
|
15
test/cli/test-mutate/patchedResource8.yaml
Normal file
15
test/cli/test-mutate/patchedResource8.yaml
Normal 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"
|
11
test/cli/test-mutate/patchedResource9.yaml
Normal file
11
test/cli/test-mutate/patchedResource9.yaml
Normal 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
|
62
test/cli/test-mutate/policy.yaml
Normal file
62
test/cli/test-mutate/policy.yaml
Normal 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"
|
100
test/cli/test-mutate/resource.yaml
Normal file
100
test/cli/test-mutate/resource.yaml
Normal 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
|
||||
|
||||
|
86
test/cli/test-mutate/test.yaml
Normal file
86
test/cli/test-mutate/test.yaml
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue