Repository: kyverno/kyverno
Pull request #1545 (2021-02-05 06:03:43)

Reduce RCR Throttling
## Related issue
Fixes https://github.com/kyverno/kyverno/issues/1497.

This PR adds a buffer to slow down report change request creation.


Patch:
diff --git a/Makefile b/Makefile
index 7189d346e..519490505 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,10 @@ docker-publish-kyverno: docker-build-kyverno  docker-tag-repo-kyverno  docker-pu
 docker-build-kyverno:
 	@docker build -f $(PWD)/$(KYVERNO_PATH)/Dockerfile -t $(REPO)/$(KYVERNO_IMAGE):$(IMAGE_TAG) . --build-arg LD_FLAGS=$(LD_FLAGS)
 
+docker-build-local-kyverno:
+	CGO_ENABLED=0 GOOS=linux go build -o $(PWD)/$(KYVERNO_PATH)/kyverno -ldflags=$(LD_FLAGS) $(PWD)/$(KYVERNO_PATH)/main.go
+	@docker build -f $(PWD)/$(KYVERNO_PATH)/localDockerfile -t $(REPO)/$(KYVERNO_IMAGE):$(IMAGE_TAG) $(PWD)/$(KYVERNO_PATH)
+
 docker-tag-repo-kyverno:
 	@echo "docker tag $(REPO)/$(KYVERNO_IMAGE):$(IMAGE_TAG) $(REPO)/$(KYVERNO_IMAGE):latest"
 	@docker tag $(REPO)/$(KYVERNO_IMAGE):$(IMAGE_TAG) $(REPO)/$(KYVERNO_IMAGE):latest
