From 44f0de5c5307e1e7ff127ce0f5b84ea2296039e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 12 Sep 2023 17:20:21 +0200 Subject: [PATCH] fix: verifyImages w/ multiple entries is not consistent (#8357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: verifyImages w/ multiple entries is not consistent Signed-off-by: Charles-Edouard Brétéché * clean Signed-off-by: Charles-Edouard Brétéché * unit tests Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Charles-Edouard Brétéché --- .../kubectl-kyverno/commands/apply/command.go | 39 +- .../commands/apply/command_test.go | 462 +++++++++--------- .../processor/policy_processor.go | 36 +- 3 files changed, 276 insertions(+), 261 deletions(-) diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command.go b/cmd/cli/kubectl-kyverno/commands/apply/command.go index 3e664a69ff..b6b72f6b31 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command.go @@ -67,10 +67,9 @@ type ApplyCommandConfig struct { } func Command() *cobra.Command { - var cmd *cobra.Command var removeColor, detailedResults, table bool applyCommandConfig := &ApplyCommandConfig{} - cmd = &cobra.Command{ + cmd := &cobra.Command{ Use: "apply", Short: command.FormatDescription(true, websiteUrl, false, description...), Long: command.FormatDescription(false, websiteUrl, false, description...), @@ -298,7 +297,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource( if err != nil { return &rc, resources, responses, sanitizederror.NewWithError(fmt.Errorf("failed to apply policies on resource %v", resource.GetName()).Error(), err) } - responses = append(responses, processSkipEngineResponses(ers)...) + responses = append(responses, ers...) } return &rc, resources, responses, nil } @@ -475,37 +474,3 @@ func exit(rc *processor.ResultCounts, warnExitCode int, warnNoPassed bool) error } return nil } - -func processSkipEngineResponses(responses []engineapi.EngineResponse) []engineapi.EngineResponse { - var processedEngineResponses []engineapi.EngineResponse - for _, response := range responses { - if !response.IsEmpty() { - pol := response.Policy() - if polType := pol.GetType(); polType == engineapi.ValidatingAdmissionPolicyType { - return processedEngineResponses - } - - for _, rule := range autogen.ComputeRules(pol.GetPolicy().(kyvernov1.PolicyInterface)) { - if rule.HasValidate() || rule.HasVerifyImageChecks() || rule.HasVerifyImages() { - ruleFoundInEngineResponse := false - for _, valResponseRule := range response.PolicyResponse.Rules { - if rule.Name == valResponseRule.Name() { - ruleFoundInEngineResponse = true - } - } - if !ruleFoundInEngineResponse { - response.PolicyResponse.Rules = append(response.PolicyResponse.Rules, - *engineapi.RuleSkip( - rule.Name, - engineapi.Validation, - rule.Validation.Message, - ), - ) - } - } - } - } - processedEngineResponses = append(processedEngineResponses, response) - } - return processedEngineResponses -} diff --git a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go index f14cab8b78..3b1f163ef6 100644 --- a/cmd/cli/kubectl-kyverno/commands/apply/command_test.go +++ b/cmd/cli/kubectl-kyverno/commands/apply/command_test.go @@ -24,118 +24,126 @@ func Test_Apply(t *testing.T) { assert.NilError(t, err) defer func() { _ = os.Remove(localFileName) }() - testcases := []*TestCase{{ - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, - PolicyReport: true, - }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 2, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 0, + testcases := []*TestCase{ + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{localFileName}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 2, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 2, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{localFileName}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 2, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 1, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/apply/policies"}, - ResourcePaths: []string{"../../../../../test/cli/apply/resource"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 1, - Skip: 8, - Error: 0, - Warn: 2, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/apply/policies"}, + ResourcePaths: []string{"../../../../../test/cli/apply/resource"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, - PolicyReport: true, - AuditWarn: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 2, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 1, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, + PolicyReport: true, + AuditWarn: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"-"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, - PolicyReport: true, - AuditWarn: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 1, + }, + }}, }, - stdinFile: "../../../../../test/best_practices/disallow_latest_tag.yaml", - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 1, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"-"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_latest_tag.yaml"}, + PolicyReport: true, + AuditWarn: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, - ResourcePaths: []string{"-"}, - PolicyReport: true, - AuditWarn: true, + stdinFile: "../../../../../test/best_practices/disallow_latest_tag.yaml", + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 1, + }, + }}, }, - stdinFile: "../../../../../test/resources/pod_with_latest_tag.yaml", - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 1, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml"}, + ResourcePaths: []string{"-"}, + PolicyReport: true, + AuditWarn: true, }, - }}, - }, { - // TODO + stdinFile: "../../../../../test/resources/pod_with_latest_tag.yaml", + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 1, + }, + }}, + }, + // { + // // TODO // config: ApplyCommandConfig{ // PolicyPaths: []string{"https://github.com/kyverno/policies/openshift/team-validate-ns-name/"}, // ResourcePaths: []string{"../../../../../test/openshift/team-validate-ns-name.yaml"}, @@ -151,146 +159,156 @@ func Test_Apply(t *testing.T) { // Warn: 0, // }, // }}, - // }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/apply/policies-set"}, - ResourcePaths: []string{"../../../../../test/cli/apply/resources-set"}, - Variables: []string{"request.operation=UPDATE"}, - PolicyReport: true, - }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 2, - Fail: 0, - Skip: 4, - Error: 0, - Warn: 0, + // }, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/apply/policies-set"}, + ResourcePaths: []string{"../../../../../test/cli/apply/resources-set"}, + Variables: []string{"request.operation=UPDATE"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/deployment1.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 2, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/deployment1.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/deployment2.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 0, - Fail: 1, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployments-replica/deployment2.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/pod1.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 0, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/pod1.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/pod2.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 0, - Fail: 1, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/disallow-host-path/pod2.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/deployment1.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 0, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 1, - Fail: 0, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/deployment1.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/policy.yaml"}, - ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/deployment2.yaml"}, - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 1, + Fail: 0, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 0, - Fail: 1, - Skip: 0, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/policy.yaml"}, + ResourcePaths: []string{"../../../../../test/cli/test-validating-admission-policy/check-deployment-labels/deployment2.yaml"}, + PolicyReport: true, }, - }}, - }, { - config: ApplyCommandConfig{ - PolicyPaths: []string{"https://github.com/kyverno/policies/best-practices/require-labels/", "../../../../../test/best_practices/disallow_latest_tag.yaml"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, - GitBranch: "main", - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 0, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 2, - Fail: 1, - Skip: 2, - Error: 0, - Warn: 0, + { + config: ApplyCommandConfig{ + PolicyPaths: []string{"https://github.com/kyverno/policies/best-practices/require-labels/", "../../../../../test/best_practices/disallow_latest_tag.yaml"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, + GitBranch: "main", + PolicyReport: true, }, - }}, - }, { - // Same as the above test case but the policy paths are reordered - config: ApplyCommandConfig{ - PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml", "https://github.com/kyverno/policies/best-practices/require-labels/"}, - ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, - GitBranch: "main", - PolicyReport: true, + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 2, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, }, - expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ - Summary: policyreportv1alpha2.PolicyReportSummary{ - Pass: 2, - Fail: 1, - Skip: 2, - Error: 0, - Warn: 0, + { + // Same as the above test case but the policy paths are reordered + config: ApplyCommandConfig{ + PolicyPaths: []string{"../../../../../test/best_practices/disallow_latest_tag.yaml", "https://github.com/kyverno/policies/best-practices/require-labels/"}, + ResourcePaths: []string{"../../../../../test/resources/pod_with_version_tag.yaml"}, + GitBranch: "main", + PolicyReport: true, }, - }}, - }} + expectedPolicyReports: []policyreportv1alpha2.PolicyReport{{ + Summary: policyreportv1alpha2.PolicyReportSummary{ + Pass: 2, + Fail: 1, + Skip: 0, + Error: 0, + Warn: 0, + }, + }}, + }, + } compareSummary := func(expected policyreportv1alpha2.PolicyReportSummary, actual policyreportv1alpha2.PolicyReportSummary, desc string) { assert.Equal(t, actual.Pass, expected.Pass, desc) diff --git a/cmd/cli/kubectl-kyverno/processor/policy_processor.go b/cmd/cli/kubectl-kyverno/processor/policy_processor.go index 0c33f1b5c6..9d73adf94a 100644 --- a/cmd/cli/kubectl-kyverno/processor/policy_processor.go +++ b/cmd/cli/kubectl-kyverno/processor/policy_processor.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + json_patch "github.com/evanphx/json-patch/v5" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values" @@ -22,9 +23,12 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/engine/mutate/patch" "github.com/kyverno/kyverno/pkg/engine/policycontext" "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" + jsonutils "github.com/kyverno/kyverno/pkg/utils/json" + "gomodules.xyz/jsonpatch/v2" yamlv2 "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -113,8 +117,36 @@ func (p *PolicyProcessor) ApplyPoliciesOnResource() ([]engineapi.EngineResponse, if err != nil { return responses, err } - // TODO annotation - verifyImageResponse, _ := eng.VerifyAndPatchImages(context.TODO(), policyContext) + verifyImageResponse, verifiedImageData := eng.VerifyAndPatchImages(context.TODO(), policyContext) + // update annotation to reflect verified images + var patches []jsonpatch.JsonPatchOperation + if !verifiedImageData.IsEmpty() { + annotationPatches, err := verifiedImageData.Patches(len(verifyImageResponse.PatchedResource.GetAnnotations()) != 0, log.Log) + if err != nil { + return responses, err + } + // add annotation patches first + patches = append(annotationPatches, patches...) + } + if len(patches) != 0 { + patch := jsonutils.JoinPatches(patch.ConvertPatches(patches...)...) + decoded, err := json_patch.DecodePatch(patch) + if err != nil { + return responses, err + } + options := &json_patch.ApplyOptions{SupportNegativeIndices: true, AllowMissingPathOnRemove: true, EnsurePathExistsOnAdd: true} + resourceBytes, err := verifyImageResponse.PatchedResource.MarshalJSON() + if err != nil { + return responses, err + } + patchedResourceBytes, err := decoded.ApplyWithOptions(resourceBytes, options) + if err != nil { + return responses, err + } + if err := verifyImageResponse.PatchedResource.UnmarshalJSON(patchedResourceBytes); err != nil { + return responses, err + } + } responses = append(responses, verifyImageResponse) resource = verifyImageResponse.PatchedResource }