mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-15 00:36:28 +00:00
Mutate existing CLI support (#11453)
* feat: Add flags for target resources and add fake client initialization Signed-off-by: aerosouund <aerosound161@gmail.com> * feat: Add fake discovery client and cluster bool in the policy processor Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Use the full mutation engine policy response in the engine response Signed-off-by: aerosouund <aerosound161@gmail.com> * feat: Extract mutated targets from the policy responses and print them out Signed-off-by: aerosouund <aerosound161@gmail.com> * feat: Add TargetResources field in the cli test schema Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Generate CLI crds Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: modify checkResult to take an arbitrary actual resource and resource name Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: change getAndCompareResource to take a resource name and cascade it to GetResourceFromPath Signed-off-by: aerosouund <aerosound161@gmail.com> * test: Create a simple test to test mutate existing in the CLI Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Allow GetResourceFromPath to select a resource with a name from a multi resource yaml Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Modify the runTest command to return the TestResponse type - Create a fake client, load the target resources in it and use it in the PolicyProcessor. - Create the TestResponse which contains Trigger and Target fields, each is a map of gvk/name to the responses corresponding to that resource. Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Rewrite output.go to use the TestResponse type - Check for both target and trigger - Create logic for appending the resource array in case no resources are passed - Move row creation logic into a separate method to avoid code duplication - Extract the proper target resource based on rule type - Create a function to extract mutated target from the engine response Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Move tests to the correct folder Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Use apiVersion/Kind/Name as the key in the test responses Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Use the apiVersion/Kind/name key schema in checking results and fix invalid resource name checking for generate policies Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Use better variable names for rows Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Account for Generate resources being an array Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Use generated resource name in checking the results and printing output Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Uncomment checks printing Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Remove bug in engine response creation Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Move the generate logic into an else block Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Fix namespace fetching bug in cel validator Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Use pointer to int in the test counter Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Remove redundant method Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Skip resources not being found in the manifests Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Create another field in the engine to denote if this is a cluster engine or an offline engine Simply checking for the client being nil is no longer enough because for cli operations the client will be a fake client A pointer to bool is chosen because callers who don't necessarily know what to pass should be able to pass nil Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: Add extra argument in fake client initiation Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: add extra argument in fuzz test Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: Add extra arg Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: Handle resources specified as ns/name as this schema will be deprecated in favor of apiVersion/Kind/Name Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: Fix linter complaints Signed-off-by: ammar <ammar.yasser@vodafone.com> * fix: Use comma separation as array separators as kubernetes names don't support commas To avoid undefined array length on splitting on / using commas will result in a fixed length since all resources will have an apiVersion, kind, namespace and name Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Change resource array type to an array of any instead of array of string To support the use of a string or a TestResourceSpec Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Expect the resource array to be an array of string or array of TestResourceSpec Assert that an array element is either of these types and match the resources in both cases according to the element type Expect that the key in responses is now separated by commas instead of slashes Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Expect that the resource array is now of type array of any and modify tests that use it Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Skip response check if the policy name isnt whats in the result Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Match the name if its specified as ns/name Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Fix linter complaint Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Run codegen Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Create CLI CRDs Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Run codegen Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Fix linter complaints Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Cleanup invalid code used in FixTest to adapt it to the schema changes Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Check if resource is nil before extracting Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: use the loadResources method to open targets in a directory Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Account for target resources with the same name but different namespaces Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Add CLI test for mutate existing with the same name Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Infer resource name and namespace from the actual resource and account for resources with the same name and namespace but different kinds Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: remove extra line Signed-off-by: aerosouund <aerosound161@gmail.com> * feat: Add printing mutate existing resources to the output or to a file Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Minor fixes Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: fix linter complaint Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: codegen Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Revert result back to error Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Use io discard to not print resources in the test command Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Update vague comments and remove outdated ones Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Integrate mutate existing changes with diff generation Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Move resource key generation into a function Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Add a mutate existing test that fails Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: fix linter complaint Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Remove redundant comment Signed-off-by: aerosouund <aerosound161@gmail.com> * refactor: Fix array of any assignment in cli test Signed-off-by: aerosouund <aerosound161@gmail.com> * fix: Dont check duplicate strings for field that is an array of any Signed-off-by: aerosouund <aerosound161@gmail.com> * bug: Fix appending to the wrong array Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: run fix tests Signed-off-by: aerosouund <aerosound161@gmail.com> * chore: Run fix tests Signed-off-by: aerosouund <aerosound161@gmail.com> --------- Signed-off-by: aerosouund <aerosound161@gmail.com> Signed-off-by: ammar <ammar.yasser@vodafone.com> Signed-off-by: Ammar Yasser <aerosound161@gmail.com> Co-authored-by: ammar <ammar.yasser@vodafone.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
e9704e7d8f
commit
739e6a21c4
53 changed files with 812 additions and 214 deletions
|
@ -24,6 +24,9 @@ type Test struct {
|
|||
// Resources are the resource to be used in the test
|
||||
Resources []string `json:"resources,omitempty"`
|
||||
|
||||
// Target Resources are for policies that have mutate existing
|
||||
TargetResources []string `json:"targetResources,omitempty"`
|
||||
|
||||
// Variables is the values to be used in the test
|
||||
Variables string `json:"variables,omitempty"`
|
||||
|
||||
|
@ -54,6 +57,15 @@ type CheckResult struct {
|
|||
Error v1alpha1.Any `json:"error"`
|
||||
}
|
||||
|
||||
type TestResourceSpec struct {
|
||||
Group string `json:"group,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Subresource string `json:"subresource,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type CheckMatch struct {
|
||||
// Resource filters engine responses
|
||||
Resource *v1alpha1.Any `json:"resource,omitempty"`
|
||||
|
|
|
@ -67,5 +67,5 @@ type TestResult struct {
|
|||
TestResultDeprecated `json:",inline,omitempty"`
|
||||
|
||||
// Resources gives us the list of resources on which the policy is going to be applied.
|
||||
Resources []string `json:"resources"`
|
||||
Resources []any `json:"resources"`
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
@ -60,6 +62,7 @@ type ApplyCommandConfig struct {
|
|||
AuditWarn bool
|
||||
ResourcePaths []string
|
||||
PolicyPaths []string
|
||||
TargetResourcePaths []string
|
||||
GitBranch string
|
||||
warnExitCode int
|
||||
warnNoPassed bool
|
||||
|
@ -134,6 +137,8 @@ func Command() *cobra.Command {
|
|||
}
|
||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resource", "r", []string{}, "Path to resource files")
|
||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resources", "", []string{}, "Path to resource files")
|
||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resource", "", []string{}, "Path to individual files containing target resources files for policies that have mutate existing")
|
||||
cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resources", "", []string{}, "Path to a directory containing target resources files for policies that have mutate existing")
|
||||
cmd.Flags().BoolVarP(&applyCommandConfig.Cluster, "cluster", "c", false, "Checks if policies should be applied to cluster in the current context")
|
||||
cmd.Flags().StringVarP(&applyCommandConfig.MutateLogPath, "output", "o", "", "Prints the mutated/generated resources in provided file/directory")
|
||||
// currently `set` flag supports variable for single policy applied on single resource
|
||||
|
@ -189,18 +194,28 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
|
|||
return nil, nil, skipInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err)
|
||||
}
|
||||
var store store.Store
|
||||
rc, resources1, skipInvalidPolicies, responses1, dClient, err := c.initStoreAndClusterClient(&store, skipInvalidPolicies)
|
||||
if err != nil {
|
||||
return rc, resources1, skipInvalidPolicies, responses1, err
|
||||
}
|
||||
rc, resources1, skipInvalidPolicies, responses1, policies, vaps, vapBindings, err := c.loadPolicies(skipInvalidPolicies)
|
||||
if err != nil {
|
||||
return rc, resources1, skipInvalidPolicies, responses1, err
|
||||
}
|
||||
resources, err := c.loadResources(out, policies, vaps, dClient)
|
||||
var targetResources []*unstructured.Unstructured
|
||||
if len(c.TargetResourcePaths) > 0 {
|
||||
targetResources, err = c.loadResources(out, c.TargetResourcePaths, policies, vaps, nil)
|
||||
if err != nil {
|
||||
return rc, resources1, skipInvalidPolicies, responses1, err
|
||||
}
|
||||
}
|
||||
|
||||
rc, resources1, skipInvalidPolicies, responses1, dClient, err := c.initStoreAndClusterClient(&store, skipInvalidPolicies, targetResources...)
|
||||
if err != nil {
|
||||
return rc, resources1, skipInvalidPolicies, responses1, err
|
||||
}
|
||||
|
||||
resources, err := c.loadResources(out, c.ResourcePaths, policies, vaps, dClient)
|
||||
if err != nil {
|
||||
return rc, resources1, skipInvalidPolicies, responses1, err
|
||||
}
|
||||
|
||||
var exceptions []*kyvernov2.PolicyException
|
||||
if c.inlineExceptions {
|
||||
exceptions = exception.SelectFrom(resources)
|
||||
|
@ -339,6 +354,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
|
|||
Stdin: c.Stdin,
|
||||
Rc: &rc,
|
||||
PrintPatchResource: true,
|
||||
Cluster: c.Cluster,
|
||||
Client: dClient,
|
||||
AuditWarn: c.AuditWarn,
|
||||
Subresources: vars.Subresources(),
|
||||
|
@ -362,8 +378,8 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
|
|||
return &rc, resources, responses, nil
|
||||
}
|
||||
|
||||
func (c *ApplyCommandConfig) loadResources(out io.Writer, policies []kyvernov1.PolicyInterface, vap []admissionregistrationv1beta1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) {
|
||||
resources, err := common.GetResourceAccordingToResourcePath(out, nil, c.ResourcePaths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "")
|
||||
func (c *ApplyCommandConfig) loadResources(out io.Writer, paths []string, policies []kyvernov1.PolicyInterface, vap []admissionregistrationv1beta1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) {
|
||||
resources, err := common.GetResourceAccordingToResourcePath(out, nil, paths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "")
|
||||
if err != nil {
|
||||
return resources, fmt.Errorf("failed to load resources (%w)", err)
|
||||
}
|
||||
|
@ -429,7 +445,7 @@ func (c *ApplyCommandConfig) loadPolicies(skipInvalidPolicies SkippedInvalidPoli
|
|||
return nil, nil, skipInvalidPolicies, nil, policies, vaps, vapBindings, nil
|
||||
}
|
||||
|
||||
func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, skipInvalidPolicies SkippedInvalidPolicies) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, dclient.Interface, error) {
|
||||
func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, skipInvalidPolicies SkippedInvalidPolicies, targetResources ...*unstructured.Unstructured) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, dclient.Interface, error) {
|
||||
store.SetLocal(true)
|
||||
store.SetRegistryAccess(c.RegistryAccess)
|
||||
if c.Cluster {
|
||||
|
@ -455,6 +471,18 @@ func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, skipI
|
|||
return nil, nil, skipInvalidPolicies, nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(targetResources) > 0 && !c.Cluster {
|
||||
var targets []runtime.Object
|
||||
for _, t := range targetResources {
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
dClient, err = dclient.NewFakeClient(runtime.NewScheme(), map[schema.GroupVersionResource]string{}, targets...)
|
||||
dClient.SetDiscovery(dclient.NewFakeDiscoveryClient(nil))
|
||||
if err != nil {
|
||||
return nil, nil, skipInvalidPolicies, nil, nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil, skipInvalidPolicies, nil, dClient, err
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func Command() *cobra.Command {
|
||||
|
@ -127,7 +126,7 @@ func testCommandExecute(
|
|||
if err := printTestResult(filteredResults, responses, rc, &resultsTable, test.Fs, resourcePath); err != nil {
|
||||
return fmt.Errorf("failed to print test result (%w)", err)
|
||||
}
|
||||
if err := printCheckResult(test.Test.Checks, responses, rc, &resultsTable); err != nil {
|
||||
if err := printCheckResult(test.Test.Checks, *responses, rc, &resultsTable); err != nil {
|
||||
return fmt.Errorf("failed to print test result (%w)", err)
|
||||
}
|
||||
fullTable.AddFailed(resultsTable.RawRows...)
|
||||
|
@ -152,19 +151,15 @@ func testCommandExecute(
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkResult(test v1alpha1.TestResult, fs billy.Filesystem, resoucePath string, response engineapi.EngineResponse, rule engineapi.RuleResponse) (bool, string, string) {
|
||||
func checkResult(test v1alpha1.TestResult, fs billy.Filesystem, resoucePath string, response engineapi.EngineResponse, rule engineapi.RuleResponse, actualResource unstructured.Unstructured) (bool, string, string) {
|
||||
expected := test.Result
|
||||
// fallback to the deprecated field
|
||||
if expected == "" {
|
||||
expected = test.Status
|
||||
}
|
||||
// fallback on deprecated field
|
||||
patchedResource := test.PatchedResource
|
||||
if test.PatchedResources != "" {
|
||||
patchedResource = test.PatchedResources
|
||||
}
|
||||
if patchedResource != "" {
|
||||
equals, diff, err := getAndCompareResource([]*unstructured.Unstructured{&response.PatchedResource}, fs, filepath.Join(resoucePath, patchedResource))
|
||||
if test.PatchedResource != "" {
|
||||
equals, diff, err := getAndCompareResource(actualResource, fs, filepath.Join(resoucePath, test.PatchedResource))
|
||||
if err != nil {
|
||||
return false, err.Error(), "Resource error"
|
||||
}
|
||||
|
@ -175,14 +170,14 @@ func checkResult(test v1alpha1.TestResult, fs billy.Filesystem, resoucePath stri
|
|||
}
|
||||
}
|
||||
if test.GeneratedResource != "" {
|
||||
equals, diff, err := getAndCompareResource(rule.GeneratedResources(), fs, filepath.Join(resoucePath, test.GeneratedResource))
|
||||
equals, diff, err := getAndCompareResource(actualResource, fs, filepath.Join(resoucePath, test.GeneratedResource))
|
||||
if err != nil {
|
||||
return false, err.Error(), "Resource error"
|
||||
}
|
||||
if !equals {
|
||||
dmp := diffmatchpatch.New()
|
||||
legend := dmp.DiffPrettyText(dmp.DiffMain("only in expected", "only in actual", false))
|
||||
return false, fmt.Sprintf("Generated resource didn't match the generated resource in the test result\n(%s)\n\n%s", legend, diff), "Resource diff"
|
||||
return false, fmt.Sprintf("Patched resource didn't match the generated resource in the test result\n(%s)\n\n%s", legend, diff), "Resource diff"
|
||||
}
|
||||
}
|
||||
result := report.ComputePolicyReportResult(false, response, rule)
|
||||
|
@ -192,27 +187,6 @@ func checkResult(test v1alpha1.TestResult, fs billy.Filesystem, resoucePath stri
|
|||
return true, result.Message, "Ok"
|
||||
}
|
||||
|
||||
func lookupEngineResponses(test v1alpha1.TestResult, resourceName string, responses ...engineapi.EngineResponse) []engineapi.EngineResponse {
|
||||
matches := make([]engineapi.EngineResponse, 0, len(responses))
|
||||
for _, response := range responses {
|
||||
policy := response.Policy()
|
||||
resource := response.Resource
|
||||
pName := cache.MetaObjectToName(policy.MetaObject()).String()
|
||||
rName := cache.MetaObjectToName(&resource).String()
|
||||
if test.Kind != resource.GetKind() {
|
||||
continue
|
||||
}
|
||||
if pName != test.Policy {
|
||||
continue
|
||||
}
|
||||
if resourceName != "" && rName != resourceName && resource.GetName() != resourceName {
|
||||
continue
|
||||
}
|
||||
matches = append(matches, response)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func lookupRuleResponses(test v1alpha1.TestResult, responses ...engineapi.RuleResponse) []engineapi.RuleResponse {
|
||||
var matches []engineapi.RuleResponse
|
||||
// Since there are no rules in case of validating admission policies, responses are returned without checking rule names.
|
||||
|
|
|
@ -11,41 +11,26 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func getAndCompareResource(actualResources []*unstructured.Unstructured, fs billy.Filesystem, path string) (bool, string, error) {
|
||||
expectedResources, err := resource.GetResourceFromPath(fs, path)
|
||||
func getAndCompareResource(actualResource unstructured.Unstructured, fs billy.Filesystem, path string) (bool, string, error) {
|
||||
expectedResource, err := resource.GetResourceFromPath(fs, path, actualResource.GetAPIVersion(), actualResource.GetKind(), actualResource.GetNamespace(), actualResource.GetName())
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("error: failed to load resource (%s)", err)
|
||||
}
|
||||
resource.FixupGenerateLabels(actualResource)
|
||||
resource.FixupGenerateLabels(*expectedResource)
|
||||
|
||||
expectedResourcesMap := map[string]unstructured.Unstructured{}
|
||||
for _, expectedResource := range expectedResources {
|
||||
if expectedResource == nil {
|
||||
continue
|
||||
}
|
||||
r := *expectedResource
|
||||
resource.FixupGenerateLabels(r)
|
||||
expectedResourcesMap[expectedResource.GetNamespace()+"/"+expectedResource.GetName()] = r
|
||||
equals, err := resource.Compare(actualResource, *expectedResource, true)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("error: failed to compare resources (%s)", err)
|
||||
}
|
||||
|
||||
for _, actualResource := range actualResources {
|
||||
if actualResource == nil {
|
||||
continue
|
||||
}
|
||||
r := *actualResource
|
||||
resource.FixupGenerateLabels(r)
|
||||
equals, err := resource.Compare(r, expectedResourcesMap[r.GetNamespace()+"/"+r.GetName()], true)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("error: failed to compare resources (%s)", err)
|
||||
}
|
||||
if !equals {
|
||||
log.Log.V(4).Info("Resource diff", "expected", expectedResourcesMap[r.GetNamespace()+"/"+r.GetName()], "actual", r)
|
||||
es, _ := yaml.Marshal(expectedResourcesMap[r.GetNamespace()+"/"+r.GetName()])
|
||||
as, _ := yaml.Marshal(r)
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(es), string(as), false)
|
||||
log.Log.V(4).Info("\n" + dmp.DiffPrettyText(diffs) + "\n")
|
||||
return false, dmp.DiffPrettyText(diffs), nil
|
||||
}
|
||||
if !equals {
|
||||
log.Log.V(4).Info("Resource diff", "expected", expectedResource, "actual", actualResource)
|
||||
es, _ := yaml.Marshal(expectedResource)
|
||||
as, _ := yaml.Marshal(actualResource)
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(es), string(as), false)
|
||||
log.Log.V(4).Info("\n" + dmp.DiffPrettyText(diffs) + "\n")
|
||||
return false, dmp.DiffPrettyText(diffs), nil
|
||||
}
|
||||
return true, "", nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/kyverno/kyverno-json/pkg/engine/assert"
|
||||
|
@ -12,12 +13,13 @@ import (
|
|||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
|
||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/table"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func printCheckResult(
|
||||
checks []v1alpha1.CheckResult,
|
||||
responses []engineapi.EngineResponse,
|
||||
responses TestResponse,
|
||||
rc *resultCounts,
|
||||
resultsTable *table.Table,
|
||||
) error {
|
||||
|
@ -25,7 +27,10 @@ func printCheckResult(
|
|||
testCount := 1
|
||||
for _, check := range checks {
|
||||
// filter engine responses
|
||||
matchingEngineResponses := responses
|
||||
var matchingEngineResponses []engineapi.EngineResponse
|
||||
for _, engineresponses := range responses.Trigger {
|
||||
matchingEngineResponses = append(matchingEngineResponses, engineresponses...)
|
||||
}
|
||||
// 1. by resource
|
||||
if check.Match.Resource != nil {
|
||||
var filtered []engineapi.EngineResponse
|
||||
|
@ -160,9 +165,10 @@ func printCheckResult(
|
|||
return nil
|
||||
}
|
||||
|
||||
// a test that contains a policy that may contain several rules
|
||||
func printTestResult(
|
||||
tests []v1alpha1.TestResult,
|
||||
responses []engineapi.EngineResponse,
|
||||
responses *TestResponse,
|
||||
rc *resultCounts,
|
||||
resultsTable *table.Table,
|
||||
fs billy.Filesystem,
|
||||
|
@ -170,80 +176,144 @@ func printTestResult(
|
|||
) error {
|
||||
testCount := 1
|
||||
for _, test := range tests {
|
||||
// lookup matching engine responses (without the resource name)
|
||||
// to reduce the search scope
|
||||
responses := lookupEngineResponses(test, "", responses...)
|
||||
// TODO fix deprecated fields
|
||||
// identify the resources to be looked up
|
||||
var resources []string
|
||||
if test.Resources != nil {
|
||||
resources = append(resources, test.Resources...)
|
||||
} else if test.Resource != "" {
|
||||
resources = append(resources, test.Resource)
|
||||
// The test specifies certain resources to check, results will be checked for those resources only
|
||||
if test.Resource != "" {
|
||||
test.Resources = append(test.Resources, test.Resource)
|
||||
}
|
||||
for _, resource := range resources {
|
||||
var rows []table.Row
|
||||
// lookup matching engine responses (with the resource name this time)
|
||||
for _, response := range lookupEngineResponses(test, resource, responses...) {
|
||||
// lookup matching rule responses
|
||||
for _, rule := range lookupRuleResponses(test, response.PolicyResponse.Rules...) {
|
||||
// perform test checks
|
||||
ok, message, reason := checkResult(test, fs, resoucePath, response, rule)
|
||||
// if checks failed but we were expecting a fail it's considered a success
|
||||
success := ok || (!ok && test.Result == policyreportv1alpha2.StatusFail)
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: testCount,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(test.Kind, test.Namespace, resource),
|
||||
Reason: reason,
|
||||
IsFailure: !success,
|
||||
},
|
||||
Message: message,
|
||||
}
|
||||
if success {
|
||||
row.Result = color.ResultPass()
|
||||
if test.Result == policyreportv1alpha2.StatusSkip {
|
||||
rc.Skip++
|
||||
} else {
|
||||
rc.Pass++
|
||||
if test.Resources != nil {
|
||||
for _, r := range test.Resources {
|
||||
for _, m := range []map[string][]engineapi.EngineResponse{responses.Target, responses.Trigger} {
|
||||
for resourceGVKAndName := range m {
|
||||
nameParts := strings.Split(resourceGVKAndName, ",")
|
||||
if resourceString, ok := r.(string); ok {
|
||||
nsAndName := strings.Split(resourceString, "/")
|
||||
if len(nsAndName) == 1 {
|
||||
if resourceString == nameParts[len(nameParts)-1] {
|
||||
resources = append(resources, resourceGVKAndName)
|
||||
}
|
||||
}
|
||||
if len(nsAndName) == 2 {
|
||||
if nsAndName[0] == nameParts[len(nameParts)-2] && nsAndName[1] == nameParts[len(nameParts)-1] {
|
||||
resources = append(resources, resourceGVKAndName)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
row.Result = color.ResultFail()
|
||||
rc.Fail++
|
||||
}
|
||||
testCount++
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
// if there are no RuleResponse, the resource has been excluded. This is a pass.
|
||||
if len(rows) == 0 {
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: testCount,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(test.Kind, test.Namespace, resource),
|
||||
Result: color.ResultPass(),
|
||||
Reason: color.Excluded(),
|
||||
IsFailure: false,
|
||||
},
|
||||
Message: color.Excluded(),
|
||||
if resourceSpec, ok := r.(v1alpha1.TestResourceSpec); ok {
|
||||
if resourceSpec.Group == "" {
|
||||
if resourceSpec.Version != nameParts[0] {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if resourceSpec.Group+"/"+resourceSpec.Version != nameParts[0] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if resourceSpec.Namespace != nameParts[len(nameParts)-2] {
|
||||
continue
|
||||
}
|
||||
if resourceSpec.Name == nameParts[len(nameParts)-1] {
|
||||
resources = append(resources, resourceGVKAndName)
|
||||
}
|
||||
}
|
||||
}
|
||||
rc.Skip++
|
||||
testCount++
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
// if not found
|
||||
if len(rows) == 0 {
|
||||
}
|
||||
|
||||
// The test specifies no resources, check all results
|
||||
if len(resources) == 0 {
|
||||
for r := range responses.Target {
|
||||
resources = append(resources, r)
|
||||
}
|
||||
for r := range responses.Trigger {
|
||||
resources = append(resources, r)
|
||||
}
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
var rows []table.Row
|
||||
var resourceSkipped bool
|
||||
if _, ok := responses.Trigger[resource]; ok {
|
||||
for _, response := range responses.Trigger[resource] {
|
||||
polNameNs := strings.Split(test.Policy, "/")
|
||||
if response.Policy().GetName() != polNameNs[len(polNameNs)-1] {
|
||||
continue
|
||||
}
|
||||
for _, rule := range lookupRuleResponses(test, response.PolicyResponse.Rules...) {
|
||||
r := response.Resource
|
||||
|
||||
if rule.RuleType() != "Generation" {
|
||||
if rule.RuleType() == "Mutation" {
|
||||
r = response.PatchedResource
|
||||
}
|
||||
|
||||
ok, message, reason := checkResult(test, fs, resoucePath, response, rule, r)
|
||||
if strings.Contains(message, "not found in manifest") {
|
||||
resourceSkipped = true
|
||||
continue
|
||||
}
|
||||
|
||||
success := ok || (!ok && test.Result == policyreportv1alpha2.StatusFail)
|
||||
resourceRows := createRowsAccordingToResults(test, rc, &testCount, success, message, reason, strings.Replace(resource, ",", "/", -1))
|
||||
rows = append(rows, resourceRows...)
|
||||
} else {
|
||||
generatedResources := rule.GeneratedResources()
|
||||
for _, r := range generatedResources {
|
||||
ok, message, reason := checkResult(test, fs, resoucePath, response, rule, *r)
|
||||
|
||||
success := ok || (!ok && test.Result == policyreportv1alpha2.StatusFail)
|
||||
resourceRows := createRowsAccordingToResults(test, rc, &testCount, success, message, reason, r.GetName())
|
||||
rows = append(rows, resourceRows...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no RuleResponse, the resource has been excluded. This is a pass.
|
||||
if len(rows) == 0 && !resourceSkipped {
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: testCount,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(test.Kind, test.Namespace, strings.Replace(resource, ",", "/", -1)),
|
||||
Result: color.ResultPass(),
|
||||
Reason: color.Excluded(),
|
||||
IsFailure: false,
|
||||
},
|
||||
Message: color.Excluded(),
|
||||
}
|
||||
rc.Skip++
|
||||
testCount++
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the resource specified exists in the targets
|
||||
if _, ok := responses.Target[resource]; ok {
|
||||
for _, response := range responses.Target[resource] {
|
||||
// we are doing this twice which is kinda not nice
|
||||
nameParts := strings.Split(resource, ",")
|
||||
name, ns, kind, apiVersion := nameParts[len(nameParts)-1], nameParts[len(nameParts)-2], nameParts[len(nameParts)-3], nameParts[len(nameParts)-4]
|
||||
|
||||
r, rule := extractPatchedTargetFromEngineResponse(apiVersion, kind, name, ns, response)
|
||||
ok, message, reason := checkResult(test, fs, resoucePath, response, *rule, *r)
|
||||
|
||||
success := ok || (!ok && test.Result == policyreportv1alpha2.StatusFail)
|
||||
resourceRows := createRowsAccordingToResults(test, rc, &testCount, success, message, reason, strings.Replace(resource, ",", "/", -1))
|
||||
rows = append(rows, resourceRows...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) == 0 && !resourceSkipped {
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: testCount,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(test.Kind, test.Namespace, resource),
|
||||
Resource: color.Resource(test.Kind, test.Namespace, strings.Replace(resource, ",", "/", -1)),
|
||||
IsFailure: true,
|
||||
Result: color.ResultFail(),
|
||||
Reason: color.NotFound(),
|
||||
|
@ -261,6 +331,70 @@ func printTestResult(
|
|||
return nil
|
||||
}
|
||||
|
||||
func createRowsAccordingToResults(test v1alpha1.TestResult, rc *resultCounts, globalTestCounter *int, success bool, message string, reason string, resourceGVKAndName string) []table.Row {
|
||||
resourceParts := strings.Split(resourceGVKAndName, "/")
|
||||
rows := []table.Row{}
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: *globalTestCounter,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(strings.Join(resourceParts[:len(resourceParts)-1], "/"), test.Namespace, resourceParts[len(resourceParts)-1]),
|
||||
Reason: reason,
|
||||
IsFailure: !success,
|
||||
},
|
||||
Message: message,
|
||||
}
|
||||
if success {
|
||||
row.Result = color.ResultPass()
|
||||
if test.Result == policyreportv1alpha2.StatusSkip {
|
||||
rc.Skip++
|
||||
} else {
|
||||
rc.Pass++
|
||||
}
|
||||
} else {
|
||||
row.Result = color.ResultFail()
|
||||
rc.Fail++
|
||||
}
|
||||
*globalTestCounter++
|
||||
rows = append(rows, row)
|
||||
|
||||
// if there are no RuleResponse, the resource has been excluded. This is a pass.
|
||||
if len(rows) == 0 {
|
||||
row := table.Row{
|
||||
RowCompact: table.RowCompact{
|
||||
ID: *globalTestCounter,
|
||||
Policy: color.Policy("", test.Policy),
|
||||
Rule: color.Rule(test.Rule),
|
||||
Resource: color.Resource(strings.Join(resourceParts[:len(resourceParts)-1], "/"), test.Namespace, resourceParts[len(resourceParts)-1]), // todo: handle namespace
|
||||
Result: color.ResultPass(),
|
||||
Reason: color.Excluded(),
|
||||
IsFailure: false,
|
||||
},
|
||||
Message: color.Excluded(),
|
||||
}
|
||||
rc.Skip++
|
||||
*globalTestCounter++
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func extractPatchedTargetFromEngineResponse(apiVersion, kind, resourceName, resourceNamespace string, response engineapi.EngineResponse) (*unstructured.Unstructured, *engineapi.RuleResponse) {
|
||||
for _, rule := range response.PolicyResponse.Rules {
|
||||
r, _, _ := rule.PatchedTarget()
|
||||
if r != nil {
|
||||
if resourceNamespace == "" {
|
||||
resourceNamespace = r.GetNamespace()
|
||||
}
|
||||
if r.GetAPIVersion() == apiVersion && r.GetKind() == kind && r.GetName() == resourceName && r.GetNamespace() == resourceNamespace {
|
||||
return r, &rule
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func printFailedTestResult(out io.Writer, resultsTable table.Table, detailedResults bool) {
|
||||
printer := table.NewTablePrinter(out)
|
||||
for i := range resultsTable.RawRows {
|
||||
|
|
|
@ -26,9 +26,16 @@ import (
|
|||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) ([]engineapi.EngineResponse, error) {
|
||||
type TestResponse struct {
|
||||
Trigger map[string][]engineapi.EngineResponse
|
||||
Target map[string][]engineapi.EngineResponse
|
||||
}
|
||||
|
||||
func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) (*TestResponse, error) {
|
||||
// don't process test case with errors
|
||||
if testCase.Err != nil {
|
||||
return nil, testCase.Err
|
||||
|
@ -75,6 +82,25 @@ func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) ([]engi
|
|||
fmt.Fprintln(out, " warning: found duplicated resource", dup.Kind, dup.Name, dup.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
targetResourcesPath := path.GetFullPaths(testCase.Test.TargetResources, testDir, isGit)
|
||||
targetResources, err := common.GetResourceAccordingToResourcePath(out, testCase.Fs, targetResourcesPath, false, results.Policies, results.VAPs, dClient, "", false, testDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error: failed to load target resources (%s)", err)
|
||||
}
|
||||
|
||||
targets := []runtime.Object{}
|
||||
for _, t := range targetResources {
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
// this will be a dclient containing all target resources. a policy may not do anything with any targets in these
|
||||
dClient, err = dclient.NewFakeClient(runtime.NewScheme(), map[schema.GroupVersionResource]string{}, targets...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dClient.SetDiscovery(dclient.NewFakeDiscoveryClient(nil))
|
||||
|
||||
// exceptions
|
||||
fmt.Fprintln(out, " Loading exceptions", "...")
|
||||
exceptionFullPath := path.GetFullPaths(testCase.Test.PolicyExceptions, testDir, isGit)
|
||||
|
@ -164,7 +190,12 @@ func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) ([]engi
|
|||
// execute engine
|
||||
var engineResponses []engineapi.EngineResponse
|
||||
var resultCounts processor.ResultCounts
|
||||
testResponse := TestResponse{
|
||||
Trigger: map[string][]engineapi.EngineResponse{},
|
||||
Target: map[string][]engineapi.EngineResponse{},
|
||||
}
|
||||
for _, resource := range uniques {
|
||||
// the policy processor is for multiple policies at once
|
||||
processor := processor.PolicyProcessor{
|
||||
Store: &store,
|
||||
Policies: validPolicies,
|
||||
|
@ -177,16 +208,28 @@ func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) ([]engi
|
|||
NamespaceSelectorMap: vars.NamespaceSelectors(),
|
||||
Rc: &resultCounts,
|
||||
RuleToCloneSourceResource: ruleToCloneSourceResource,
|
||||
Cluster: false,
|
||||
Client: dClient,
|
||||
Subresources: vars.Subresources(),
|
||||
Out: out,
|
||||
Out: io.Discard,
|
||||
}
|
||||
ers, err := processor.ApplyPoliciesOnResource()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to apply policies on resource %v (%w)", resource.GetName(), err)
|
||||
}
|
||||
resourceKey := generateResourceKey(resource)
|
||||
engineResponses = append(engineResponses, ers...)
|
||||
testResponse.Trigger[resourceKey] = ers
|
||||
}
|
||||
for _, targetResource := range targetResources {
|
||||
for _, engineResponse := range engineResponses {
|
||||
if r, _ := extractPatchedTargetFromEngineResponse(targetResource.GetAPIVersion(), targetResource.GetKind(), targetResource.GetName(), targetResource.GetNamespace(), engineResponse); r != nil {
|
||||
resourceKey := generateResourceKey(targetResource)
|
||||
testResponse.Target[resourceKey] = append(testResponse.Target[resourceKey], engineResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, resource := range uniques {
|
||||
processor := processor.ValidatingAdmissionPolicyProcessor{
|
||||
Policies: results.VAPs,
|
||||
|
@ -200,7 +243,13 @@ func runTest(out io.Writer, testCase test.TestCase, registryAccess bool) ([]engi
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to apply policies on resource %s (%w)", resource.GetName(), err)
|
||||
}
|
||||
engineResponses = append(engineResponses, ers...)
|
||||
resourceKey := generateResourceKey(resource)
|
||||
testResponse.Trigger[resourceKey] = append(testResponse.Trigger[resourceKey], ers...)
|
||||
}
|
||||
return engineResponses, nil
|
||||
// this is an array of responses of all policies, generated by all of their rules
|
||||
return &testResponse, nil
|
||||
}
|
||||
|
||||
func generateResourceKey(resource *unstructured.Unstructured) string {
|
||||
return resource.GetAPIVersion() + "," + resource.GetKind() + "," + resource.GetNamespace() + "," + resource.GetName()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: tests.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
@ -182,6 +182,11 @@ spec:
|
|||
- result
|
||||
type: object
|
||||
type: array
|
||||
targetResources:
|
||||
description: Target Resources are for policies that have mutate existing
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
userinfo:
|
||||
description: UserInfo is the user info to be used in the test
|
||||
type: string
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: userinfoes.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: values.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: tests.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
@ -182,6 +182,11 @@ spec:
|
|||
- result
|
||||
type: object
|
||||
type: array
|
||||
targetResources:
|
||||
description: Target Resources are for policies that have mutate existing
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
userinfo:
|
||||
description: UserInfo is the user info to be used in the test
|
||||
type: string
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: userinfoes.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
|
|
@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
controller-gen.kubebuilder.io/version: (devel)
|
||||
name: values.cli.kyverno.io
|
||||
spec:
|
||||
group: cli.kyverno.io
|
||||
|
|
|
@ -37,7 +37,7 @@ func FixTest(test v1alpha1.Test, compress bool) (v1alpha1.Test, []string, error)
|
|||
messages = append(messages, "test result should not use both `resource` and `resources` fields")
|
||||
}
|
||||
if result.Resource != "" {
|
||||
var resources []string
|
||||
var resources []any
|
||||
messages = append(messages, "test result uses deprecated `resource` field, moving it into the `resources` field")
|
||||
resources = append(resources, result.Resources...)
|
||||
resources = append(resources, result.Resource)
|
||||
|
@ -67,7 +67,13 @@ func FixTest(test v1alpha1.Test, compress bool) (v1alpha1.Test, []string, error)
|
|||
if compress {
|
||||
compressed := map[v1alpha1.TestResultBase][]string{}
|
||||
for _, result := range results {
|
||||
compressed[result.TestResultBase] = append(compressed[result.TestResultBase], result.Resources...)
|
||||
resourcesAsStringArray := []string{}
|
||||
for _, resource := range result.Resources {
|
||||
if r, ok := resource.(string); ok {
|
||||
resourcesAsStringArray = append(resourcesAsStringArray, r)
|
||||
}
|
||||
}
|
||||
compressed[result.TestResultBase] = append(compressed[result.TestResultBase], resourcesAsStringArray...)
|
||||
}
|
||||
results = nil
|
||||
for k, v := range compressed {
|
||||
|
@ -76,9 +82,13 @@ func FixTest(test v1alpha1.Test, compress bool) (v1alpha1.Test, []string, error)
|
|||
messages = append(messages, "test results contains duplicate resources")
|
||||
v = unique.UnsortedList()
|
||||
}
|
||||
anyArray := make([]interface{}, len(v))
|
||||
for i, r := range v {
|
||||
anyArray[i] = r
|
||||
}
|
||||
results = append(results, v1alpha1.TestResult{
|
||||
TestResultBase: k,
|
||||
Resources: v,
|
||||
Resources: anyArray,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -104,14 +114,25 @@ func FixTest(test v1alpha1.Test, compress bool) (v1alpha1.Test, []string, error)
|
|||
if x := cmp.Compare(a.CloneSourceResource, b.CloneSourceResource); x != 0 {
|
||||
return x
|
||||
}
|
||||
slices.Sort(a.Resources)
|
||||
slices.Sort(b.Resources)
|
||||
asArray1 := []string{}
|
||||
for _, r := range a.Resources {
|
||||
resourceString, _ := r.(string)
|
||||
asArray1 = append(asArray1, resourceString)
|
||||
}
|
||||
asArray2 := []string{}
|
||||
for _, r := range b.Resources {
|
||||
resourceString, _ := r.(string)
|
||||
asArray2 = append(asArray1, resourceString)
|
||||
}
|
||||
slices.Sort(asArray1)
|
||||
slices.Sort(asArray2)
|
||||
|
||||
if x := cmp.Compare(len(a.Resources), len(b.Resources)); x != 0 {
|
||||
return x
|
||||
}
|
||||
if len(a.Resources) == len(b.Resources) {
|
||||
for i := range a.Resources {
|
||||
if x := cmp.Compare(a.Resources[i], b.Resources[i]); x != 0 {
|
||||
if x := cmp.Compare(asArray1[i], asArray2[i]); x != 0 {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ func initializeMockController(out io.Writer, s *store.Store, gvrToListKind map[s
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
store.ContextLoaderFactory(s, nil),
|
||||
nil,
|
||||
nil,
|
||||
))
|
||||
return c, nil
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ type PolicyProcessor struct {
|
|||
MutateLogPath string
|
||||
MutateLogPathIsDir bool
|
||||
Variables *variables.Variables
|
||||
Cluster bool
|
||||
UserInfo *kyvernov2.RequestInfo
|
||||
PolicyReport bool
|
||||
NamespaceSelectorMap map[string]map[string]string
|
||||
|
@ -72,6 +73,7 @@ func (p *PolicyProcessor) ApplyPoliciesOnResource() ([]engineapi.EngineResponse,
|
|||
if rclient == nil {
|
||||
rclient = registryclient.NewOrDie()
|
||||
}
|
||||
isCluster := false
|
||||
eng := engine.NewEngine(
|
||||
cfg,
|
||||
config.NewDefaultMetricsConfiguration(),
|
||||
|
@ -81,13 +83,14 @@ func (p *PolicyProcessor) ApplyPoliciesOnResource() ([]engineapi.EngineResponse,
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
store.ContextLoaderFactory(p.Store, nil),
|
||||
exceptions.New(policyExceptionLister),
|
||||
&isCluster,
|
||||
)
|
||||
gvk, subresource := resource.GroupVersionKind(), ""
|
||||
resourceKind := resource.GetKind()
|
||||
resourceName := resource.GetName()
|
||||
resourceNamespace := resource.GetNamespace()
|
||||
// If --cluster flag is not set, then we need to find the top level resource GVK and subresource
|
||||
if p.Client == nil {
|
||||
if !p.Cluster {
|
||||
for _, s := range p.Subresources {
|
||||
subgvk := schema.GroupVersionKind{
|
||||
Group: s.Subresource.Group,
|
||||
|
@ -377,6 +380,21 @@ func (p *PolicyProcessor) printOutput(resource interface{}, response engineapi.E
|
|||
return fmt.Errorf("failed to marshal (%w)", err)
|
||||
}
|
||||
|
||||
var yamlEncodedTargetResources [][]byte
|
||||
for _, ruleResponese := range response.PolicyResponse.Rules {
|
||||
patchedTarget, _, _ := ruleResponese.PatchedTarget()
|
||||
|
||||
if patchedTarget != nil {
|
||||
yamlEncodedResource, err := yamlv2.Marshal(patchedTarget.Object)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal (%w)", err)
|
||||
}
|
||||
|
||||
yamlEncodedResource = append(yamlEncodedResource, []byte("\n---\n")...)
|
||||
yamlEncodedTargetResources = append(yamlEncodedTargetResources, yamlEncodedResource)
|
||||
}
|
||||
}
|
||||
|
||||
if p.MutateLogPath == "" {
|
||||
resource := string(yamlEncodedResource) + string("\n---")
|
||||
if len(strings.TrimSpace(resource)) > 0 {
|
||||
|
@ -384,6 +402,12 @@ func (p *PolicyProcessor) printOutput(resource interface{}, response engineapi.E
|
|||
fmt.Fprintf(p.Out, "\npolicy %s applied to %s:", response.Policy().GetName(), resourcePath)
|
||||
}
|
||||
fmt.Fprintf(p.Out, "\n"+resource+"\n") //nolint:govet
|
||||
if len(yamlEncodedTargetResources) > 0 {
|
||||
fmt.Fprintf(p.Out, "patched targets: \n")
|
||||
for _, patchedTarget := range yamlEncodedTargetResources {
|
||||
fmt.Fprintf(p.Out, "\n"+string(patchedTarget)+"\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -409,11 +433,14 @@ func (p *PolicyProcessor) printOutput(resource interface{}, response engineapi.E
|
|||
file = f
|
||||
}
|
||||
if _, err := file.Write([]byte(string(yamlEncodedResource) + "\n---\n\n")); err != nil {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Log.Error(err, "failed to close file")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, patchedTarget := range yamlEncodedTargetResources {
|
||||
if _, err := file.Write(patchedTarget); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ func YamlToUnstructured(resourceYaml []byte) (*unstructured.Unstructured, error)
|
|||
return resource, nil
|
||||
}
|
||||
|
||||
func GetResourceFromPath(fs billy.Filesystem, path string) ([]*unstructured.Unstructured, error) {
|
||||
// should be able to specify a single resource in a multi yaml file, dont error out when there are multiple resource
|
||||
func GetResourceFromPath(fs billy.Filesystem, path string, apiVersion, kind, resourceNamespace, resourceName string) (*unstructured.Unstructured, error) {
|
||||
var resourceBytes []byte
|
||||
if fs == nil {
|
||||
data, err := GetFileBytes(path)
|
||||
|
@ -83,10 +84,18 @@ func GetResourceFromPath(fs billy.Filesystem, path string) ([]*unstructured.Unst
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
return nil, fmt.Errorf("no resources found")
|
||||
|
||||
for _, r := range resources {
|
||||
name := r.GetName()
|
||||
ns := r.GetNamespace()
|
||||
apiv := r.GetAPIVersion()
|
||||
k := r.GetKind()
|
||||
|
||||
if apiv == apiVersion && k == kind && name == resourceName && ns == resourceNamespace {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return resources, nil
|
||||
return nil, fmt.Errorf("resource with name %s not found in manifest", resourceName)
|
||||
}
|
||||
|
||||
func GetFileBytes(path string) ([]byte, error) {
|
||||
|
|
|
@ -84,9 +84,6 @@ func LoadTest(fs billy.Filesystem, path string) TestCase {
|
|||
func cleanTest(test *v1alpha1.Test) {
|
||||
test.Policies = removeDuplicateStrings(test.Policies)
|
||||
test.Resources = removeDuplicateStrings(test.Resources)
|
||||
for index, result := range test.Results {
|
||||
test.Results[index].Resources = removeDuplicateStrings(result.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDuplicateStrings(strings []string) []string {
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Result: policyreportv1alpha2.StatusPass,
|
||||
Rule: "only-allow-trusted-images",
|
||||
},
|
||||
Resources: []string{
|
||||
Resources: []any{
|
||||
"test-pod-with-non-root-user-image",
|
||||
"test-pod-with-trusted-registry",
|
||||
},
|
||||
|
@ -97,7 +97,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Rule: "generate-limitrange",
|
||||
GeneratedResource: "generatedLimitRange.yaml",
|
||||
},
|
||||
Resources: []string{"hello-world-namespace"},
|
||||
Resources: []any{"hello-world-namespace"},
|
||||
}, {
|
||||
TestResultBase: v1alpha1.TestResultBase{
|
||||
Kind: "Namespace",
|
||||
|
@ -106,7 +106,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Rule: "generate-resourcequota",
|
||||
GeneratedResource: "generatedResourceQuota.yaml",
|
||||
},
|
||||
Resources: []string{"hello-world-namespace"},
|
||||
Resources: []any{"hello-world-namespace"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
|
@ -134,7 +134,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Result: policyreportv1alpha2.StatusPass,
|
||||
Rule: "only-allow-trusted-images",
|
||||
},
|
||||
Resources: []string{
|
||||
Resources: []any{
|
||||
"test-pod-with-non-root-user-image",
|
||||
"test-pod-with-trusted-registry",
|
||||
},
|
||||
|
@ -160,7 +160,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Rule: "generate-limitrange",
|
||||
GeneratedResource: "generatedLimitRange.yaml",
|
||||
},
|
||||
Resources: []string{"hello-world-namespace"},
|
||||
Resources: []any{"hello-world-namespace"},
|
||||
}, {
|
||||
TestResultBase: v1alpha1.TestResultBase{
|
||||
Kind: "Namespace",
|
||||
|
@ -169,7 +169,7 @@ func TestLoadTests(t *testing.T) {
|
|||
Rule: "generate-resourcequota",
|
||||
GeneratedResource: "generatedResourceQuota.yaml",
|
||||
},
|
||||
Resources: []string{"hello-world-namespace"},
|
||||
Resources: []any{"hello-world-namespace"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
|
@ -234,7 +234,7 @@ func TestLoadTest(t *testing.T) {
|
|||
Result: policyreportv1alpha2.StatusPass,
|
||||
Rule: "only-allow-trusted-images",
|
||||
},
|
||||
Resources: []string{
|
||||
Resources: []any{
|
||||
"test-pod-with-non-root-user-image",
|
||||
"test-pod-with-trusted-registry",
|
||||
},
|
||||
|
@ -263,7 +263,7 @@ func TestLoadTest(t *testing.T) {
|
|||
Result: policyreportv1alpha2.StatusPass,
|
||||
Rule: "only-allow-trusted-images",
|
||||
},
|
||||
Resources: []string{
|
||||
Resources: []any{
|
||||
"test-pod-with-non-root-user-image",
|
||||
"test-pod-with-trusted-registry",
|
||||
},
|
||||
|
|
|
@ -53,6 +53,7 @@ func NewEngine(
|
|||
ivCache,
|
||||
factories.DefaultContextLoaderFactory(configMapResolver, factories.WithAPICallConfig(apiCallConfig), factories.WithGlobalContextStore(gctxStore)),
|
||||
exceptionsSelector,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ kyverno apply [flags]
|
|||
-s, --set strings Variables that are required
|
||||
-i, --stdin Optional mutate policy parameter to pipe directly through to kubectl
|
||||
-t, --table Show results in table format
|
||||
--target-resource strings Path to individual files containing target resources files for policies that have mutate existing
|
||||
--target-resources strings Path to a directory containing target resources files for policies that have mutate existing
|
||||
-u, --userinfo string Admission Info including Roles, Cluster Roles and Subjects
|
||||
-f, --values-file string File containing values for policy variables
|
||||
--warn-exit-code int Set the exit code for warnings; if failures or errors are found, will exit 1
|
||||
|
|
|
@ -112,6 +112,17 @@ This field is deprecated, use <code>metadata.name</code> instead</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>targetResources</code><br/>
|
||||
<em>
|
||||
[]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Target Resources are for policies that have mutate existing</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>variables</code><br/>
|
||||
<em>
|
||||
string
|
||||
|
@ -655,6 +666,81 @@ Kubernetes meta/v1.APIResource
|
|||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3 id="cli.kyverno.io/v1alpha1.TestResourceSpec">TestResourceSpec
|
||||
</h3>
|
||||
<p>
|
||||
</p>
|
||||
<table class="table table-striped">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>group</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>version</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>namespace</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>subresource</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br/>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3 id="cli.kyverno.io/v1alpha1.TestResult">TestResult
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -706,7 +792,7 @@ TestResultDeprecated
|
|||
<td>
|
||||
<code>resources</code><br/>
|
||||
<em>
|
||||
[]string
|
||||
[]any
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -193,6 +193,35 @@ This field is deprecated, use <code>metadata.name</code> instead</p>
|
|||
|
||||
|
||||
|
||||
<tr>
|
||||
<td><code>targetResources</code>
|
||||
|
||||
<span style="color:blue;"> *</span>
|
||||
|
||||
</br>
|
||||
|
||||
|
||||
|
||||
|
||||
<span style="font-family: monospace">[]string</span>
|
||||
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
|
||||
<p>Target Resources are for policies that have mutate existing</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td><code>variables</code>
|
||||
|
||||
|
@ -1475,7 +1504,7 @@ This field is deprecated, use <code>metadata.name</code> instead</p>
|
|||
|
||||
|
||||
|
||||
<span style="font-family: monospace">[]string</span>
|
||||
<span style="font-family: monospace">[]any</span>
|
||||
|
||||
|
||||
</td>
|
||||
|
|
|
@ -31,6 +31,7 @@ type engine struct {
|
|||
metricsConfiguration config.MetricsConfiguration
|
||||
jp jmespath.Interface
|
||||
client engineapi.Client
|
||||
isCluster bool
|
||||
rclientFactory engineapi.RegistryClientFactory
|
||||
ivCache imageverifycache.Client
|
||||
contextLoader engineapi.ContextLoaderFactory
|
||||
|
@ -51,7 +52,12 @@ func NewEngine(
|
|||
ivCache imageverifycache.Client,
|
||||
contextLoader engineapi.ContextLoaderFactory,
|
||||
exceptionSelector engineapi.PolicyExceptionSelector,
|
||||
isCluster *bool,
|
||||
) engineapi.Engine {
|
||||
if isCluster == nil {
|
||||
defaultCluster := true
|
||||
isCluster = &defaultCluster
|
||||
}
|
||||
meter := otel.GetMeterProvider().Meter(metrics.MeterName)
|
||||
resultCounter, err := meter.Int64Counter(
|
||||
"kyverno_policy_results",
|
||||
|
@ -74,6 +80,7 @@ func NewEngine(
|
|||
client: client,
|
||||
rclientFactory: rclientFactory,
|
||||
ivCache: ivCache,
|
||||
isCluster: *isCluster,
|
||||
contextLoader: contextLoader,
|
||||
exceptionSelector: exceptionSelector,
|
||||
resultCounter: resultCounter,
|
||||
|
@ -107,8 +114,8 @@ func (e *engine) Mutate(
|
|||
if internal.MatchPolicyContext(logger, e.client, policyContext, e.configuration) {
|
||||
policyResponse, patchedResource := e.mutate(ctx, logger, policyContext)
|
||||
response = response.
|
||||
WithPolicyResponse(policyResponse).
|
||||
WithPatchedResource(patchedResource)
|
||||
WithPatchedResource(patchedResource).
|
||||
WithPolicyResponse(policyResponse)
|
||||
}
|
||||
response = response.WithStats(engineapi.NewExecutionStats(startTime, time.Now()))
|
||||
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
|
||||
|
|
|
@ -43,6 +43,7 @@ var (
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(nil),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
initter sync.Once
|
||||
)
|
||||
|
@ -126,6 +127,7 @@ func FuzzVerifyImageAndPatchTest(f *testing.F) {
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(nil),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
_, _ = verifyImageAndPatchEngine.VerifyAndPatchImages(
|
||||
|
@ -271,6 +273,7 @@ func FuzzMutateTest(f *testing.F) {
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(nil),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
e.Mutate(
|
||||
context.Background(),
|
||||
|
|
|
@ -30,12 +30,14 @@ import (
|
|||
)
|
||||
|
||||
type validateCELHandler struct {
|
||||
client engineapi.Client
|
||||
client engineapi.Client
|
||||
isCluster bool
|
||||
}
|
||||
|
||||
func NewValidateCELHandler(client engineapi.Client) (handlers.Handler, error) {
|
||||
func NewValidateCELHandler(client engineapi.Client, isCluster bool) (handlers.Handler, error) {
|
||||
return validateCELHandler{
|
||||
client: client,
|
||||
client: client,
|
||||
isCluster: isCluster,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -140,7 +142,7 @@ func (h validateCELHandler) Process(
|
|||
ns = ""
|
||||
}
|
||||
if ns != "" {
|
||||
if h.client != nil {
|
||||
if h.client != nil && h.isCluster {
|
||||
namespace, err = h.client.GetNamespace(ctx, ns, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return resource, handlers.WithResponses(
|
||||
|
|
|
@ -324,6 +324,7 @@ func testVerifyAndPatchImages(
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(cmResolver),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return e.VerifyAndPatchImages(
|
||||
ctx,
|
||||
|
@ -1059,6 +1060,7 @@ func testImageVerifyCache(
|
|||
ivCache,
|
||||
factories.DefaultContextLoaderFactory(cmResolver),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return e.VerifyAndPatchImages(
|
||||
ctx,
|
||||
|
|
|
@ -42,6 +42,7 @@ func testMutate(
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
contextLoader,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return e.Mutate(
|
||||
ctx,
|
||||
|
|
|
@ -51,7 +51,7 @@ func (e *engine) validate(
|
|||
} else if hasValidatePss {
|
||||
return validation.NewValidatePssHandler()
|
||||
} else if hasValidateCEL {
|
||||
return validation.NewValidateCELHandler(e.client)
|
||||
return validation.NewValidateCELHandler(e.client, e.isCluster)
|
||||
} else {
|
||||
return validation.NewValidateResourceHandler()
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ func testValidate(
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
contextLoader,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return e.Validate(
|
||||
ctx,
|
||||
|
|
|
@ -66,6 +66,7 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) *resour
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(configMapResolver),
|
||||
exceptions.New(peLister),
|
||||
nil,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2086,6 +2086,7 @@ func TestValidate_failure_action_overrides(t *testing.T) {
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(nil),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
for i, tc := range testcases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
|
@ -2188,6 +2189,7 @@ func Test_RuleSelector(t *testing.T) {
|
|||
imageverifycache.DisabledImageVerifyCache(),
|
||||
factories.DefaultContextLoaderFactory(nil),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
resp := eng.Validate(
|
||||
context.TODO(),
|
||||
|
|
|
@ -7,13 +7,6 @@ policies:
|
|||
resources:
|
||||
- resource.yaml
|
||||
results:
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource1.yaml
|
||||
policy: add-default-resources
|
||||
resources:
|
||||
- nginx-demo1
|
||||
result: pass
|
||||
rule: add-default-requests
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource3.yaml
|
||||
policy: add-default-resources
|
||||
|
@ -21,6 +14,13 @@ results:
|
|||
- nginx-demo3
|
||||
result: pass
|
||||
rule: add-default-requests
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource1.yaml
|
||||
policy: add-default-resources
|
||||
resources:
|
||||
- nginx-demo1
|
||||
result: pass
|
||||
rule: add-default-requests
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource2.yaml
|
||||
policy: add-default-resources
|
||||
|
|
|
@ -7,13 +7,6 @@ policies:
|
|||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource.yaml
|
||||
policy: add-safe-to-evict
|
||||
resources:
|
||||
- pod-with-emptydir-hostpath
|
||||
result: pass
|
||||
rule: annotate-empty-dir
|
||||
- kind: Pod
|
||||
patchedResources: patchedResourceWithVolume.yaml
|
||||
policy: add-safe-to-evict
|
||||
|
@ -21,6 +14,13 @@ results:
|
|||
- pod-with-emptydir-hostpath-1
|
||||
result: pass
|
||||
rule: annotate-empty-dir
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource.yaml
|
||||
policy: add-safe-to-evict
|
||||
resources:
|
||||
- pod-with-emptydir-hostpath
|
||||
result: pass
|
||||
rule: annotate-empty-dir
|
||||
- kind: Pod
|
||||
policy: add-safe-to-evict
|
||||
resources:
|
||||
|
|
|
@ -7,6 +7,12 @@ policies:
|
|||
resources:
|
||||
- resource.yaml
|
||||
results:
|
||||
- kind: Pod
|
||||
policy: karpenter-annotations-to-nodeselector
|
||||
resources:
|
||||
- soft-pod-antiaffinity-1-copy
|
||||
result: pass
|
||||
rule: hard-nodeselector-lifecycle-on-demand
|
||||
- kind: Pod
|
||||
patchedResources: patched.yaml
|
||||
policy: karpenter-annotations-to-nodeselector
|
||||
|
@ -14,9 +20,3 @@ results:
|
|||
- soft-pod-antiaffinity-1
|
||||
result: pass
|
||||
rule: hard-nodeselector-lifecycle-on-demand
|
||||
- kind: Pod
|
||||
policy: karpenter-annotations-to-nodeselector
|
||||
resources:
|
||||
- soft-pod-antiaffinity-1-copy
|
||||
result: pass
|
||||
rule: hard-nodeselector-lifecycle-on-demand
|
||||
|
|
|
@ -14,13 +14,6 @@ results:
|
|||
- mydeploy
|
||||
result: pass
|
||||
rule: add-label
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource3.yaml
|
||||
policy: add-label
|
||||
resources:
|
||||
- production/same-name-but-diff-namespace
|
||||
result: pass
|
||||
rule: add-label
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource6.yaml
|
||||
policy: add-label
|
||||
|
@ -35,6 +28,13 @@ results:
|
|||
- testing/same-name-but-diff-namespace
|
||||
result: pass
|
||||
rule: add-label
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource3.yaml
|
||||
policy: add-label
|
||||
resources:
|
||||
- production/same-name-but-diff-namespace
|
||||
result: pass
|
||||
rule: add-label
|
||||
- kind: Pod
|
||||
patchedResources: patchedResource1.yaml
|
||||
policy: add-label
|
||||
|
|
18
test/cli/test-mutate/mutate-existing-fail/kyverno-test.yaml
Normal file
18
test/cli/test-mutate/mutate-existing-fail/kyverno-test.yaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
apiVersion: cli.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: kyverno-test.yaml
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- trigger-cm.yaml
|
||||
results:
|
||||
- kind: ""
|
||||
patchedResources: mutated-secret.yaml
|
||||
policy: mutate-existing-secret
|
||||
resources:
|
||||
- secret-1
|
||||
result: fail
|
||||
rule: mutate-secret-on-configmap-create
|
||||
targetResources:
|
||||
- raw-secret.yaml
|
7
test/cli/test-mutate/mutate-existing-fail/mutated-secret.yaml
Executable file
7
test/cli/test-mutate/mutate-existing-fail/mutated-secret.yaml
Executable file
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
otherlabel: bar
|
||||
name: secret-1
|
||||
namespace: staging
|
27
test/cli/test-mutate/mutate-existing-fail/policy.yaml
Executable file
27
test/cli/test-mutate/mutate-existing-fail/policy.yaml
Executable file
|
@ -0,0 +1,27 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: mutate-existing-secret
|
||||
spec:
|
||||
rules:
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
names:
|
||||
- dictionary-1
|
||||
namespaces:
|
||||
- staging
|
||||
mutate:
|
||||
mutateExistingOnPolicyUpdate: false
|
||||
patchStrategicMerge:
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
targets:
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
name: secret-1
|
||||
namespace: staging
|
||||
name: mutate-secret-on-configmap-create
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-1
|
||||
namespace: staging
|
7
test/cli/test-mutate/mutate-existing-fail/trigger-cm.yaml
Executable file
7
test/cli/test-mutate/mutate-existing-fail/trigger-cm.yaml
Executable file
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dictionary-1
|
||||
namespace: staging
|
17
test/cli/test-mutate/mutate-existing/kyverno-test.yaml
Normal file
17
test/cli/test-mutate/mutate-existing/kyverno-test.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
apiVersion: cli.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: kyverno-test.yaml
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- trigger-cm.yaml
|
||||
results:
|
||||
- kind: ""
|
||||
patchedResources: mutated-secret.yaml
|
||||
policy: mutate-existing-secret
|
||||
resources: []
|
||||
result: pass
|
||||
rule: mutate-secret-on-configmap-create
|
||||
targetResources:
|
||||
- raw-secret.yaml
|
15
test/cli/test-mutate/mutate-existing/mutated-secret.yaml
Executable file
15
test/cli/test-mutate/mutate-existing/mutated-secret.yaml
Executable file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dictionary-1
|
||||
namespace: staging
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
name: secret-1
|
||||
namespace: staging
|
27
test/cli/test-mutate/mutate-existing/policy.yaml
Executable file
27
test/cli/test-mutate/mutate-existing/policy.yaml
Executable file
|
@ -0,0 +1,27 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: mutate-existing-secret
|
||||
spec:
|
||||
rules:
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
names:
|
||||
- dictionary-1
|
||||
namespaces:
|
||||
- staging
|
||||
mutate:
|
||||
mutateExistingOnPolicyUpdate: false
|
||||
patchStrategicMerge:
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
targets:
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
name: secret-1
|
||||
namespace: staging
|
||||
name: mutate-secret-on-configmap-create
|
5
test/cli/test-mutate/mutate-existing/raw-secret.yaml
Normal file
5
test/cli/test-mutate/mutate-existing/raw-secret.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-1
|
||||
namespace: staging
|
7
test/cli/test-mutate/mutate-existing/trigger-cm.yaml
Executable file
7
test/cli/test-mutate/mutate-existing/trigger-cm.yaml
Executable file
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dictionary-1
|
||||
namespace: staging
|
7
test/cli/test-mutate/same-name-mutate-existing/cm.yaml
Executable file
7
test/cli/test-mutate/same-name-mutate-existing/cm.yaml
Executable file
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
foo: bar
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dictionary-1
|
||||
namespace: staging
|
|
@ -0,0 +1,20 @@
|
|||
apiVersion: cli.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: kyverno-test.yaml
|
||||
policies:
|
||||
- policy.yaml
|
||||
resources:
|
||||
- cm.yaml
|
||||
results:
|
||||
- kind: ""
|
||||
patchedResources: mutated-resources.yaml
|
||||
policy: mutate-existing-secret
|
||||
resources:
|
||||
- staging/secret-1
|
||||
- prod/secret-1
|
||||
result: pass
|
||||
rule: mutate-secret-on-configmap-create
|
||||
targetResources:
|
||||
- secret-1.yaml
|
||||
- secret-2.yaml
|
16
test/cli/test-mutate/same-name-mutate-existing/mutated-resources.yaml
Executable file
16
test/cli/test-mutate/same-name-mutate-existing/mutated-resources.yaml
Executable file
|
@ -0,0 +1,16 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
name: secret-1
|
||||
namespace: staging
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
name: secret-1
|
||||
namespace: prod
|
||||
|
26
test/cli/test-mutate/same-name-mutate-existing/policy.yaml
Executable file
26
test/cli/test-mutate/same-name-mutate-existing/policy.yaml
Executable file
|
@ -0,0 +1,26 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: mutate-existing-secret
|
||||
spec:
|
||||
rules:
|
||||
- match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- ConfigMap
|
||||
names:
|
||||
- dictionary-1
|
||||
namespaces:
|
||||
- staging
|
||||
mutate:
|
||||
mutateExistingOnPolicyUpdate: false
|
||||
patchStrategicMerge:
|
||||
metadata:
|
||||
labels:
|
||||
foo: bar
|
||||
targets:
|
||||
- apiVersion: v1
|
||||
kind: Secret
|
||||
name: secret-1
|
||||
name: mutate-secret-on-configmap-create
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-1
|
||||
namespace: staging
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-1
|
||||
namespace: prod
|
|
@ -7,13 +7,6 @@ policies:
|
|||
resources:
|
||||
- resources.yaml
|
||||
results:
|
||||
- kind: ScaledObject
|
||||
patchedResources: patchedResource1.yaml
|
||||
policy: keda-prometheus-serveraddress
|
||||
resources:
|
||||
- service-1
|
||||
result: pass
|
||||
rule: keda-prometheus-serveraddress
|
||||
- kind: ScaledObject
|
||||
patchedResources: patchedResource2.yaml
|
||||
policy: keda-prometheus-serveraddress
|
||||
|
@ -21,6 +14,13 @@ results:
|
|||
- service-2
|
||||
result: pass
|
||||
rule: keda-prometheus-serveraddress
|
||||
- kind: ScaledObject
|
||||
patchedResources: patchedResource1.yaml
|
||||
policy: keda-prometheus-serveraddress
|
||||
resources:
|
||||
- service-1
|
||||
result: pass
|
||||
rule: keda-prometheus-serveraddress
|
||||
- kind: ScaledObject
|
||||
policy: keda-prometheus-serveraddress
|
||||
resources:
|
||||
|
|
Loading…
Add table
Reference in a new issue