diff --git a/cmd/kyverno/localDockerfile b/cmd/kyverno/localDockerfile
new file mode 100644
index 000000000..42309392d
--- /dev/null
+++ b/cmd/kyverno/localDockerfile
@@ -0,0 +1,3 @@
+FROM scratch
+ADD kyverno /kyverno
+ENTRYPOINT ["/kyverno"]
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 59dfbcc55..34f11bd84 100644
--- a/go.mod
+++ b/go.mod
@@ -26,6 +26,7 @@ require (
 	github.com/onsi/ginkgo v1.11.0
 	github.com/onsi/gomega v1.8.1
 	github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
+	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.6.0 // indirect
 	github.com/spf13/cobra v1.0.0
diff --git a/go.sum b/go.sum
index c5d0ae174..60fe3e14d 100644
--- a/go.sum
+++ b/go.sum
@@ -540,6 +540,9 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
 github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
 github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk=
 github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
diff --git a/pkg/api/kyverno/v1/policy_types.go b/pkg/api/kyverno/v1/policy_types.go
index b57f6fc4d..83c2e68be 100755
--- a/pkg/api/kyverno/v1/policy_types.go
+++ b/pkg/api/kyverno/v1/policy_types.go
@@ -178,6 +178,14 @@ const (
 	In ConditionOperator = "In"
 	// NotIn evaluates if the key is not contained in the set of values.
 	NotIn ConditionOperator = "NotIn"
+	// GreaterThanOrEquals evaluates if the key (numeric) is greater than or equal to the value (numeric).
+	GreaterThanOrEquals ConditionOperator = "GreaterThanOrEquals"
+	// GreaterThan evaluates if the key (numeric) is greater than the value (numeric).
+	GreaterThan ConditionOperator = "GreaterThan"
+	// LessThan evaluates if the key (numeric) is less than or equal to the value (numeric).
+	LessThanOrEquals ConditionOperator = "LessThanOrEquals"
+	// LessThan evaluates if the key (numeric) is less than the value (numeric).
+	LessThan ConditionOperator = "LessThan"
 )
 
 // MatchResources is used to specify resource and admission review request data for
diff --git a/pkg/engine/validate/pattern.go b/pkg/engine/validate/pattern.go
index a08f7d4e2..46e9c1cf9 100644
--- a/pkg/engine/validate/pattern.go
+++ b/pkg/engine/validate/pattern.go
@@ -154,10 +154,10 @@ func validateValueWithNilPattern(log logr.Logger, value interface{}) bool {
 
 // Handler for pattern values during validation process
 func validateValueWithStringPatterns(log logr.Logger, value interface{}, pattern string) bool {
-	statements := strings.Split(pattern, "|")
-	for _, statement := range statements {
-		statement = strings.Trim(statement, " ")
-		if validateValueWithStringPattern(log, value, statement) {
+	conditions := strings.Split(pattern, "|")
+	for _, condition := range conditions {
+		condition = strings.Trim(condition, " ")
+		if checkForAndConditionsAndValidate(log, value, condition) {
 			return true
 		}
 	}
@@ -165,6 +165,18 @@ func validateValueWithStringPatterns(log logr.Logger, value interface{}, pattern
 	return false
 }
 
+func checkForAndConditionsAndValidate(log logr.Logger, value interface{}, pattern string) bool {
+	conditions := strings.Split(pattern, "&")
+	for _, condition := range conditions {
+		condition = strings.Trim(condition, " ")
+		if !validateValueWithStringPattern(log, value, condition) {
+			return false
+		}
+	}
+
+	return true
+}
+
 // Handler for single pattern value during validation process
 // Detects if pattern has a number
 func validateValueWithStringPattern(log logr.Logger, value interface{}, pattern string) bool {
diff --git a/pkg/engine/variables/operator/numeric.go b/pkg/engine/variables/operator/numeric.go
new file mode 100644
index 000000000..687cb0146
--- /dev/null
+++ b/pkg/engine/variables/operator/numeric.go
@@ -0,0 +1,150 @@
+package operator
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/go-logr/logr"
+	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
+	"github.com/kyverno/kyverno/pkg/engine/context"
+)
+
+//NewNumericOperatorHandler returns handler to manage the provided numeric operations (>, >=, <=, <)
+func NewNumericOperatorHandler(log logr.Logger, ctx context.EvalInterface, subHandler VariableSubstitutionHandler, op kyverno.ConditionOperator) OperatorHandler {
+	return NumericOperatorHandler{
+		ctx:        ctx,
+		subHandler: subHandler,
+		log:        log,
+		condition:  op,
+	}
+}
+
+//NumericOperatorHandler provides implementation to handle Numeric Operations associated with policies
+type NumericOperatorHandler struct {
+	ctx        context.EvalInterface
+	subHandler VariableSubstitutionHandler
+	log        logr.Logger
+	condition  kyverno.ConditionOperator
+}
+
+// compareByCondition compares a float64 key with a float64 value on the basis of the provided operator
+func compareByCondition(key float64, value float64, op kyverno.ConditionOperator, log *logr.Logger) bool {
+	switch op {
+	case kyverno.GreaterThanOrEquals:
+		return key >= value
+	case kyverno.GreaterThan:
+		return key > value
+	case kyverno.LessThanOrEquals:
+		return key <= value
+	case kyverno.LessThan:
+		return key < value
+	default:
+		(*log).Info(fmt.Sprintf("Expected operator, one of [GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan], found %s", op))
+		return false
+	}
+}
+
+func (noh NumericOperatorHandler) Evaluate(key, value interface{}) bool {
+	if key, err := noh.subHandler(noh.log, noh.ctx, key); err != nil {
+		// Failed to resolve the variable
+		noh.log.Error(err, "Failed to resolve variable", "variable", key)
+		return false
+	}
+	if value, err := noh.subHandler(noh.log, noh.ctx, value); err != nil {
+		// Failed to resolve the variable
+		noh.log.Error(err, "Failed to resolve variable", "variable", value)
+		return false
+	}
+
+	switch typedKey := key.(type) {
+	case int:
+		return noh.validateValueWithIntPattern(int64(typedKey), value)
+	case int64:
+		return noh.validateValueWithIntPattern(typedKey, value)
+	case float64:
+		return noh.validateValueWithFloatPattern(typedKey, value)
+	case string:
+		return noh.validateValueWithStringPattern(typedKey, value)
+	default:
+		noh.log.Info("Unsupported type", "value", typedKey, "type", fmt.Sprintf("%T", typedKey))
+		return false
+	}
+}
+
+func (noh NumericOperatorHandler) validateValueWithIntPattern(key int64, value interface{}) bool {
+	switch typedValue := value.(type) {
+	case int:
+		return compareByCondition(float64(key), float64(typedValue), noh.condition, &noh.log)
+	case int64:
+		return compareByCondition(float64(key), float64(typedValue), noh.condition, &noh.log)
+	case float64:
+		return compareByCondition(float64(key), typedValue, noh.condition, &noh.log)
+	case string:
+		// extract float64 and (if that fails) then, int64 from the string
+		float64val, err := strconv.ParseFloat(typedValue, 64)
+		if err == nil {
+			return compareByCondition(float64(key), float64val, noh.condition, &noh.log)
+		}
+		int64val, err := strconv.ParseInt(typedValue, 10, 64)
+		if err == nil {
+			return compareByCondition(float64(key), float64(int64val), noh.condition, &noh.log)
+		}
+		noh.log.Error(fmt.Errorf("Parse Error: "), "Failed to parse both float64 and int64 from the string value")
+		return false
+	default:
+		noh.log.Info("Expected type int", "value", value, "type", fmt.Sprintf("%T", value))
+		return false
+	}
+}
+
+func (noh NumericOperatorHandler) validateValueWithFloatPattern(key float64, value interface{}) bool {
+	switch typedValue := value.(type) {
+	case int:
+		return compareByCondition(key, float64(typedValue), noh.condition, &noh.log)
+	case int64:
+		return compareByCondition(key, float64(typedValue), noh.condition, &noh.log)
+	case float64:
+		return compareByCondition(key, typedValue, noh.condition, &noh.log)
+	case string:
+		float64val, err := strconv.ParseFloat(typedValue, 64)
+		if err == nil {
+			return compareByCondition(key, float64val, noh.condition, &noh.log)
+		}
+		int64val, err := strconv.ParseInt(typedValue, 10, 64)
+		if err == nil {
+			return compareByCondition(key, float64(int64val), noh.condition, &noh.log)
+		}
+		noh.log.Error(fmt.Errorf("Parse Error: "), "Failed to parse both float64 and int64 from the string value")
+		return false
+	default:
+		noh.log.Info("Expected type float", "value", value, "type", fmt.Sprintf("%T", value))
+		return false
+	}
+}
+
+func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, value interface{}) bool {
+	// extracting float64 from the string key
+	float64key, err := strconv.ParseFloat(key, 64)
+	if err == nil {
+		return noh.validateValueWithFloatPattern(float64key, value)
+	}
+	// extracting int64 from the string because float64 extraction failed
+	int64key, err := strconv.ParseInt(key, 10, 64)
+	if err == nil {
+		return noh.validateValueWithIntPattern(int64key, value)
+	}
+	noh.log.Error(fmt.Errorf("Parse Error: "), "Failed to parse both float64 and int64 from the string keyt")
+	return false
+}
+
+// the following functions are unreachable because the key is strictly supposed to be numeric
+// still the following functions are just created to make NumericOperatorHandler struct implement OperatorHandler interface
+func (noh NumericOperatorHandler) validateValueWithBoolPattern(key bool, value interface{}) bool {
+	return false
+}
+func (noh NumericOperatorHandler) validateValueWithMapPattern(key map[string]interface{}, value interface{}) bool {
+	return false
+}
+func (noh NumericOperatorHandler) validateValueWithSlicePattern(key []interface{}, value interface{}) bool {
+	return false
+}
diff --git a/pkg/engine/variables/operator/operator.go b/pkg/engine/variables/operator/operator.go
index 34fc9dd75..48801105d 100644
--- a/pkg/engine/variables/operator/operator.go
+++ b/pkg/engine/variables/operator/operator.go
@@ -1,10 +1,11 @@
 package operator
 
 import (
+	"strings"
+
 	"github.com/go-logr/logr"
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/engine/context"
-	"strings"
 )
 
 //OperatorHandler provides interface to manage types
@@ -44,6 +45,12 @@ func CreateOperatorHandler(log logr.Logger, ctx context.EvalInterface, op kyvern
 	case strings.ToLower(string(kyverno.NotIn)):
 		return NewNotInHandler(log, ctx, subHandler)
 
+	case strings.ToLower(string(kyverno.GreaterThanOrEquals)),
+		strings.ToLower(string(kyverno.GreaterThan)),
+		strings.ToLower(string(kyverno.LessThanOrEquals)),
+		strings.ToLower(string(kyverno.LessThan)):
+		return NewNumericOperatorHandler(log, ctx, subHandler, op)
+
 	default:
 		log.Info("operator not supported", "operator", str)
 	}
diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go
index 34efcec10..573c8e75c 100644
--- a/pkg/kyverno/apply/command.go
+++ b/pkg/kyverno/apply/command.go
@@ -1,10 +1,7 @@
 package apply
 
 import (
-	"bufio"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -14,21 +11,17 @@ import (
 	v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	pkgCommon "github.com/kyverno/kyverno/pkg/common"
 	client "github.com/kyverno/kyverno/pkg/dclient"
-	"github.com/kyverno/kyverno/pkg/engine"
-	"github.com/kyverno/kyverno/pkg/engine/context"
 	"github.com/kyverno/kyverno/pkg/engine/response"
 	"github.com/kyverno/kyverno/pkg/kyverno/common"
 	sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
 	"github.com/kyverno/kyverno/pkg/openapi"
 	policy2 "github.com/kyverno/kyverno/pkg/policy"
-	"github.com/kyverno/kyverno/pkg/utils"
 	"github.com/spf13/cobra"
-	yamlv2 "gopkg.in/yaml.v2"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/util/yaml"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	log "sigs.k8s.io/controller-runtime/pkg/log"
 	yaml1 "sigs.k8s.io/yaml"
+	"github.com/go-git/go-billy/v5/memfs"
 )
 
 type resultCounts struct {
@@ -148,12 +141,13 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 	variablesString string, valuesFile string, namespace string, policyPaths []string) (validateEngineResponses []*response.EngineResponse, rc *resultCounts, resources []*unstructured.Unstructured, skippedPolicies []SkippedPolicy, err error) {
 
 	kubernetesConfig := genericclioptions.NewConfigFlags(true)
+	fs := memfs.New()
 
 	if valuesFile != "" && variablesString != "" {
 		return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
 	}
 
-	variables, valuesMap, err := getVariable(variablesString, valuesFile)
+	variables, valuesMap, err :=  common.GetVariable(variablesString, valuesFile)
 	if err != nil {
 		if !sanitizederror.IsErrorSanitized(err) {
 			return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", err)
@@ -186,7 +180,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 		return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
 	}
 
-	policies, err := getPoliciesFromPaths(policyPaths)
+	policies, err := common.GetPoliciesFromPaths(fs,policyPaths, false)
 	if err != nil {
 		fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
 		os.Exit(1)
@@ -204,14 +198,14 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 		return validateEngineResponses, rc, resources, skippedPolicies, err
 	}
 
-	mutatedPolicies, err := mutatePolices(policies)
+	mutatedPolicies, err := common.MutatePolices(policies)
 	if err != nil {
 		if !sanitizederror.IsErrorSanitized(err) {
 			return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to mutate policy", err)
 		}
 	}
 
-	resources, err = getResourceAccordingToResourcePath(resourcePaths, cluster, mutatedPolicies, dClient, namespace, policyReport)
+	resources, err = common.GetResourceAccordingToResourcePath(fs, resourcePaths, cluster, mutatedPolicies, dClient, namespace, policyReport, false)
 	if err != nil {
 		fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
 		os.Exit(1)
@@ -245,7 +239,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 		}
 
 		matches := common.PolicyHasVariables(*policy)
-		variable := removeDuplicatevariables(matches)
+		variable := common.RemoveDuplicateVariables(matches)
 
 		if len(matches) > 0 && variablesString == "" && valuesFile == "" {
 			rc.skip++
@@ -274,11 +268,18 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 				return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
 			}
 
-			ers, validateErs, err := applyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, rc, policyReport)
+			ers, validateErs, responseError, rcErs, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport)
 			if err != nil {
 				return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
 			}
-
+			if responseError == true {
+				rc.fail++
+			} else {
+				rc.pass++
+			}
+			if rcErs == true {
+				rc.error++
+			}
 			engineResponses = append(engineResponses, ers...)
 			validateEngineResponses = append(validateEngineResponses, validateErs)
 		}
@@ -287,44 +288,6 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 	return validateEngineResponses, rc, resources, skippedPolicies, nil
 }
 
-// getVariable - get the variables from console/file
-func getVariable(variablesString, valuesFile string) (variables map[string]string, valuesMap map[string]map[string]Resource, err error) {
-	if variablesString != "" {
-		kvpairs := strings.Split(strings.Trim(variablesString, " "), ",")
-		for _, kvpair := range kvpairs {
-			kvs := strings.Split(strings.Trim(kvpair, " "), "=")
-			variables[strings.Trim(kvs[0], " ")] = strings.Trim(kvs[1], " ")
-		}
-	}
-
-	if valuesFile != "" {
-		yamlFile, err := ioutil.ReadFile(valuesFile)
-		if err != nil {
-			return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
-		}
-
-		valuesBytes, err := yaml.ToJSON(yamlFile)
-		if err != nil {
-			return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err)
-		}
-
-		values := &Values{}
-		if err := json.Unmarshal(valuesBytes, values); err != nil {
-			return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
-		}
-
-		for _, p := range values.Policies {
-			pmap := make(map[string]Resource)
-			for _, r := range p.Resources {
-				pmap[r.Name] = r
-			}
-			valuesMap[p.Name] = pmap
-		}
-	}
-
-	return variables, valuesMap, nil
-}
-
 // checkMutateLogPath - checking path for printing mutated resource (-o flag)
 func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err error) {
 	if mutateLogPath != "" {
@@ -347,68 +310,6 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro
 	return mutateLogPathIsDir, err
 }
 
-// getPoliciesFromPaths - get policies according to the resource path
-func getPoliciesFromPaths(policyPaths []string) (policies []*v1.ClusterPolicy, err error) {
-	if len(policyPaths) > 0 && policyPaths[0] == "-" {
-		if common.IsInputFromPipe() {
-			policyStr := ""
-			scanner := bufio.NewScanner(os.Stdin)
-			for scanner.Scan() {
-				policyStr = policyStr + scanner.Text() + "\n"
-			}
-
-			yamlBytes := []byte(policyStr)
-			policies, err = utils.GetPolicy(yamlBytes)
-			if err != nil {
-				return nil, sanitizederror.NewWithError("failed to extract the resources", err)
-			}
-		}
-	} else {
-		var errors []error
-		policies, errors = common.GetPolicies(policyPaths)
-		if len(policies) == 0 {
-			if len(errors) > 0 {
-				return nil, sanitizederror.NewWithErrors("failed to read policies", errors)
-			}
-
-			return nil, sanitizederror.New(fmt.Sprintf("no policies found in paths %v", policyPaths))
-		}
-
-		if len(errors) > 0 && log.Log.V(1).Enabled() {
-			fmt.Printf("ignoring errors: \n")
-			for _, e := range errors {
-				fmt.Printf("    %v \n", e.Error())
-			}
-		}
-	}
-	return
-}
-
-// getResourceAccordingToResourcePath - get resources according to the resource path
-func getResourceAccordingToResourcePath(resourcePaths []string, cluster bool, policies []*v1.ClusterPolicy, dClient *client.Client, namespace string, policyReport bool) (resources []*unstructured.Unstructured, err error) {
-	if len(resourcePaths) > 0 && resourcePaths[0] == "-" {
-		if common.IsInputFromPipe() {
-			resourceStr := ""
-			scanner := bufio.NewScanner(os.Stdin)
-			for scanner.Scan() {
-				resourceStr = resourceStr + scanner.Text() + "\n"
-			}
-
-			yamlBytes := []byte(resourceStr)
-			resources, err = common.GetResource(yamlBytes)
-			if err != nil {
-				return nil, sanitizederror.NewWithError("failed to extract the resources", err)
-			}
-		}
-	} else if (len(resourcePaths) > 0 && resourcePaths[0] != "-") || len(resourcePaths) < 0 || cluster {
-		resources, err = common.GetResources(policies, resourcePaths, dClient, cluster, namespace, policyReport)
-		if err != nil {
-			return resources, err
-		}
-	}
-	return resources, err
-}
-
 // printReportOrViolation - printing policy report/violations
 func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *resultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []SkippedPolicy) {
 	if policyReport {
@@ -437,165 +338,6 @@ func printReportOrViolation(policyReport bool, validateEngineResponses []*respon
 	}
 }
 
-// applyPolicyOnResource - function to apply policy on resource
-func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
-	mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string,
-	rc *resultCounts, policyReport bool) ([]*response.EngineResponse, *response.EngineResponse, error) {
-
-	responseError := false
-	engineResponses := make([]*response.EngineResponse, 0)
-
-	resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
-	log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
-
-	ctx := context.NewContext()
-	for key, value := range variables {
-		startString := ""
-		endString := ""
-		for _, k := range strings.Split(key, ".") {
-			startString += fmt.Sprintf(`{"%s":`, k)
-			endString += `}`
-		}
-
-		midString := fmt.Sprintf(`"%s"`, value)
-		finalString := startString + midString + endString
-		var jsonData = []byte(finalString)
-		ctx.AddJSON(jsonData)
-	}
-
-	mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
-	engineResponses = append(engineResponses, mutateResponse)
-
-	if !mutateResponse.IsSuccessful() {
-		fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath)
-		for i, r := range mutateResponse.PolicyResponse.Rules {
-			fmt.Printf("\n%d. %s", i+1, r.Message)
-		}
-		responseError = true
-	} else {
-		if len(mutateResponse.PolicyResponse.Rules) > 0 {
-			yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
-			if err != nil {
-				rc.error++
-			}
-
-			if mutateLogPath == "" {
-				mutatedResource := string(yamlEncodedResource)
-				if len(strings.TrimSpace(mutatedResource)) > 0 {
-					fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
-					fmt.Printf("\n" + mutatedResource)
-					fmt.Printf("\n")
-				}
-			} else {
-				err := printMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated")
-				if err != nil {
-					return engineResponses, &response.EngineResponse{}, sanitizederror.NewWithError("failed to print mutated result", err)
-				}
-				fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
-			}
-
-		}
-	}
-
-	if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 {
-		if policy.HasAutoGenAnnotation() {
-			if _, ok := policy.GetAnnotations()[engine.PodControllersAnnotation]; ok {
-				delete(policy.Annotations, engine.PodControllersAnnotation)
-			}
-		}
-	}
-
-	policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
-	validateResponse := engine.Validate(policyCtx)
-	if !policyReport {
-		if !validateResponse.IsSuccessful() {
-			fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
-			for i, r := range validateResponse.PolicyResponse.Rules {
-				if !r.Success {
-					fmt.Printf("%d. %s: %s \n", i+1, r.Name, r.Message)
-				}
-			}
-
-			responseError = true
-		}
-	}
-
-	var policyHasGenerate bool
-	for _, rule := range policy.Spec.Rules {
-		if rule.HasGenerate() {
-			policyHasGenerate = true
-		}
-	}
-
-	if policyHasGenerate {
-		ctx := &engine.PolicyContext{Policy: *policy, NewResource: *resource}
-		generateResponse := engine.Generate(ctx)
-		engineResponses = append(engineResponses, generateResponse)
-		if len(generateResponse.PolicyResponse.Rules) > 0 {
-			log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
-		} else {
-			fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath)
-			for i, r := range generateResponse.PolicyResponse.Rules {
-				fmt.Printf("%d. %s \b", i+1, r.Message)
-			}
-
-			responseError = true
-		}
-	}
-
-	if responseError == true {
-		rc.fail++
-	} else {
-		rc.pass++
-	}
-
-	return engineResponses, validateResponse, nil
-}
-
-// mutatePolicies - function to apply mutation on policies
-func mutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
-	newPolicies := make([]*v1.ClusterPolicy, 0)
-	logger := log.Log.WithName("apply")
-
-	for _, policy := range policies {
-		p, err := common.MutatePolicy(policy, logger)
-		if err != nil {
-			if !sanitizederror.IsErrorSanitized(err) {
-				return nil, sanitizederror.NewWithError("failed to mutate policy.", err)
-			}
-			return nil, err
-		}
-		newPolicies = append(newPolicies, p)
-	}
-	return newPolicies, nil
-}
-
-// printMutatedOutput - function to print output in provided file or directory
-func printMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml string, fileName string) error {
-	var f *os.File
-	var err error
-	yaml = yaml + ("\n---\n\n")
-
-	if !mutateLogPathIsDir {
-		f, err = os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0644)
-	} else {
-		f, err = os.OpenFile(mutateLogPath+"/"+fileName+".yaml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
-	}
-
-	if err != nil {
-		return err
-	}
-	if _, err := f.Write([]byte(yaml)); err != nil {
-		f.Close()
-		return err
-	}
-	if err := f.Close(); err != nil {
-		return err
-	}
-
-	return nil
-}
-
 // createFileOrFolder - creating file or folder according to path provided
 func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
 	mutateLogPath = filepath.Clean(mutateLogPath)
