mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 16:06:56 +00:00
adding kyverno test command with git support
Signed-off-by: vyankatesh_neualto <vyankatesh@neualto.com>
This commit is contained in:
parent
01ac9058d9
commit
ce9ab9ef69
4 changed files with 357 additions and 487 deletions
|
@ -1,10 +1,7 @@
|
||||||
package apply
|
package apply
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -14,21 +11,17 @@ import (
|
||||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||||
pkgCommon "github.com/kyverno/kyverno/pkg/common"
|
pkgCommon "github.com/kyverno/kyverno/pkg/common"
|
||||||
client "github.com/kyverno/kyverno/pkg/dclient"
|
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/engine/response"
|
||||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||||
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
|
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
|
||||||
"github.com/kyverno/kyverno/pkg/openapi"
|
"github.com/kyverno/kyverno/pkg/openapi"
|
||||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||||
"github.com/kyverno/kyverno/pkg/utils"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
yamlv2 "gopkg.in/yaml.v2"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
log "sigs.k8s.io/controller-runtime/pkg/log"
|
log "sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
yaml1 "sigs.k8s.io/yaml"
|
yaml1 "sigs.k8s.io/yaml"
|
||||||
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resultCounts struct {
|
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) {
|
variablesString string, valuesFile string, namespace string, policyPaths []string) (validateEngineResponses []*response.EngineResponse, rc *resultCounts, resources []*unstructured.Unstructured, skippedPolicies []SkippedPolicy, err error) {
|
||||||
|
|
||||||
kubernetesConfig := genericclioptions.NewConfigFlags(true)
|
kubernetesConfig := genericclioptions.NewConfigFlags(true)
|
||||||
|
fs := memfs.New()
|
||||||
|
|
||||||
if valuesFile != "" && variablesString != "" {
|
if valuesFile != "" && variablesString != "" {
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("pass the values either using set flag or values_file flag", err)
|
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 err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to decode yaml", 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)
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -204,14 +198,14 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, err
|
return validateEngineResponses, rc, resources, skippedPolicies, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mutatedPolicies, err := mutatePolices(policies)
|
mutatedPolicies, err := common.MutatePolices(policies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, sanitizederror.NewWithError("failed to mutate policy", 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 {
|
if err != nil {
|
||||||
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
|
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -245,7 +239,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := common.PolicyHasVariables(*policy)
|
matches := common.PolicyHasVariables(*policy)
|
||||||
variable := removeDuplicatevariables(matches)
|
variable := common.RemoveDuplicateVariables(matches)
|
||||||
|
|
||||||
if len(matches) > 0 && variablesString == "" && valuesFile == "" {
|
if len(matches) > 0 && variablesString == "" && valuesFile == "" {
|
||||||
rc.skip++
|
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)
|
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 {
|
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)
|
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...)
|
engineResponses = append(engineResponses, ers...)
|
||||||
validateEngineResponses = append(validateEngineResponses, validateErs)
|
validateEngineResponses = append(validateEngineResponses, validateErs)
|
||||||
}
|
}
|
||||||
|
@ -287,44 +288,6 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
|
||||||
return validateEngineResponses, rc, resources, skippedPolicies, nil
|
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)
|
// checkMutateLogPath - checking path for printing mutated resource (-o flag)
|
||||||
func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err error) {
|
func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err error) {
|
||||||
if mutateLogPath != "" {
|
if mutateLogPath != "" {
|
||||||
|
@ -347,68 +310,6 @@ func checkMutateLogPath(mutateLogPath string) (mutateLogPathIsDir bool, err erro
|
||||||
return mutateLogPathIsDir, err
|
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
|
// printReportOrViolation - printing policy report/violations
|
||||||
func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *resultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []SkippedPolicy) {
|
func printReportOrViolation(policyReport bool, validateEngineResponses []*response.EngineResponse, rc *resultCounts, resourcePaths []string, resourcesLen int, skippedPolicies []SkippedPolicy) {
|
||||||
if policyReport {
|
if policyReport {
|
||||||
|
@ -437,164 +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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// createFileOrFolder - creating file or folder according to path provided
|
||||||
func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
|
func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
|
||||||
mutateLogPath = filepath.Clean(mutateLogPath)
|
mutateLogPath = filepath.Clean(mutateLogPath)
|
||||||
|
@ -643,16 +386,3 @@ func createFileOrFolder(mutateLogPath string, mutateLogPathIsDir bool) error {
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
|
@ -20,9 +21,32 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
yaml_v2 "sigs.k8s.io/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
|
// 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) {
|
func GetPolicies(paths []string) (policies []*v1.ClusterPolicy, errors []error) {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
log.Log.V(5).Info("reading policies", "path", path)
|
log.Log.V(5).Info("reading policies", "path", path)
|
||||||
|
@ -222,3 +246,299 @@ func IsInputFromPipe() bool {
|
||||||
fileInfo, _ := os.Stdin.Stat()
|
fileInfo, _ := os.Stdin.Stat()
|
||||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
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) (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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"reflect"
|
"reflect"
|
||||||
"bufio"
|
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
log "sigs.k8s.io/controller-runtime/pkg/log"
|
log "sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
@ -16,23 +15,19 @@ import (
|
||||||
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
|
||||||
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
"github.com/kyverno/kyverno/pkg/kyverno/common"
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
policy2 "github.com/kyverno/kyverno/pkg/policy"
|
||||||
"github.com/kyverno/kyverno/pkg/engine"
|
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
|
||||||
"github.com/kyverno/kyverno/pkg/openapi"
|
"github.com/kyverno/kyverno/pkg/openapi"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||||
yamlv2 "gopkg.in/yaml.v2"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"github.com/kyverno/kyverno/pkg/policyreport"
|
"github.com/kyverno/kyverno/pkg/policyreport"
|
||||||
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
|
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||||
ut "github.com/kyverno/kyverno/pkg/utils"
|
|
||||||
"github.com/kataras/tablewriter"
|
"github.com/kataras/tablewriter"
|
||||||
"github.com/lensesio/tableprinter"
|
"github.com/lensesio/tableprinter"
|
||||||
"github.com/go-git/go-billy/v5/memfs"
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
client "github.com/kyverno/kyverno/pkg/dclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command returns version command
|
// Command returns version command
|
||||||
|
@ -51,9 +46,9 @@ func Command() *cobra.Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = testCommandHelper(dirPath, valuesFile)
|
err = testCommandExecute(dirPath, valuesFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.V(3).Info("Test command fail to apply")
|
log.Log.V(3).Info("a directory is required")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -66,7 +61,7 @@ type Test struct {
|
||||||
Policies []string `json:"policies"`
|
Policies []string `json:"policies"`
|
||||||
Resources []string `json:"resources"`
|
Resources []string `json:"resources"`
|
||||||
Variables string `json:"variables"`
|
Variables string `json:"variables"`
|
||||||
TResults []TestResults `json:"results"`
|
Results []TestResults `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SkippedPolicy struct {
|
type SkippedPolicy struct {
|
||||||
|
@ -107,14 +102,14 @@ type Values struct {
|
||||||
Policies []Policy `json:"policies"`
|
Policies []Policy `json:"policies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCommandHelper(dirPath []string, valuesFile string) (err error) {
|
func testCommandExecute(dirPath []string, valuesFile string) (err error) {
|
||||||
var errors []error
|
var errors []error
|
||||||
fs := memfs.New()
|
fs := memfs.New()
|
||||||
|
|
||||||
if len(dirPath) == 0 {
|
if len(dirPath) == 0 {
|
||||||
return sanitizederror.NewWithError(fmt.Sprintf("require test yamls"), err)
|
return sanitizederror.NewWithError(fmt.Sprintf("a directory is required"), err)
|
||||||
}
|
}
|
||||||
if strings.Contains(string(dirPath[0]), "https://github.com/") {
|
if strings.Contains(string(dirPath[0]), "https://") {
|
||||||
gitUrl, err := url.Parse(dirPath[0])
|
gitUrl, err := url.Parse(dirPath[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sanitizederror.NewWithError("failed to parse URL", err)
|
return sanitizederror.NewWithError("failed to parse URL", err)
|
||||||
|
@ -187,147 +182,6 @@ func testCommandHelper(dirPath []string, valuesFile string) (err error) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
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 common.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 = common.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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
func getResourceAccordingToResourcePath(fs billy.Filesystem,resourcePaths []string, policies []*v1.ClusterPolicy, isGit bool) (resources []*unstructured.Unstructured, err error) {
|
|
||||||
resources, err = common.GetResourcesWithTest(fs, policies, resourcePaths, isGit)
|
|
||||||
if err != nil {
|
|
||||||
return resources, err
|
|
||||||
}
|
|
||||||
return resources, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured) ([]*response.EngineResponse, *response.EngineResponse, error) {
|
|
||||||
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()
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(mutateResponse.PolicyResponse.Rules) > 0 {
|
|
||||||
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
|
|
||||||
if err != nil {
|
|
||||||
log.Log.V(3).Info(fmt.Sprintf("yaml encoded resource not valid"), "error", err)
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return engineResponses, validateResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} {
|
func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface{} {
|
||||||
results := make(map[string][]interface{})
|
results := make(map[string][]interface{})
|
||||||
|
@ -354,73 +208,39 @@ func buildPolicyResults(resps []*response.EngineResponse) map[string][]interface
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
func getVariable( valuesFile string) ( valuesMap map[string]map[string]Resource, err error) {
|
|
||||||
if valuesFile != "" {
|
|
||||||
yamlFile, err := ioutil.ReadFile(valuesFile)
|
|
||||||
if err != nil {
|
|
||||||
return valuesMap, sanitizederror.NewWithError("unable to read yaml", err)
|
|
||||||
}
|
|
||||||
valuesBytes, err := yaml.ToJSON(yamlFile)
|
|
||||||
if err != nil {
|
|
||||||
return valuesMap, sanitizederror.NewWithError("failed to convert json", err)
|
|
||||||
}
|
|
||||||
values := &Values{}
|
|
||||||
if err := json.Unmarshal(valuesBytes, values); err != nil {
|
|
||||||
return 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 valuesMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool) (err error) {
|
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool) (err error) {
|
||||||
openAPIController, err := openapi.NewOpenAPIController()
|
openAPIController, err := openapi.NewOpenAPIController()
|
||||||
engineResponses := make([]*response.EngineResponse, 0)
|
engineResponses := make([]*response.EngineResponse, 0)
|
||||||
validateEngineResponses := make([]*response.EngineResponse, 0)
|
validateEngineResponses := make([]*response.EngineResponse, 0)
|
||||||
skippedPolicies := make([]SkippedPolicy, 0)
|
skippedPolicies := make([]SkippedPolicy, 0)
|
||||||
|
var dClient *client.Client
|
||||||
values := &Test{}
|
values := &Test{}
|
||||||
|
var variablesString string
|
||||||
|
|
||||||
if err := json.Unmarshal(policyBytes, values); err != nil {
|
if err := json.Unmarshal(policyBytes, values); err != nil {
|
||||||
return sanitizederror.NewWithError("failed to decode yaml", err)
|
return sanitizederror.NewWithError("failed to decode yaml", err)
|
||||||
}
|
}
|
||||||
valuesMap, err := getVariable(values.Variables)
|
_, valuesMap, err := common.GetVariable(variablesString, values.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return sanitizederror.NewWithError("failed to decode yaml", err)
|
return sanitizederror.NewWithError("failed to decode yaml", err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
policies, err := getPoliciesFromPaths(fs, values.Policies, isGit)
|
policies, err := common.GetPoliciesFromPaths(fs, values.Policies, isGit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
mutatedPolicies, err := mutatePolices(policies)
|
mutatedPolicies, err := common.MutatePolices(policies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !sanitizederror.IsErrorSanitized(err) {
|
if !sanitizederror.IsErrorSanitized(err) {
|
||||||
return sanitizederror.NewWithError("failed to mutate policy", err)
|
return sanitizederror.NewWithError("failed to mutate policy", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resources, err := getResourceAccordingToResourcePath(fs,values.Resources, mutatedPolicies, isGit)
|
resources, err := common.GetResourceAccordingToResourcePath(fs, values.Resources, false, mutatedPolicies, dClient, "", false, isGit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
|
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -447,7 +267,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matches := common.PolicyHasVariables(*policy)
|
matches := common.PolicyHasVariables(*policy)
|
||||||
variable := removeDuplicatevariables(matches)
|
variable := common.RemoveDuplicateVariables(matches)
|
||||||
if len(matches) > 0 && valuesFile == "" {
|
if len(matches) > 0 && valuesFile == "" {
|
||||||
skipPolicy := SkippedPolicy{
|
skipPolicy := SkippedPolicy{
|
||||||
Name: policy.GetName(),
|
Name: policy.GetName(),
|
||||||
|
@ -468,7 +288,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
||||||
return sanitizederror.NewWithError(fmt.Sprintf("policy %s have variables. pass the values for the variables using set/values_file flag", policy.Name), err)
|
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 := applyPolicyOnResource(policy, resource)
|
ers, validateErs, _, _, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
|
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
|
||||||
}
|
}
|
||||||
|
@ -477,7 +297,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultsMap := buildPolicyResults(validateEngineResponses)
|
resultsMap := buildPolicyResults(validateEngineResponses)
|
||||||
resuleErr := printTestResult(resultsMap, values.TResults)
|
resuleErr := printTestResult(resultsMap, values.Results)
|
||||||
if resuleErr != nil {
|
if resuleErr != nil {
|
||||||
return sanitizederror.NewWithError("Unable to genrate result. error:", resuleErr)
|
return sanitizederror.NewWithError("Unable to genrate result. error:", resuleErr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -485,6 +305,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func printTestResult(resps map[string][]interface {}, testResults []TestResults) (error){
|
func printTestResult(resps map[string][]interface {}, testResults []TestResults) (error){
|
||||||
printer := tableprinter.New(os.Stdout)
|
printer := tableprinter.New(os.Stdout)
|
||||||
table := []*Table{}
|
table := []*Table{}
|
||||||
|
@ -502,6 +323,7 @@ func printTestResult(resps map[string][]interface {}, testResults []TestResults)
|
||||||
}
|
}
|
||||||
var c []ReportResult
|
var c []ReportResult
|
||||||
json.Unmarshal(valuesBytes, &c)
|
json.Unmarshal(valuesBytes, &c)
|
||||||
|
res.Result = boldRed.Sprintf("Fail")
|
||||||
if len(c) != 0 {
|
if len(c) != 0 {
|
||||||
var resource1 TestResults
|
var resource1 TestResults
|
||||||
for _, c1 := range c {
|
for _, c1 := range c {
|
||||||
|
@ -510,11 +332,9 @@ func printTestResult(resps map[string][]interface {}, testResults []TestResults)
|
||||||
resource1.Rule = c1.Rule
|
resource1.Rule = c1.Rule
|
||||||
resource1.Status = c1.Status
|
resource1.Status = c1.Status
|
||||||
resource1.Resource = c1.Resources[0].Name
|
resource1.Resource = c1.Resources[0].Name
|
||||||
if v != resource1 {
|
|
||||||
res.Result = boldRed.Sprintf("Fail")
|
if v == resource1 {
|
||||||
} else {
|
|
||||||
res.Result = "Pass"
|
res.Result = "Pass"
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue