1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-09 17:37:12 +00:00
kyverno/cmd/cli/kubectl-kyverno/commands/test/test.go
Charles-Edouard Brétéché c0a74fe0d5
fix: bad test file causes all tests to pass with success (#8258)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2023-09-05 11:09:45 +00:00

291 lines
9.7 KiB
Go

package test
import (
"fmt"
"os"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/filter"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
pathutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/path"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/background/generate"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/log"
)
func applyPoliciesFromPath(
testCase test.TestCase,
policyResourcePath string,
rc *resultCounts,
openApiManager openapi.Manager,
filter filter.Filter,
auditWarn bool,
) ([]api.TestResults, []engineapi.EngineResponse, error) {
var engineResponses []engineapi.EngineResponse
test := testCase.Test
fs := testCase.Fs
isGit := fs != nil
var dClient dclient.Interface
var resultCounts common.ResultCounts
store.SetLocal(true)
var filteredResults []api.TestResults
for _, res := range test.Results {
if filter.Apply(res) {
filteredResults = append(filteredResults, res)
}
}
test.Results = filteredResults
if len(test.Results) == 0 {
return nil, nil, nil
}
fmt.Printf("\nExecuting %s...\n", test.Name)
valuesFile := test.Variables
userInfoFile := test.UserInfo
variables, globalValMap, valuesMap, namespaceSelectorMap, subresources, err := common.GetVariable(nil, test.Values, test.Variables, fs, isGit, policyResourcePath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return nil, nil, sanitizederror.NewWithError("failed to decode yaml", err)
}
return nil, nil, err
}
// get the user info as request info from a different file
var userInfo v1beta1.RequestInfo
if userInfoFile != "" {
userInfo, err = common.GetUserInfoFromPath(fs, userInfoFile, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load request info\nCause: %s\n", err)
os.Exit(1)
}
}
policyFullPath := pathutils.GetFullPaths(test.Policies, policyResourcePath, isGit)
resourceFullPath := pathutils.GetFullPaths(test.Resources, policyResourcePath, isGit)
policies, validatingAdmissionPolicies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1)
}
var filteredPolicies []kyvernov1.PolicyInterface
for _, p := range policies {
for _, res := range test.Results {
if p.GetName() == res.Policy {
filteredPolicies = append(filteredPolicies, p)
break
}
}
}
var filteredVAPs []v1alpha1.ValidatingAdmissionPolicy
for _, p := range validatingAdmissionPolicies {
for _, res := range test.Results {
if p.GetName() == res.Policy {
filteredVAPs = append(filteredVAPs, p)
break
}
}
}
validatingAdmissionPolicies = filteredVAPs
ruleToCloneSourceResource := map[string]string{}
for _, p := range filteredPolicies {
var filteredRules []kyvernov1.Rule
for _, rule := range autogen.ComputeRules(p) {
for _, res := range test.Results {
if res.IsValidatingAdmissionPolicy {
continue
}
if rule.Name == res.Rule {
filteredRules = append(filteredRules, rule)
if rule.HasGenerate() {
ruleUnstr, err := generate.GetUnstrRule(rule.Generation.DeepCopy())
if err != nil {
fmt.Printf("Error: failed to get unstructured rule\nCause: %s\n", err)
break
}
genClone, _, err := unstructured.NestedMap(ruleUnstr.Object, "clone")
if err != nil {
fmt.Printf("Error: failed to read data\nCause: %s\n", err)
break
}
if len(genClone) != 0 {
if isGit {
ruleToCloneSourceResource[rule.Name] = res.CloneSourceResource
} else {
ruleToCloneSourceResource[rule.Name] = pathutils.GetFullPath(res.CloneSourceResource, policyResourcePath)
}
}
}
break
}
}
}
p.GetSpec().SetRules(filteredRules)
}
policies = filteredPolicies
resources, err := common.GetResourceAccordingToResourcePath(fs, resourceFullPath, false, policies, validatingAdmissionPolicies, dClient, "", false, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1)
}
checkableResources := selectResourcesForCheck(resources, test)
msgPolicies := "1 policy"
if len(policies)+len(validatingAdmissionPolicies) > 1 {
msgPolicies = fmt.Sprintf("%d policies", len(policies)+len(validatingAdmissionPolicies))
}
msgResources := "1 resource"
if len(checkableResources) > 1 {
msgResources = fmt.Sprintf("%d resources", len(checkableResources))
}
if len(policies) > 0 && len(checkableResources) > 0 {
fmt.Printf("applying %s to %s... \n", msgPolicies, msgResources)
}
for _, policy := range policies {
_, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName()))
if err != nil {
log.Log.Error(err, "skipping invalid policy", "name", policy.GetName())
continue
}
matches := common.HasVariables(policy)
variable := common.RemoveDuplicateAndObjectVariables(matches)
if len(variable) > 0 {
if len(variables) == 0 {
// check policy in variable file
if valuesFile == "" || valuesMap[policy.GetName()] == nil {
fmt.Printf("test skipped for policy %v (as required variables are not provided by the users) \n \n", policy.GetName())
}
}
}
kindOnwhichPolicyIsApplied := common.GetKindsFromPolicy(policy, subresources, dClient)
for _, resource := range checkableResources {
thisPolicyResourceValues, err := common.CheckVariableForPolicy(valuesMap, globalValMap, policy.GetName(), resource.GetName(), resource.GetKind(), variables, kindOnwhichPolicyIsApplied, variable)
if err != nil {
return nil, nil, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.GetName(), resource.GetName()), err)
}
applyPolicyConfig := common.ApplyPolicyConfig{
Policy: policy,
Resource: resource,
MutateLogPath: "",
Variables: thisPolicyResourceValues,
UserInfo: userInfo,
PolicyReport: true,
NamespaceSelectorMap: namespaceSelectorMap,
Rc: &resultCounts,
RuleToCloneSourceResource: ruleToCloneSourceResource,
Client: dClient,
Subresources: subresources,
}
ers, err := common.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return nil, nil, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
engineResponses = append(engineResponses, ers...)
}
}
validatingAdmissionPolicy := common.ValidatingAdmissionPolicies{}
for _, policy := range validatingAdmissionPolicies {
for _, resource := range resources {
applyPolicyConfig := common.ApplyPolicyConfig{
ValidatingAdmissionPolicy: policy,
Resource: resource,
PolicyReport: true,
Rc: &resultCounts,
Client: dClient,
Subresources: subresources,
}
ers, err := validatingAdmissionPolicy.ApplyPolicyOnResource(applyPolicyConfig)
if err != nil {
return nil, nil, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.GetName(), resource.GetName()).Error(), err)
}
engineResponses = append(engineResponses, ers...)
}
}
return test.Results, engineResponses, nil
}
func selectResourcesForCheck(resources []*unstructured.Unstructured, values *api.Test) []*unstructured.Unstructured {
res, _, _ := selectResourcesForCheckInternal(resources, values)
return res
}
// selectResourcesForCheckInternal internal method to test duplicates and unused
func selectResourcesForCheckInternal(resources []*unstructured.Unstructured, values *api.Test) ([]*unstructured.Unstructured, int, int) {
var duplicates int
var unused int
uniqResources := make(map[string]*unstructured.Unstructured)
for i := range resources {
r := resources[i]
key := fmt.Sprintf("%s/%s/%s", r.GetKind(), r.GetName(), r.GetNamespace())
if _, ok := uniqResources[key]; ok {
fmt.Println("skipping duplicate resource, resource :", r)
duplicates++
} else {
uniqResources[key] = r
}
}
selectedResources := map[string]*unstructured.Unstructured{}
for key := range uniqResources {
r := uniqResources[key]
for _, res := range values.Results {
if res.Kind == r.GetKind() {
for _, testr := range res.Resources {
if r.GetName() == testr {
selectedResources[key] = r
}
}
if r.GetName() == res.Resource {
selectedResources[key] = r
}
}
}
}
var checkableResources []*unstructured.Unstructured
for key := range selectedResources {
checkableResources = append(checkableResources, selectedResources[key])
delete(uniqResources, key)
}
for _, r := range uniqResources {
fmt.Println("skipping unused resource, resource :", r)
unused++
}
return checkableResources, duplicates, unused
}