@@ -644,16 +386,3 @@ func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
 	return nil
 }
 
-// removeDuplicatevariables - remove duplicate variables
-func removeDuplicatevariables(matches [][]string) string {
-	var variableStr string
-	for _, m := range matches {
-		for _, v := range m {
-			foundVariable := strings.Contains(variableStr, v)
-			if !foundVariable {
-				variableStr = variableStr + " " + v
-			}
-		}
-	}
-	return variableStr
-}
diff --git a/pkg/kyverno/common/common.go b/pkg/kyverno/common/common.go
index 751c722f3..8ab85bf9b 100644
--- a/pkg/kyverno/common/common.go
+++ b/pkg/kyverno/common/common.go
@@ -9,6 +9,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
 	jsonpatch "github.com/evanphx/json-patch"
@@ -20,9 +21,32 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/util/yaml"
 	yaml_v2 "sigs.k8s.io/yaml"
+	"github.com/kyverno/kyverno/pkg/engine"
+	"github.com/kyverno/kyverno/pkg/engine/context"
+	"github.com/kyverno/kyverno/pkg/engine/response"
+	yamlv2 "gopkg.in/yaml.v2"
+	"github.com/go-git/go-billy/v5"
+	ut "github.com/kyverno/kyverno/pkg/utils"
+	client "github.com/kyverno/kyverno/pkg/dclient"
 )
 
 // GetPolicies - Extracting the policies from multiple YAML
+
+type Resource struct {
+	Name   string            `json:"name"`
+	Values map[string]string `json:"values"`
+}
+
+type Policy struct {
+	Name      string     `json:"name"`
+	Resources []Resource `json:"resources"`
+}
+
+type Values struct {
+	Policies []Policy `json:"policies"`
+}
+
+
 func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) {
 	for _, path := range paths {
 		log.Log.V(5).Info("reading policies", "path", path)
@@ -222,3 +246,300 @@ func IsInputFromPipe() bool {
 	fileInfo, _ := os.Stdin.Stat()
 	return fileInfo.Mode()&os.ModeCharDevice == 0
 }
+
+
+// RemoveDuplicateVariables - remove duplicate variables
+func RemoveDuplicateVariables(matches [][]string) string {
+	var variableStr string
+	for _, m := range matches {
+		for _, v := range m {
+			foundVariable := strings.Contains(variableStr, v)
+			if !foundVariable {
+				variableStr = variableStr + " " + v
+			}
+		}
+	}
+	return variableStr
+}
+
+// GetVariable - get the variables from console/file
+func GetVariable(variablesString, valuesFile string) (  map[string]string,  map[string]map[string]Resource,  error) {
+	valuesMap := make(map[string]map[string]Resource)
+	variables := make(map[string]string)
+	if variablesString != "" {
+		kvpairs := strings.Split(strings.Trim(variablesString, " "), ",")
+		for _, kvpair := range kvpairs {
+			kvs := strings.Split(strings.Trim(kvpair, " "), "=")
+			variables[strings.Trim(kvs[0], " ")] = strings.Trim(kvs[1], " ")
+		}
+	}
+	if valuesFile != "" {
+		yamlFile, err := ioutil.ReadFile(valuesFile)
+		if err != nil {
+			return variables, valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
+		}
+
+		valuesBytes, err := yaml.ToJSON(yamlFile)
+		if err != nil {
+			return variables, valuesMap, sanitizederror.NewWithError("failed to convert json", err)
+		}
+
+		values := &Values{}
+		if err := json.Unmarshal(valuesBytes, values); err != nil {
+			return variables, valuesMap, sanitizederror.NewWithError("failed to decode yaml", err)
+		}
+
+		for _, p := range values.Policies {
+			pmap := make(map[string]Resource)
+			for _, r := range p.Resources {
+				pmap[r.Name] = r
+			}
+			valuesMap[p.Name] = pmap
+		}
+	}
+
+	return variables, valuesMap, nil
+}
+
+// MutatePolices - function to apply mutation on policies
+func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
+	newPolicies := make([]*v1.ClusterPolicy, 0)
+	logger := log.Log.WithName("apply")
+
+	for _, policy := range policies {
+		p, err := MutatePolicy(policy, logger)
+		if err != nil {
+			if !sanitizederror.IsErrorSanitized(err) {
+				return nil, sanitizederror.NewWithError("failed to mutate policy.", err)
+			}
+			return nil, err
+		}
+		newPolicies = append(newPolicies, p)
+	}
+	return newPolicies, nil
+}
+
+// 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) ([]*response.EngineResponse, *response.EngineResponse,  bool,  bool, error) {
+
+	responseError := false
+	rcError := false
+	engineResponses := make([]*response.EngineResponse, 0)
+
+	resPath := fmt.Sprintf("%s/%s/%s", resource.GetNamespace(), resource.GetKind(), resource.GetName())
+	log.Log.V(3).Info("applying policy on resource", "policy", policy.Name, "resource", resPath)
+
+	ctx := context.NewContext()
+	for key, value := range variables {
+		startString := ""
+		endString := ""
+		for _, k := range strings.Split(key, ".") {
+			startString += fmt.Sprintf(`{"%s":`, k)
+			endString += `}`
+		}
+
+		midString := fmt.Sprintf(`"%s"`, value)
+		finalString := startString + midString + endString
+		var jsonData = []byte(finalString)
+		ctx.AddJSON(jsonData)
+	}
+
+	mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx})
+	engineResponses = append(engineResponses, mutateResponse)
+
+	if !mutateResponse.IsSuccessful() {
+		fmt.Printf("Failed to apply mutate policy %s -> resource %s", policy.Name, resPath)
+		for i, r := range mutateResponse.PolicyResponse.Rules {
+			fmt.Printf("\n%d. %s", i+1, r.Message)
+		}
+		responseError = true
+	} else {
+		if len(mutateResponse.PolicyResponse.Rules) > 0 {
+			yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
+			if err != nil {
+				rcError = true
+			}
+
+			if mutateLogPath == "" {
+				mutatedResource := string(yamlEncodedResource)
+				if len(strings.TrimSpace(mutatedResource)) > 0 {
+					fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
+					fmt.Printf("\n" + mutatedResource)
+					fmt.Printf("\n")
+				}
+			} else {
+				err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resource.GetName()+"-mutated")
+				if err != nil {
+					return engineResponses, &response.EngineResponse{}, responseError, rcError, sanitizederror.NewWithError("failed to print mutated result", err)
+				}
+				fmt.Printf("\n\nMutation:\nMutation has been applied successfully. Check the files.")
+			}
+
+		}
+	}
+
+	if resource.GetKind() == "Pod" && len(resource.GetOwnerReferences()) > 0 {
+		if policy.HasAutoGenAnnotation() {
+			if _, ok := policy.GetAnnotations()[engine.PodControllersAnnotation]; ok {
+				delete(policy.Annotations, engine.PodControllersAnnotation)
+			}
+		}
+	}
+
+	policyCtx := &engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource, JSONContext: ctx}
+	validateResponse := engine.Validate(policyCtx)
+	if !policyReport {
+		if !validateResponse.IsSuccessful() {
+			fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
+			for i, r := range validateResponse.PolicyResponse.Rules {
+				if !r.Success {
+					fmt.Printf("%d. %s: %s \n", i+1, r.Name, r.Message)
+				}
+			}
+
+			responseError = true
+		}
+	}
+
+	var policyHasGenerate bool
+	for _, rule := range policy.Spec.Rules {
+		if rule.HasGenerate() {
+			policyHasGenerate = true
+		}
+	}
+
+	if policyHasGenerate {
+		generateResponse := engine.Generate(&engine.PolicyContext{Policy: *policy, NewResource: *resource})
+		engineResponses = append(engineResponses, generateResponse)
+		if len(generateResponse.PolicyResponse.Rules) > 0 {
+			log.Log.V(3).Info("generate resource is valid", "policy", policy.Name, "resource", resPath)
+		} else {
+			fmt.Printf("generate policy %s resource %s is invalid \n", policy.Name, resPath)
+			for i, r := range generateResponse.PolicyResponse.Rules {
+				fmt.Printf("%d. %s \b", i+1, r.Message)
+			}
+
+			responseError = true
+		}
+	}
+	
+	return engineResponses, validateResponse, responseError, rcError, nil
+}
+
+// PrintMutatedOutput - function to print output in provided file or directory
+func PrintMutatedOutput(mutateLogPath string, mutateLogPathIsDir bool, yaml string, fileName string) error {
+	var f *os.File
+	var err error
+	yaml = yaml + ("\n---\n\n")
+
+	if !mutateLogPathIsDir {
+		f, err = os.OpenFile(mutateLogPath, os.O_APPEND|os.O_WRONLY, 0644)
+	} else {
+		f, err = os.OpenFile(mutateLogPath+"/"+fileName+".yaml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	}
+
+	if err != nil {
+		return err
+	}
+	if _, err := f.Write([]byte(yaml)); err != nil {
+		f.Close()
+		return err
+	}
+	if err := f.Close(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetPoliciesFromPaths - get policies according to the resource path
+func GetPoliciesFromPaths(fs billy.Filesystem, dirPath []string, isGit bool) (policies []*v1.ClusterPolicy, err error) {
+	var errors []error
+	if isGit {
+		for _, pp := range dirPath {
+			filep, err := fs.Open(pp)
+			bytes, err := ioutil.ReadAll(filep)
+			if err != nil {
+				fmt.Printf("Error: failed to read file %s: %v", filep.Name(), err.Error())
+			}
+			policyBytes, err := yaml.ToJSON(bytes)
+			if err != nil {
+				fmt.Printf("failed to convert to JSON: %v", err)
+				continue
+			}
+			policiesFromFile, errFromFile := ut.GetPolicy(policyBytes)
+			if errFromFile != nil {
+				err := fmt.Errorf("failed to process : %v", errFromFile.Error())
+				errors = append(errors, err)
+				continue
+			}
+			policies = append(policies, policiesFromFile...)
+		}
+	} else {
+		if len(dirPath) > 0 && dirPath[0] == "-" {
+			if IsInputFromPipe() {
+				policyStr := ""
+				scanner := bufio.NewScanner(os.Stdin)
+				for scanner.Scan() {
+					policyStr = policyStr + scanner.Text() + "\n"
+				}
+				yamlBytes := []byte(policyStr)
+				policies, err = ut.GetPolicy(yamlBytes)
+				if err != nil {
+					return nil, sanitizederror.NewWithError("failed to extract the resources", err)
+				}
+			}
+		} else {
+			var errors []error
+			policies, errors = GetPolicies(dirPath)
+			if len(policies) == 0 {
+				if len(errors) > 0 {
+					return nil, sanitizederror.NewWithErrors("failed to read file", errors)
+				}
+				return nil, sanitizederror.New(fmt.Sprintf("no file found in paths %v", dirPath))
+			}
+			if len(errors) > 0 && log.Log.V(1).Enabled() {
+				fmt.Printf("ignoring errors: \n")
+				for _, e := range errors {
+					fmt.Printf("    %v \n", e.Error())
+				}
+			}
+		}
+	}	
+	return
+}
+
+// GetResourceAccordingToResourcePath - get resources according to the resource path
+func GetResourceAccordingToResourcePath(fs billy.Filesystem, resourcePaths []string, 
+	cluster bool, policies []*v1.ClusterPolicy, dClient *client.Client, namespace string, policyReport bool, isGit bool) (resources []*unstructured.Unstructured, err error) {
+	if isGit {
+		resources, err = GetResourcesWithTest(fs, policies, resourcePaths, isGit)
+		if err != nil {
+			return nil, sanitizederror.NewWithError("failed to extract the resources", err)
+		}
+	} else {
+		if len(resourcePaths) > 0 && resourcePaths[0] == "-" {
+			if IsInputFromPipe() {
+				resourceStr := ""
+				scanner := bufio.NewScanner(os.Stdin)
+				for scanner.Scan() {
+					resourceStr = resourceStr + scanner.Text() + "\n"
+				}
+
+				yamlBytes := []byte(resourceStr)
+				resources, err = GetResource(yamlBytes)
+				if err != nil {
+					return nil, sanitizederror.NewWithError("failed to extract the resources", err)
+				}
+			}
+		} else if (len(resourcePaths) > 0 && resourcePaths[0] != "-") || len(resourcePaths) < 0 || cluster {
+			resources, err = GetResources(policies, resourcePaths, dClient, cluster, namespace, policyReport)
+			if err != nil {
+				return resources, err
+			}
+		}
+
+	}
+	return resources, err
+}
\ No newline at end of file
diff --git a/pkg/kyverno/common/fetch.go b/pkg/kyverno/common/fetch.go
index 3f4d1b0d5..a7dafe303 100644
--- a/pkg/kyverno/common/fetch.go
+++ b/pkg/kyverno/common/fetch.go
@@ -15,6 +15,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/kubernetes/scheme"
 	log "sigs.k8s.io/controller-runtime/pkg/log"
+	"github.com/go-git/go-billy/v5"
 )
 
 // GetResources gets matched resources by the given policies
@@ -96,6 +97,52 @@ func GetResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient
 	}
 	return resources, nil
 }
+// GetResourcesWithTest with gets matched resources by the given policies
+func GetResourcesWithTest(fs billy.Filesystem,policies []*v1.ClusterPolicy, resourcePaths []string, isGit bool) ([]*unstructured.Unstructured,  error) {
+	resources := make([]*unstructured.Unstructured, 0)
+	var resourceTypesMap = make(map[string]bool)
+	var resourceTypes []string
+	for _, policy := range policies {
+		for _, rule := range policy.Spec.Rules {
+			for _, kind := range rule.MatchResources.Kinds {
+				resourceTypesMap[kind] = true
+			}
+		}
+	}
+	for kind := range resourceTypesMap {
+		resourceTypes = append(resourceTypes, kind)
+	}
+	if len(resourcePaths) > 0 {
+		for _, resourcePath := range resourcePaths {
+			var resourceBytes []byte
+			var err error
+			if isGit {
+				filep, err := fs.Open(resourcePath) 
+				if err != nil {
+					fmt.Printf("Unable to open resource file: %s. error: %s", resourcePath, err)
+					continue
+				}
+				resourceBytes, err = ioutil.ReadAll(filep)
+			} else {
+				resourceBytes, err = getFileBytes(resourcePath)
+			}
+			if err != nil {
+				fmt.Printf("\n----------------------------------------------------------------------\nfailed to load resources: %s. \nerror: %s\n----------------------------------------------------------------------\n", resourcePath, err)
+				continue
+			}
+
+			getResources, err := GetResource(resourceBytes)
+			if err != nil {
+				return nil, err
+			}
+
+			for _, resource := range getResources {
+				resources = append(resources, resource)
+			}
+		}
+	}
+	return resources, nil
+}
 
 // GetResource converts raw bytes to unstructured object
 func GetResource(resourceBytes []byte) ([]*unstructured.Unstructured, error) {
diff --git a/pkg/kyverno/main.go b/pkg/kyverno/main.go
index 73f9ed71f..ea3bdc489 100644
--- a/pkg/kyverno/main.go
+++ b/pkg/kyverno/main.go
@@ -7,6 +7,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/kyverno/apply"
 	"github.com/kyverno/kyverno/pkg/kyverno/validate"
 	"github.com/kyverno/kyverno/pkg/kyverno/version"
+	"github.com/kyverno/kyverno/pkg/kyverno/test"
 	"github.com/spf13/cobra"
 	"k8s.io/klog"
 	"k8s.io/klog/klogr"
@@ -26,6 +27,7 @@ func CLI() {
 		version.Command(),
 		apply.Command(),
 		validate.Command(),
+		test.Command(), 
 	}
 
 	cli.AddCommand(commands...)
diff --git a/pkg/policyreport/builder.go b/pkg/policyreport/builder.go
index 6f59ef380..1569ccfee 100755
--- a/pkg/policyreport/builder.go
+++ b/pkg/policyreport/builder.go
@@ -13,18 +13,18 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/response"
 	"github.com/kyverno/kyverno/pkg/engine/utils"
 	v1 "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/runtime"
 	"k8s.io/apimachinery/pkg/types"
 )
 
 const (
-	clusterreportchangerequest string = "clusterreportchangerequest"
-	resourceLabelNamespace     string = "kyverno.io/resource.namespace"
-	deletedLabelResource       string = "kyverno.io/delete.resource.name"
-	deletedLabelResourceKind   string = "kyverno.io/delete.resource.kind"
-	deletedLabelPolicy         string = "kyverno.io/delete.policy"
-	deletedLabelRule           string = "kyverno.io/delete.rule"
+	resourceLabelNamespace   string = "kyverno.io/resource.namespace"
+	deletedLabelResource     string = "kyverno.io/delete.resource.name"
+	deletedLabelResourceKind string = "kyverno.io/delete.resource.kind"
+	deletedLabelPolicy       string = "kyverno.io/delete.policy"
+	deletedLabelRule         string = "kyverno.io/delete.rule"
 )
 
 func generatePolicyReportName(ns string) string {
@@ -72,7 +72,7 @@ type requestBuilder struct {
 }
 
 // NewBuilder ...
-func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernolister.PolicyLister) *requestBuilder {
+func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernolister.PolicyLister) Builder {
 	return &requestBuilder{cpolLister: cpolLister, polLister: polLister}
 }
 
@@ -123,6 +123,7 @@ func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured,
 		}
 	}
 
+	req.SetCreationTimestamp(metav1.Now())
 	return req, nil
 }
 
@@ -152,7 +153,7 @@ func set(obj *unstructured.Unstructured, info Info) {
 	obj.SetAPIVersion(request.SchemeGroupVersion.Group + "/" + request.SchemeGroupVersion.Version)
 
 	if info.Namespace == "" {
-		obj.SetGenerateName(clusterreportchangerequest + "-")
+		obj.SetGenerateName("crcr-")
 		obj.SetKind("ClusterReportChangeRequest")
 	} else {
 		obj.SetGenerateName("rcr-")
diff --git a/pkg/policyreport/changerequestcreator.go b/pkg/policyreport/changerequestcreator.go
new file mode 100644
index 000000000..bd6fbafc7
--- /dev/null
+++ b/pkg/policyreport/changerequestcreator.go
@@ -0,0 +1,240 @@
+package policyreport
+
+import (
+	"crypto/rand"
+	"math/big"
+	"reflect"
+	"sync"
+	"time"
+
+	"github.com/go-logr/logr"
+	"github.com/kyverno/kyverno/pkg/config"
+	dclient "github.com/kyverno/kyverno/pkg/dclient"
+	cache "github.com/patrickmn/go-cache"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+// creator is an interface that buffers report change requests
+// merges and creates requests every tickerInterval
+type creator interface {
+	add(request *unstructured.Unstructured)
+	create(request *unstructured.Unstructured) error
+	run(stopChan <-chan struct{})
+}
+
+type changeRequestCreator struct {
+	dclient *dclient.Client
+
+	// addCache preserves requests that are to be added to report
+	RCRCache *cache.Cache
+
+	CRCRCache *cache.Cache
+	// removeCache preserves requests that are to be removed from report
+	// removeCache *cache.Cache
+	mutex sync.RWMutex
+	queue []string
+
+	tickerInterval time.Duration
+
+	log logr.Logger
+}
+
+func newChangeRequestCreator(client *dclient.Client, tickerInterval time.Duration, log logr.Logger) creator {
+	return &changeRequestCreator{
+		dclient:        client,
+		RCRCache:       cache.New(0, 24*time.Hour),
+		CRCRCache:      cache.New(0, 24*time.Hour),
+		queue:          []string{},
+		tickerInterval: tickerInterval,
+		log:            log,
+	}
+}
+
+func (c *changeRequestCreator) add(request *unstructured.Unstructured) {
+	uid, _ := rand.Int(rand.Reader, big.NewInt(100000))
+
+	switch request.GetKind() {
+	case "ClusterReportChangeRequest":
+		c.CRCRCache.Add(uid.String(), request, cache.NoExpiration)
+	case "ReportChangeRequest":
+		c.RCRCache.Add(uid.String(), request, cache.NoExpiration)
+	default:
+		return
+	}
+
+	c.mutex.Lock()
+	c.queue = append(c.queue, uid.String())
+	c.mutex.Unlock()
+}
+
+func (c *changeRequestCreator) create(request *unstructured.Unstructured) error {
+	ns := ""
+	if request.GetKind() == "ReportChangeRequest" {
+		ns = config.KyvernoNamespace
+	}
+	_, err := c.dclient.CreateResource(request.GetAPIVersion(), request.GetKind(), ns, request, false)
+	return err
+}
+
+func (c *changeRequestCreator) run(stopChan <-chan struct{}) {
+	ticker := time.NewTicker(c.tickerInterval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			requests, size := c.mergeRequests()
+			for _, request := range requests {
+				if err := c.create(request); err != nil {
+					c.log.Error(err, "failed to create report change request", "req", request.Object)
+				}
+			}
+
+			c.cleanupQueue(size)
+		case <-stopChan:
+			return
+		}
+	}
+}
+
+func (c *changeRequestCreator) cleanupQueue(size int) {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+
+	for i := 0; i < size; i++ {
+		uid := c.queue[i]
+		c.CRCRCache.Delete(uid)
+		c.RCRCache.Delete(uid)
+	}
+
+	c.queue = c.queue[size:]
+}
+
+// mergeRequests merges all current cached requests
+// it blocks writing to the cache
+func (c *changeRequestCreator) mergeRequests() (results []*unstructured.Unstructured, size int) {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
+
+	mergedCRCR := &unstructured.Unstructured{}
+	mergedRCR := make(map[string]*unstructured.Unstructured, 0)
+	size = len(c.queue)
+
+	for _, uid := range c.queue {
+		if unstr, ok := c.CRCRCache.Get(uid); ok {
+			if crcr, ok := unstr.(*unstructured.Unstructured); ok {
+				if isDeleteRequest(crcr) {
+					if !reflect.DeepEqual(mergedCRCR, &unstructured.Unstructured{}) {
+						results = append(results, mergedCRCR)
+						mergedCRCR = &unstructured.Unstructured{}
+					}
+
+					results = append(results, crcr)
+				} else {
+					if reflect.DeepEqual(mergedCRCR, &unstructured.Unstructured{}) {
+						mergedCRCR = crcr
+						continue
+					}
+
+					if ok := merge(mergedCRCR, crcr); !ok {
+						results = append(results, mergedCRCR)
+						mergedCRCR = crcr
+					}
+				}
+			}
+			continue
+		}
+
+		if unstr, ok := c.RCRCache.Get(uid); ok {
+			if rcr, ok := unstr.(*unstructured.Unstructured); ok {
+				resourceNS := rcr.GetLabels()[resourceLabelNamespace]
+				mergedNamespacedRCR, ok := mergedRCR[resourceNS]
+				if !ok {
+					mergedNamespacedRCR = &unstructured.Unstructured{}
+				}
+
+				if isDeleteRequest(rcr) {
+					if !reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) {
+						results = append(results, mergedNamespacedRCR)
+						mergedRCR[resourceNS] = &unstructured.Unstructured{}
+					}
+
+					results = append(results, rcr)
+				} else {
+					if reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) {
+						mergedRCR[resourceNS] = rcr
+						continue
+					}
+
+					if ok := merge(mergedNamespacedRCR, rcr); !ok {
+						results = append(results, mergedNamespacedRCR)
+						mergedRCR[resourceNS] = rcr
+					} else {
+						mergedRCR[resourceNS] = mergedNamespacedRCR
+					}
+				}
+			}
+		}
+	}
+
+	if !reflect.DeepEqual(mergedCRCR, &unstructured.Unstructured{}) {
+		results = append(results, mergedCRCR)
+	}
+
+	for _, mergedNamespacedRCR := range mergedRCR {
+		if !reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) {
+			results = append(results, mergedNamespacedRCR)
+		}
+	}
+
+	return
+}
+
+// merge merges elements from a source object into a
+// destination object if they share the same namespace label
+func merge(dst, src *unstructured.Unstructured) bool {
+	dstNS := dst.GetLabels()[resourceLabelNamespace]
+	srcNS := src.GetLabels()[resourceLabelNamespace]
+	if dstNS != srcNS {
+		return false
+	}
+
+	if dstResults, ok, _ := unstructured.NestedSlice(dst.UnstructuredContent(), "results"); ok {
+		if srcResults, ok, _ := unstructured.NestedSlice(src.UnstructuredContent(), "results"); ok {
+			dstResults = append(dstResults, srcResults...)
+
+			if err := unstructured.SetNestedSlice(dst.UnstructuredContent(), dstResults, "results"); err == nil {
+				addSummary(dst, src)
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func addSummary(dst, src *unstructured.Unstructured) {
+	if dstSum, ok, _ := unstructured.NestedMap(dst.UnstructuredContent(), "summary"); ok {
+		if srcSum, ok, _ := unstructured.NestedMap(src.UnstructuredContent(), "summary"); ok {
+			for key, dstVal := range dstSum {
+				if dstValInt, ok := dstVal.(int64); ok {
+					if srcVal, ok := srcSum[key].(int64); ok {
+						dstSum[key] = dstValInt + srcVal
+					}
+				}
+			}
+		}
+		unstructured.SetNestedMap(dst.UnstructuredContent(), dstSum, "summary")
+	}
+}
+
+func isDeleteRequest(request *unstructured.Unstructured) bool {
+	deleteLabels := []string{deletedLabelPolicy, deletedLabelRule, deletedLabelResource, deletedLabelResourceKind}
+	labels := request.GetLabels()
+
+	for _, l := range deleteLabels {
+		if _, ok := labels[l]; ok {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/policyreport/reportrequest.go b/pkg/policyreport/reportrequest.go
index 9a03c734b..9bf2cdb00 100755
--- a/pkg/policyreport/reportrequest.go
+++ b/pkg/policyreport/reportrequest.go
@@ -10,21 +10,14 @@ import (
 
 	"github.com/go-logr/logr"
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
-	changerequest "github.com/kyverno/kyverno/pkg/api/kyverno/v1alpha1"
 	policyreportclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
 	kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
 	requestinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1"
 	kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
 	requestlister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1alpha1"
-	"github.com/kyverno/kyverno/pkg/config"
-	client "github.com/kyverno/kyverno/pkg/dclient"
 	dclient "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/engine/response"
 	"github.com/kyverno/kyverno/pkg/policystatus"
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/tools/cache"
@@ -62,7 +55,10 @@ type Generator struct {
 
 	queue     workqueue.RateLimitingInterface
 	dataStore *dataStore
-	log       logr.Logger
+
+	requestCreator creator
+
+	log logr.Logger
 }
 
 // NewReportChangeRequestGenerator returns a new instance of report request generator
@@ -86,6 +82,7 @@ func NewReportChangeRequestGenerator(client *policyreportclient.Clientset,
 		polListerSynced:                  polInformer.Informer().HasSynced,
 		queue:                            workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
 		dataStore:                        newDataStore(),
+		requestCreator:                   newChangeRequestCreator(dclient, 3*time.Second, log.WithName("requestCreator")),
 		log:                              log,
 	}
 
@@ -191,6 +188,8 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
 		go wait.Until(gen.runWorker, time.Second, stopCh)
 	}
 
+	go gen.requestCreator.run(stopCh)
+
 	<-stopCh
 }
 
@@ -264,91 +263,17 @@ func (gen *Generator) processNextWorkItem() bool {
 
 func (gen *Generator) syncHandler(info Info) error {
 	builder := NewBuilder(gen.cpolLister, gen.polLister)
-	rcrUnstructured, err := builder.build(info)
+	reportReq, err := builder.build(info)
 	if err != nil {
 		return fmt.Errorf("unable to build reportChangeRequest: %v", err)
 	}
 
-	if rcrUnstructured == nil {
+	if reportReq == nil {
 		return nil
 	}
 
-	gen.log.V(4).Info("reconcile report change request", "key", info.ToKey())
-	return gen.sync(rcrUnstructured, info)
-}
-
-func (gen *Generator) sync(reportReq *unstructured.Unstructured, info Info) error {
-	logger := gen.log.WithName("sync report change request")
-	defer logger.V(4).Info("successfully reconciled report change request", "kind", reportReq.GetKind(), "key", info.ToKey())
-
-	reportReq.SetCreationTimestamp(v1.Now())
-	if reportReq.GetKind() == "ClusterReportChangeRequest" {
-		return gen.syncClusterReportChangeRequest(reportReq, logger)
-	}
-
-	return gen.syncReportChangeRequest(reportReq, logger)
-}
-
-func (gen *Generator) syncClusterReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
-	old, err := gen.clusterReportChangeRequestLister.Get(reportReq.GetName())
-	if err != nil {
-		if apierrors.IsNotFound(err) {
-			if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), "", reportReq, false); err != nil {
-				return fmt.Errorf("failed to create clusterReportChangeRequest: %v", err)
-			}
-
-			return nil
-		}
-		return fmt.Errorf("unable to get %s: %v", reportReq.GetKind(), err)
-	}
-
-	return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
-}
-
-func (gen *Generator) syncReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
-	old, err := gen.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).Get(reportReq.GetName())
-	if err != nil {
-		if apierrors.IsNotFound(err) {
-			if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), config.KyvernoNamespace, reportReq, false); err != nil {
-				return fmt.Errorf("failed to create ReportChangeRequest: %v", err)
-			}
-
-			return nil
-		}
-		return fmt.Errorf("unable to get existing reportChangeRequest %v", err)
-	}
-
-	return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
-}
-
-func updateReportChangeRequest(dClient *client.Client, old interface{}, new *unstructured.Unstructured, log logr.Logger) (err error) {
-	oldUnstructured := make(map[string]interface{})
-	if oldTyped, ok := old.(*changerequest.ReportChangeRequest); ok {
-		if oldUnstructured, err = runtime.DefaultUnstructuredConverter.ToUnstructured(oldTyped); err != nil {
-			return fmt.Errorf("unable to convert reportChangeRequest: %v", err)
-		}
-		new.SetResourceVersion(oldTyped.GetResourceVersion())
-		new.SetUID(oldTyped.GetUID())
-	} else {
-		oldTyped := old.(*changerequest.ClusterReportChangeRequest)
-		if oldUnstructured, err = runtime.DefaultUnstructuredConverter.ToUnstructured(oldTyped); err != nil {
-			return fmt.Errorf("unable to convert clusterReportChangeRequest: %v", err)
-		}
-		new.SetUID(oldTyped.GetUID())
-		new.SetResourceVersion(oldTyped.GetResourceVersion())
-	}
-
-	if !hasResultsChanged(oldUnstructured, new.UnstructuredContent()) {
-		log.V(4).Info("unchanged report request", "name", new.GetName())
-		return nil
-	}
-
-	if _, err = dClient.UpdateResource(new.GetAPIVersion(), new.GetKind(), config.KyvernoNamespace, new, false); err != nil {
-		return fmt.Errorf("failed to update report request: %v", err)
-	}
-
-	log.V(4).Info("successfully updated report request", "kind", new.GetKind(), "name", new.GetName())
-	return
+	gen.requestCreator.add(reportReq)
+	return nil
 }
 
 func hasResultsChanged(old, new map[string]interface{}) bool {


Test patch:
diff --git a/pkg/engine/validate/pattern_test.go b/pkg/engine/validate/pattern_test.go
index 390c74d50..7b4214dd2 100644
--- a/pkg/engine/validate/pattern_test.go
+++ b/pkg/engine/validate/pattern_test.go
@@ -196,6 +196,22 @@ func TestValidateValueWithPattern_StringsLogicalOr(t *testing.T) {
 	assert.Assert(t, ValidateValueWithPattern(log.Log, value, pattern))
 }
 
+func TestValidateValueWithPattern_StringsLogicalAnd(t *testing.T) {
+	pattern := ">1 & <20"
+	value := "10"
+	assert.Assert(t, ValidateValueWithPattern(log.Log, value, pattern))
+}
+
+func TestValidateValueWithPattern_StringsAllLogicalOperators(t *testing.T) {
+	pattern := ">1 & <20 | >31 & <33"
+	value := "10"
+	assert.Assert(t, ValidateValueWithPattern(log.Log, value, pattern))
+	value = "32"
+	assert.Assert(t, ValidateValueWithPattern(log.Log, value, pattern))
+	value = "21"
+	assert.Assert(t, !ValidateValueWithPattern(log.Log, value, pattern))
+}
+
 func TestValidateValueWithPattern_EqualTwoFloats(t *testing.T) {
 	assert.Assert(t, ValidateValueWithPattern(log.Log, 7.0, 7.000))
 }
diff --git a/pkg/engine/variables/evaluate_test.go b/pkg/engine/variables/evaluate_test.go
index 48342e5d9..8b1322444 100644
--- a/pkg/engine/variables/evaluate_test.go
+++ b/pkg/engine/variables/evaluate_test.go
@@ -66,6 +66,150 @@ func Test_Eval_NoEqual_Const_String_Fail(t *testing.T) {
 	}
 }
 
+func Test_Eval_GreaterThanOrEquals_Const_string_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_string_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_string_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1.1",
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    "2",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_string_Equal_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.GreaterThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_string_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.GreaterThan,
+		Value:    0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_string_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1.1",
+		Operator: kyverno.GreaterThan,
+		Value:    "2",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_string_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_string_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "0",
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_string_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "2.0",
+		Operator: kyverno.LessThanOrEquals,
+		Value:    "1.1",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_string_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "1",
+		Operator: kyverno.LessThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_string_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "0",
+		Operator: kyverno.LessThan,
+		Value:    1,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThan_Const_string_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      "2.0",
+		Operator: kyverno.LessThan,
+		Value:    "1.1",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
 //Bool
 
 func Test_Eval_Equal_Const_Bool_Pass(t *testing.T) {
@@ -181,6 +325,150 @@ func Test_Eval_NoEqual_Const_int_Fail(t *testing.T) {
 	}
 }
 
+func Test_Eval_GreaterThanOrEquals_Const_int_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_int_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_int_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    "2",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_int_Equal_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_int_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThan,
+		Value:    0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_int_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.GreaterThan,
+		Value:    "2",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_int_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_int_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      0,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_int_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      2,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    "1",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_int_Equal_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1,
+		Operator: kyverno.LessThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_int_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      0,
+		Operator: kyverno.LessThan,
+		Value:    1,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThan_Const_int_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      2,
+		Operator: kyverno.LessThan,
+		Value:    "1",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
 // int64
 func Test_Eval_Equal_Const_int64_Pass(t *testing.T) {
 	ctx := context.NewContext()
@@ -296,6 +584,150 @@ func Test_Eval_NoEqual_Const_float64_Fail(t *testing.T) {
 	}
 }
 
+func Test_Eval_GreaterThanOrEquals_Const_float64_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.0,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_float64_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.5,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThanOrEquals_Const_float64_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.95,
+		Operator: kyverno.GreaterThanOrEquals,
+		Value:    "2",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_float64_Equal_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.0,
+		Operator: kyverno.GreaterThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_float64_Greater_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.5,
+		Operator: kyverno.GreaterThan,
+		Value:    "0",
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_GreaterThan_Const_float64_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.95,
+		Operator: kyverno.GreaterThan,
+		Value:    "2.5",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_float64_Equal_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.0,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1.0,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_float64_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      0.5,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    1,
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThanOrEquals_Const_float64_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      2.0,
+		Operator: kyverno.LessThanOrEquals,
+		Value:    "1.95",
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_float64_Equal_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      1.0,
+		Operator: kyverno.LessThan,
+		Value:    1.0,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
+func Test_Eval_LessThan_Const_float64_Less_Pass(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      0.5,
+		Operator: kyverno.LessThan,
+		Value:    "1.5",
+	}
+	if !Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to pass")
+	}
+}
+
+func Test_Eval_LessThan_Const_float64_Fail(t *testing.T) {
+	ctx := context.NewContext()
+	condition := kyverno.Condition{
+		Key:      2.5,
+		Operator: kyverno.LessThan,
+		Value:    1.95,
+	}
+	if Evaluate(log.Log, ctx, condition) {
+		t.Error("expected to fail")
+	}
+}
+
 //object/map[string]interface
 
 func Test_Eval_Equal_Const_object_Pass(t *testing.T) {
diff --git a/pkg/kyverno/test/command.go b/pkg/kyverno/test/command.go
new file mode 100644
index 000000000..ad468d188
--- /dev/null
+++ b/pkg/kyverno/test/command.go
@@ -0,0 +1,355 @@
+package test 
+import (
+	"fmt"
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"net/url"
+	"sort"
+	"reflect"
+	"strings"
+	"github.com/spf13/cobra"
+	log "sigs.k8s.io/controller-runtime/pkg/log"
+	sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
+	v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
+	"github.com/kyverno/kyverno/pkg/kyverno/common"
+	"k8s.io/apimachinery/pkg/util/yaml"
+	policy2 "github.com/kyverno/kyverno/pkg/policy"
+	"github.com/kyverno/kyverno/pkg/openapi"
+	"github.com/kyverno/kyverno/pkg/engine/response"
+	corev1 "k8s.io/api/core/v1"
+	"github.com/kyverno/kyverno/pkg/policyreport"
+	report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
+	"github.com/kyverno/kyverno/pkg/engine/utils"
+	"github.com/kataras/tablewriter"
+	"github.com/lensesio/tableprinter"
+	"github.com/go-git/go-billy/v5/memfs"
+	"github.com/go-git/go-billy/v5"
+	"github.com/fatih/color"
+	client "github.com/kyverno/kyverno/pkg/dclient"
+)
+
+// Command returns version command
+func Command() *cobra.Command {
+
+	var  valuesFile string
+	return &cobra.Command{
+		Use:   "test",
+		Short: "Shows current test of kyverno",
+		RunE: func(cmd *cobra.Command, dirPath []string) (err error) {
+			defer func() {
+				if err != nil {
+					if !sanitizederror.IsErrorSanitized(err) {
+						log.Log.Error(err, "failed to sanitize")
+						err = fmt.Errorf("internal error")
+					}
+				}
+			}()
+			err = testCommandExecute(dirPath, valuesFile)
+			if err != nil {
+				log.Log.V(3).Info("a directory is required")
+				return err
+			}
+			return nil
+		},
+	}
+}
+
+type Test struct {
+	Name          string     `json:"name"`
+	Policies      []string     `json:"policies"`
+	Resources     []string     `json:"resources"`
+	Variables     string     `json:"variables"`
+	Results      []TestResults     `json:"results"`
+}
+
+type SkippedPolicy struct {
+	Name     string    `json:"name"`
+	Rules    []v1.Rule `json:"rules"`
+	Variable string    `json:"variable"`
+}
+
+type TestResults struct {
+	Policy    string    `json:"policy"`
+	Rule  string `json:"rule"`
+	Status string    `json:"status"`
+	Resource string    `json:"resource"`	
+}
+
+type ReportResult struct {
+	TestResults 
+	Resources []*corev1.ObjectReference `json:"resources"`
+}
+
+type Resource struct {
+	Name          string     `json:"name"`
+	Values map[string]string `json:"values"`
+}
+
+type Table struct {
+	ID    int    `header:"#"`
+	Resource string    `header:"test"`
+	Result string    `header:"result"`
+	
+}
+type Policy struct {
+	Name      string     `json:"name"`
+	Resources []Resource `json:"resources"`
+}
+
+type Values struct {
+	Policies []Policy `json:"policies"`
+}
+
+func testCommandExecute(dirPath []string, valuesFile string) (err error) {
+	var errors []error
+	fs := memfs.New()
+	
+	if len(dirPath) == 0 {
+			return  sanitizederror.NewWithError(fmt.Sprintf("a directory is required"), err)
+		}
+	if strings.Contains(string(dirPath[0]), "https://") {
+		gitUrl, err := url.Parse(dirPath[0])
+		if err != nil {
+			return  sanitizederror.NewWithError("failed to parse URL", err)
+		}
+		pathElems := strings.Split(gitUrl.Path[1:], "/")
+		if len(pathElems) != 3 {
+			err := fmt.Errorf("invalid URL path %s - expected https://github.com/:owner/:repository/:branch", gitUrl.Path)
+			return  sanitizederror.NewWithError("failed to parse URL", err)
+		}
+		gitUrl.Path = strings.Join([]string{"/", pathElems[0], pathElems[1]}, "/")
+		repoURL := gitUrl.String()
+		cloneRepo, err := clone(repoURL, fs)
+		if err != nil {
+			return  sanitizederror.NewWithError("failed to clone repository ", err)
+		}
+		log.Log.V(3).Info(" clone repository", cloneRepo )
+		policyYamls, err := listYAMLs(fs, "/")
+		if err != nil {
+			return  sanitizederror.NewWithError("failed to list YAMLs in repository", err)
+		}
+		sort.Strings(policyYamls)
+		for _, yamlFilePath := range policyYamls {
+			file, err := fs.Open(yamlFilePath)	
+			bytes, err := ioutil.ReadAll(file)
+			if err != nil {
+				sanitizederror.NewWithError("Error: failed to read file", err)
+			}
+			policyBytes, err := yaml.ToJSON(bytes)
+			if err != nil {
+				sanitizederror.NewWithError("failed to convert to JSON", err)
+				continue
+			}
+			if err := applyPoliciesFromPath(fs, policyBytes, valuesFile, true); err != nil {
+				return sanitizederror.NewWithError("failed to apply test command", err)
+			}	
+		}
+	} else {
+		path := filepath.Clean(dirPath[0])
+		fileDesc, err := os.Stat(path)
+		if err != nil {
+			errors = append(errors, err)
+		}
+		if fileDesc.IsDir() {
+			files, err := ioutil.ReadDir(path)
+			if err != nil {
+				errors = append(errors, fmt.Errorf("failed to read %v: %v", path, err.Error()))
+			}
+			for _, file := range files {
+				fmt.Printf("\napplying  test on file  %s...", file.Name())
+
+				yamlFile, err := ioutil.ReadFile(filepath.Join(path, file.Name()))
+				if err != nil {
+					return  sanitizederror.NewWithError("unable to read yaml", err)
+				}
+				valuesBytes, err := yaml.ToJSON(yamlFile)
+				if err != nil {
+					return  sanitizederror.NewWithError("failed to convert json", err)
+				}
+				if err := applyPoliciesFromPath(fs, valuesBytes, valuesFile, false); err != nil {
+					return sanitizederror.NewWithError("failed to apply test command", err)
+				}	
+			}
+		}
+		if len(errors) > 0 && log.Log.V(1).Enabled() {
+			fmt.Printf("ignoring errors: \n")
+			for _, e := range errors {
+				fmt.Printf("    %v \n", e.Error())
+			}
+		}
+	}
+	return nil
+}
+
+func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} {
+	results := make(map[string][]interface{})
+	infos := policyreport.GeneratePRsFromEngineResponse(resps, log.Log)
+	for _, info := range infos {
+		for _, infoResult := range info.Results {
+			for _, rule := range infoResult.Rules {
+				if rule.Type != utils.Validation.String() {
+					continue
+				}
+				result := report.PolicyReportResult{
+					Policy: info.PolicyName,
+					Resources: []*corev1.ObjectReference{
+						{
+							Name:       infoResult.Resource.Name,
+						},
+					},	
+				}
+				result.Rule = rule.Name
+				result.Status = report.PolicyStatus(rule.Check)
+				results[rule.Name] = append(results[rule.Name], result)
+			}
+		}
+	}
+	return results
+}
+
+
+func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool) (err error) {
+	openAPIController, err := openapi.NewOpenAPIController()
+	engineResponses := make([]*response.EngineResponse, 0)
+	validateEngineResponses := make([]*response.EngineResponse, 0)
+	skippedPolicies := make([]SkippedPolicy, 0)
+	var dClient *client.Client
+	values := &Test{}
+	var variablesString string
+
+	if err := json.Unmarshal(policyBytes, values); err != nil {
+		return sanitizederror.NewWithError("failed to decode yaml", err)
+	}	
+	_, valuesMap, err := common.GetVariable(variablesString, values.Variables)
+	if err != nil {
+		if !sanitizederror.IsErrorSanitized(err) {
+			return  sanitizederror.NewWithError("failed to decode yaml", err)
+		}
+		return  err
+	}
+	policies, err := common.GetPoliciesFromPaths(fs, values.Policies, isGit)
+	if err != nil {
+		fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
+		os.Exit(1)
+	}
+	mutatedPolicies, err := common.MutatePolices(policies)
+		if err != nil {
+		if !sanitizederror.IsErrorSanitized(err) {
+			return  sanitizederror.NewWithError("failed to mutate policy", err)
+			}
+		}
+		resources, err := common.GetResourceAccordingToResourcePath(fs, values.Resources, false,  mutatedPolicies, dClient, "", false, isGit)
+		if err != nil {
+			fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
+			os.Exit(1)
+		}
+		if err != nil {
+			fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
+			os.Exit(1)
+		}
+		msgPolicies := "1 policy"
+		if len(mutatedPolicies) > 1 {
+			msgPolicies = fmt.Sprintf("%d policies", len(policies))
+		}
+		msgResources := "1 resource"
+		if len(resources) > 1 {
+			msgResources = fmt.Sprintf("%d resources", len(resources))
+		}
+		if len(mutatedPolicies) > 0 && len(resources) > 0 {
+			fmt.Printf("\napplying %s to %s... \n", msgPolicies, msgResources)
+		}
+		for _, policy := range mutatedPolicies {
+			err := policy2.Validate(policy, nil, true, openAPIController)
+			if err != nil {
+				fmt.Println("valuesMap1")
+				log.Log.V(3).Info(fmt.Sprintf("skipping policy %v as it is not valid", policy.Name), "error", err)
+				continue
+			}
+			matches := common.PolicyHasVariables(*policy)
+			variable := common.RemoveDuplicateVariables(matches)
+			if len(matches) > 0 && variablesString == "" && values.Variables == "" {
+				skipPolicy := SkippedPolicy{
+					Name:     policy.GetName(),
+					Rules:    policy.Spec.Rules,
+					Variable: variable,
+				}
+				skippedPolicies = append(skippedPolicies, skipPolicy)
+				log.Log.V(3).Info(fmt.Sprintf("skipping policy %s", policy.Name), "error", fmt.Sprintf("policy have variable - %s", variable))
+				continue
+			}
+			for _, resource := range resources {
+				thisPolicyResourceValues := make(map[string]string)
+				if len(valuesMap[policy.GetName()]) != 0 && !reflect.DeepEqual(valuesMap[policy.GetName()][resource.GetName()], Resource{}) {
+					thisPolicyResourceValues = valuesMap[policy.GetName()][resource.GetName()].Values
+				}
+				if len(common.PolicyHasVariables(*policy)) > 0 && len(thisPolicyResourceValues) == 0 {
+					return  sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
+				}
+				
+				ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true)
+				if err != nil {
+					return  sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
+				}
+				engineResponses = append(engineResponses, ers...)
+				validateEngineResponses = append(validateEngineResponses, validateErs)
+			}
+		}
+		resultsMap := buildPolicyResults(validateEngineResponses)
+		resultErr := printTestResult(resultsMap, values.Results)
+		if resultErr != nil {
+			return  sanitizederror.NewWithError("Unable to genrate result. Error:", resultErr)
+			os.Exit(1)
+		}
+	return
+}
+
+
+func printTestResult(resps map[string][]interface {}, testResults []TestResults) (error){
+	printer := tableprinter.New(os.Stdout)
+	table := []*Table{}
+	boldRed := color.New(color.FgRed).Add(color.Bold)
+	boldFgCyan := color.New(color.FgCyan).Add(color.Bold)
+	for i, v := range testResults {
+		res := new(Table)
+		res.ID = i+1
+		res.Resource =  boldFgCyan.Sprintf(v.Resource) +  " with " + boldFgCyan.Sprintf(v.Policy)  + "/" +  boldFgCyan.Sprintf(v.Rule) 
+		n := resps[v.Rule]
+		data, _ := json.Marshal(n)
+		valuesBytes, err := yaml.ToJSON(data)
+		if err != nil {
+			return  sanitizederror.NewWithError("failed to convert json", err)
+		}
+		var r []ReportResult
+		json.Unmarshal(valuesBytes, &r)
+		res.Result = boldRed.Sprintf("Fail")
+		if len(r) != 0 {
+			var resource TestResults
+			for _, testRes := range r {
+				if testRes.Resources[0].Name == v.Resource {
+				resource.Policy = testRes.Policy 
+				resource.Rule = testRes.Rule
+				resource.Status = testRes.Status
+				resource.Resource = testRes.Resources[0].Name
+					if v == resource {
+						res.Result =  "Pass"
+					}
+				}
+			}
+		}
+		table = append(table, res)
+	}	
+	printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
+	printer.CenterSeparator = "│"
+	printer.ColumnSeparator = "│"
+	printer.RowSeparator = "─"
+	printer.RowCharLimit = 300
+	printer.RowLengthTitle  = func(rowsLength int) bool {
+		return rowsLength > 10
+	}
+	printer.HeaderBgColor = tablewriter.BgBlackColor
+	printer.HeaderFgColor = tablewriter.FgGreenColor
+	printer.Print(table)
+	return nil
+}
\ No newline at end of file
diff --git a/pkg/kyverno/test/git.go b/pkg/kyverno/test/git.go
new file mode 100644
index 000000000..8dd1d857c
--- /dev/null
+++ b/pkg/kyverno/test/git.go
@@ -0,0 +1,38 @@
+package test
+import (
+	"os"
+	"path/filepath"
+	"github.com/go-git/go-billy/v5"
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/storage/memory"
+)
+
+func clone(path string, fs billy.Filesystem) (*git.Repository, error) {
+	return git.Clone(memory.NewStorage(), fs, &git.CloneOptions{
+		URL:      path,
+		Progress: os.Stdout,
+	})
+}
+
+func listYAMLs(fs billy.Filesystem, path string) ([]string, error) {
+	path = filepath.Clean(path)
+	fis, err := fs.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+	yamls := make([]string, 0)
+	for _, fi := range fis {
+		name := filepath.Join(path, fi.Name())
+		if fi.IsDir() {
+			continue
+		}
+
+		ext := filepath.Ext(name)
+		if ext != ".yml" && ext != ".yaml" {
+			continue
+		}
+
+		yamls = append(yamls, name)
+	}
+	return yamls, nil
+}