From d17375204111a88f6425ce09d98b9fb9d4be0af2 Mon Sep 17 00:00:00 2001
From: Khaled Emara
Date: Tue, 30 Jul 2024 13:52:41 +0300
Subject: [PATCH] feat(json): unmarshal once per policy (#10701)
Signed-off-by: Khaled Emara
Co-authored-by: Mariam Fahmy
Co-authored-by: shuting
---
api/kyverno/v1/common_types.go | 22 ++-
api/kyverno/v1/wrappers.go | 79 ++++++++++
api/kyverno/v1/zz_generated.deepcopy.go | 6 +-
docs/user/crd/index.html | 78 +++++++++-
docs/user/crd/kyverno.v1.html | 140 +++++++++++++++++-
.../kyverno/v1/foreachmutation.go | 4 +-
.../kyverno/v1/foreachvalidation.go | 5 +-
pkg/engine/forceMutate.go | 13 +-
pkg/engine/handlers/mutation/common.go | 11 +-
.../handlers/validation/validate_resource.go | 12 +-
pkg/policy/mutate/validate.go | 16 +-
pkg/policy/validate/validate.go | 2 +-
pkg/utils/api/json.go | 16 --
pkg/validation/policy/validate.go | 18 +--
14 files changed, 345 insertions(+), 77 deletions(-)
create mode 100644 api/kyverno/v1/wrappers.go
diff --git a/api/kyverno/v1/common_types.go b/api/kyverno/v1/common_types.go
index 15b4a2662e..5592dabb30 100644
--- a/api/kyverno/v1/common_types.go
+++ b/api/kyverno/v1/common_types.go
@@ -427,7 +427,16 @@ type ForEachMutation struct {
// Foreach declares a nested foreach iterator
// +optional
- ForEachMutation *apiextv1.JSON `json:"foreach,omitempty" yaml:"foreach,omitempty"`
+ // +kubebuilder:validation:Schemaless
+ // +kubebuilder:pruning:PreserveUnknownFields
+ ForEachMutation *ForEachMutationWrapper `json:"foreach,omitempty" yaml:"foreach,omitempty"`
+}
+
+func (m *ForEachMutation) GetForEachMutation() []ForEachMutation {
+ if m.ForEachMutation == nil {
+ return nil
+ }
+ return m.ForEachMutation.Items
}
func (m *ForEachMutation) GetPatchStrategicMerge() apiextensions.JSON {
@@ -690,7 +699,16 @@ type ForEachValidation struct {
// Foreach declares a nested foreach iterator
// +optional
- ForEachValidation *apiextv1.JSON `json:"foreach,omitempty" yaml:"foreach,omitempty"`
+ // +kubebuilder:validation:Schemaless
+ // +kubebuilder:pruning:PreserveUnknownFields
+ ForEachValidation *ForEachValidationWrapper `json:"foreach,omitempty" yaml:"foreach,omitempty"`
+}
+
+func (v *ForEachValidation) GetForEachValidation() []ForEachValidation {
+ if v.ForEachValidation == nil {
+ return nil
+ }
+ return v.ForEachValidation.Items
}
func (v *ForEachValidation) GetPattern() apiextensions.JSON {
diff --git a/api/kyverno/v1/wrappers.go b/api/kyverno/v1/wrappers.go
new file mode 100644
index 0000000000..710bfd0dcc
--- /dev/null
+++ b/api/kyverno/v1/wrappers.go
@@ -0,0 +1,79 @@
+package v1
+
+import (
+ "encoding/json"
+
+ "github.com/jinzhu/copier"
+)
+
+// ForEachValidationWrapper contains a list of ForEach descriptors.
+// +k8s:deepcopy-gen=false
+type ForEachValidationWrapper struct {
+ // Item is a descriptor on how to iterate over the list of items.
+ // +optional
+ Items []ForEachValidation `json:"-"`
+}
+
+func (in *ForEachValidationWrapper) DeepCopyInto(out *ForEachValidationWrapper) {
+ if err := copier.Copy(out, in); err != nil {
+ panic("deep copy failed")
+ }
+}
+
+func (in *ForEachValidationWrapper) DeepCopy() *ForEachValidationWrapper {
+ if in == nil {
+ return nil
+ }
+ out := new(ForEachValidationWrapper)
+ in.DeepCopyInto(out)
+ return out
+}
+
+func (a *ForEachValidationWrapper) MarshalJSON() ([]byte, error) {
+ return json.Marshal(a.Items)
+}
+
+func (a *ForEachValidationWrapper) UnmarshalJSON(data []byte) error {
+ var res []ForEachValidation
+ if err := json.Unmarshal(data, &res); err != nil {
+ return err
+ }
+ a.Items = res
+ return nil
+}
+
+// ForEachMutationWrapper contains a list of ForEach descriptors.
+// +k8s:deepcopy-gen=false
+type ForEachMutationWrapper struct {
+ // Item is a descriptor on how to iterate over the list of items.
+ // +optional
+ Items []ForEachMutation `json:"-"`
+}
+
+func (in *ForEachMutationWrapper) DeepCopyInto(out *ForEachMutationWrapper) {
+ if err := copier.Copy(out, in); err != nil {
+ panic("deep copy failed")
+ }
+}
+
+func (in *ForEachMutationWrapper) DeepCopy() *ForEachMutationWrapper {
+ if in == nil {
+ return nil
+ }
+ out := new(ForEachMutationWrapper)
+ in.DeepCopyInto(out)
+ return out
+}
+
+func (a *ForEachMutationWrapper) MarshalJSON() ([]byte, error) {
+ return json.Marshal(a.Items)
+}
+
+func (a *ForEachMutationWrapper) UnmarshalJSON(data []byte) error {
+ var res []ForEachMutation
+ if err := json.Unmarshal(data, &res); err != nil {
+ return err
+ }
+ a.Items = res
+ return nil
+}
diff --git a/api/kyverno/v1/zz_generated.deepcopy.go b/api/kyverno/v1/zz_generated.deepcopy.go
index 478a57f7e7..a2e7401891 100755
--- a/api/kyverno/v1/zz_generated.deepcopy.go
+++ b/api/kyverno/v1/zz_generated.deepcopy.go
@@ -565,8 +565,7 @@ func (in *ForEachMutation) DeepCopyInto(out *ForEachMutation) {
}
if in.ForEachMutation != nil {
in, out := &in.ForEachMutation, &out.ForEachMutation
- *out = new(apiextensionsv1.JSON)
- (*in).DeepCopyInto(*out)
+ *out = (*in).DeepCopy()
}
return
}
@@ -618,8 +617,7 @@ func (in *ForEachValidation) DeepCopyInto(out *ForEachValidation) {
}
if in.ForEachValidation != nil {
in, out := &in.ForEachValidation, &out.ForEachValidation
- *out = new(apiextensionsv1.JSON)
- (*in).DeepCopyInto(*out)
+ *out = (*in).DeepCopy()
}
return
}
diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html
index f87b0056b5..13d4807a30 100644
--- a/docs/user/crd/index.html
+++ b/docs/user/crd/index.html
@@ -1615,6 +1615,7 @@ string
(Appears on:
+ForEachMutationWrapper,
Mutation)
@@ -1718,8 +1719,8 @@ See https://tools.ietf.org/html/rf
foreach
-
-Kubernetes apiextensions/v1.JSON
+
+ForEachMutationWrapper
|
@@ -1731,10 +1732,45 @@ Kubernetes apiextensions/v1.JSON
+ForEachMutationWrapper
+
+
+(Appears on:
+ForEachMutation)
+
+
+
ForEachMutationWrapper contains a list of ForEach descriptors.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+-
+
+
+[]ForEachMutation
+
+
+ |
+
+(Optional)
+ Item is a descriptor on how to iterate over the list of items.
+ |
+
+
+
+
ForEachValidation
(Appears on:
+ForEachValidationWrapper,
Validation,
Validation)
@@ -1852,8 +1888,8 @@ Deny
foreach
-
-Kubernetes apiextensions/v1.JSON
+
+ForEachValidationWrapper
|
@@ -1865,6 +1901,40 @@ Kubernetes apiextensions/v1.JSON
+ForEachValidationWrapper
+
+
+(Appears on:
+ForEachValidation)
+
+
+
ForEachValidationWrapper contains a list of ForEach descriptors.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+-
+
+
+[]ForEachValidation
+
+
+ |
+
+(Optional)
+ Item is a descriptor on how to iterate over the list of items.
+ |
+
+
+
+
ForeachOrder
(string
alias)
diff --git a/docs/user/crd/kyverno.v1.html b/docs/user/crd/kyverno.v1.html
index 2ef4588bd3..dc349e3865 100644
--- a/docs/user/crd/kyverno.v1.html
+++ b/docs/user/crd/kyverno.v1.html
@@ -3318,6 +3318,7 @@ Dryrun requires additional permissions. See config/dryrun/dryrun_rbac.yaml
(Appears in:
+ ForEachMutationWrapper,
Mutation)
@@ -3529,7 +3530,9 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
- k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON
+
+ ForEachMutationWrapper
+
@@ -3548,6 +3551,71 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
+
+
+
+
+ ForEachMutationWrapper
+
+
+
+
+ (Appears in:
+ ForEachMutation)
+
+
+
+ ForEachMutationWrapper contains a list of ForEach descriptors.
+
+
+
+
+
+
+ Field |
+ Description |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ []ForEachMutation
+
+
+
+ |
+
+
+
+ Item is a descriptor on how to iterate over the list of items.
+
+
+
+
+
+ |
+
+
+
+
+
@@ -3558,6 +3626,7 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
(Appears in:
+ ForEachValidationWrapper,
Validation)
@@ -3795,7 +3864,9 @@ must be satisfied for the validation rule to succeed.
- k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON
+
+ ForEachValidationWrapper
+
@@ -3814,6 +3885,71 @@ must be satisfied for the validation rule to succeed.
+
+
+
+
+ ForEachValidationWrapper
+
+
+
+
+ (Appears in:
+ ForEachValidation)
+
+
+
+ ForEachValidationWrapper contains a list of ForEach descriptors.
+
+
+
+
+
+
+ Field |
+ Description |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ []ForEachValidation
+
+
+
+ |
+
+
+
+ Item is a descriptor on how to iterate over the list of items.
+
+
+
+
+
+ |
+
+
+
+
+
diff --git a/pkg/client/applyconfigurations/kyverno/v1/foreachmutation.go b/pkg/client/applyconfigurations/kyverno/v1/foreachmutation.go
index 96df14f479..d6f56a6696 100644
--- a/pkg/client/applyconfigurations/kyverno/v1/foreachmutation.go
+++ b/pkg/client/applyconfigurations/kyverno/v1/foreachmutation.go
@@ -32,7 +32,7 @@ type ForEachMutationApplyConfiguration struct {
AnyAllConditions *AnyAllConditionsApplyConfiguration `json:"preconditions,omitempty"`
RawPatchStrategicMerge *apiextensionsv1.JSON `json:"patchStrategicMerge,omitempty"`
PatchesJSON6902 *string `json:"patchesJson6902,omitempty"`
- ForEachMutation *apiextensionsv1.JSON `json:"foreach,omitempty"`
+ ForEachMutation *v1.ForEachMutationWrapper `json:"foreach,omitempty"`
}
// ForEachMutationApplyConfiguration constructs an declarative configuration of the ForEachMutation type for use with
@@ -97,7 +97,7 @@ func (b *ForEachMutationApplyConfiguration) WithPatchesJSON6902(value string) *F
// WithForEachMutation sets the ForEachMutation field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ForEachMutation field is set to the value of the last call.
-func (b *ForEachMutationApplyConfiguration) WithForEachMutation(value apiextensionsv1.JSON) *ForEachMutationApplyConfiguration {
+func (b *ForEachMutationApplyConfiguration) WithForEachMutation(value v1.ForEachMutationWrapper) *ForEachMutationApplyConfiguration {
b.ForEachMutation = &value
return b
}
diff --git a/pkg/client/applyconfigurations/kyverno/v1/foreachvalidation.go b/pkg/client/applyconfigurations/kyverno/v1/foreachvalidation.go
index c18cd3240c..04bf1f4f8b 100644
--- a/pkg/client/applyconfigurations/kyverno/v1/foreachvalidation.go
+++ b/pkg/client/applyconfigurations/kyverno/v1/foreachvalidation.go
@@ -19,6 +19,7 @@ limitations under the License.
package v1
import (
+ kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
@@ -32,7 +33,7 @@ type ForEachValidationApplyConfiguration struct {
RawPattern *apiextensionsv1.JSON `json:"pattern,omitempty"`
RawAnyPattern *apiextensionsv1.JSON `json:"anyPattern,omitempty"`
Deny *DenyApplyConfiguration `json:"deny,omitempty"`
- ForEachValidation *apiextensionsv1.JSON `json:"foreach,omitempty"`
+ ForEachValidation *kyvernov1.ForEachValidationWrapper `json:"foreach,omitempty"`
}
// ForEachValidationApplyConfiguration constructs an declarative configuration of the ForEachValidation type for use with
@@ -105,7 +106,7 @@ func (b *ForEachValidationApplyConfiguration) WithDeny(value *DenyApplyConfigura
// WithForEachValidation sets the ForEachValidation field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ForEachValidation field is set to the value of the last call.
-func (b *ForEachValidationApplyConfiguration) WithForEachValidation(value apiextensionsv1.JSON) *ForEachValidationApplyConfiguration {
+func (b *ForEachValidationApplyConfiguration) WithForEachValidation(value kyvernov1.ForEachValidationWrapper) *ForEachValidationApplyConfiguration {
b.ForEachValidation = &value
return b
}
diff --git a/pkg/engine/forceMutate.go b/pkg/engine/forceMutate.go
index e6ca6edd11..741de18b11 100644
--- a/pkg/engine/forceMutate.go
+++ b/pkg/engine/forceMutate.go
@@ -1,15 +1,12 @@
package engine
import (
- "fmt"
-
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/mutate"
"github.com/kyverno/kyverno/pkg/engine/variables"
- "github.com/kyverno/kyverno/pkg/utils/api"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -64,13 +61,9 @@ func ForceMutate(
func applyForEachMutate(name string, foreach []kyvernov1.ForEachMutation, resource unstructured.Unstructured, logger logr.Logger) (patchedResource unstructured.Unstructured, err error) {
patchedResource = resource
for _, fe := range foreach {
- if fe.ForEachMutation != nil {
- nestedForEach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](fe.ForEachMutation)
- if err != nil {
- return patchedResource, fmt.Errorf("failed to deserialize foreach: %w", err)
- }
-
- return applyForEachMutate(name, nestedForEach, patchedResource, logger)
+ fem := fe.GetForEachMutation()
+ if len(fem) > 0 {
+ return applyForEachMutate(name, fem, patchedResource, logger)
}
patchedResource, err = applyPatches(fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, patchedResource, logger)
diff --git a/pkg/engine/handlers/mutation/common.go b/pkg/engine/handlers/mutation/common.go
index 4ba5609068..5ac473daca 100644
--- a/pkg/engine/handlers/mutation/common.go
+++ b/pkg/engine/handlers/mutation/common.go
@@ -11,7 +11,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/mutate"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
- "github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
@@ -110,18 +109,14 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
}
var mutateResp *mutate.Response
- if foreach.ForEachMutation != nil {
- nestedForEach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](foreach.ForEachMutation)
- if err != nil {
- return mutate.NewErrorResponse("failed to deserialize foreach", err)
- }
-
+ fem := foreach.GetForEachMutation()
+ if len(fem) > 0 {
m := &forEachMutator{
rule: f.rule,
policyContext: f.policyContext,
resource: patchedResource,
logger: f.logger,
- foreach: nestedForEach,
+ foreach: fem,
nesting: f.nesting + 1,
contextLoader: f.contextLoader,
}
diff --git a/pkg/engine/handlers/validation/validate_resource.go b/pkg/engine/handlers/validation/validate_resource.go
index f181fcbc67..bcb5299c91 100644
--- a/pkg/engine/handlers/validation/validate_resource.go
+++ b/pkg/engine/handlers/validation/validate_resource.go
@@ -16,7 +16,6 @@ import (
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/engine/variables"
- "github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
"github.com/pkg/errors"
@@ -103,9 +102,12 @@ func newForEachValidator(
if err != nil {
return nil, fmt.Errorf("failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions: %w", err)
}
- nestedForEach, err := api.DeserializeJSONArray[kyvernov1.ForEachValidation](foreach.ForEachValidation)
- if err != nil {
- return nil, fmt.Errorf("failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions: %w", err)
+ var loopItems []kyvernov1.ForEachValidation
+ fev := foreach.GetForEachValidation()
+ if len(fev) > 0 {
+ loopItems = fev
+ } else {
+ loopItems = make([]kyvernov1.ForEachValidation, 0)
}
return &validator{
log: log,
@@ -117,7 +119,7 @@ func newForEachValidator(
pattern: foreach.GetPattern(),
anyPattern: foreach.GetAnyPattern(),
deny: foreach.Deny,
- forEach: nestedForEach,
+ forEach: loopItems,
nesting: nesting,
}, nil
}
diff --git a/pkg/policy/mutate/validate.go b/pkg/policy/mutate/validate.go
index 320916f196..4d7f221a29 100644
--- a/pkg/policy/mutate/validate.go
+++ b/pkg/policy/mutate/validate.go
@@ -8,10 +8,8 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/policy/auth"
- "github.com/kyverno/kyverno/pkg/utils/api"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"go.uber.org/multierr"
- v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
// Mutate provides implementation to validate 'mutate' rule
@@ -55,12 +53,13 @@ func (m *Mutate) Validate(ctx context.Context) (string, error) {
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (string, error) {
for i, fe := range foreach {
tag = tag + fmt.Sprintf("foreach[%d]", i)
- if fe.ForEachMutation != nil {
+ fem := fe.GetForEachMutation()
+ if len(fem) > 0 {
if fe.Context != nil || fe.AnyAllConditions != nil || fe.PatchesJSON6902 != "" || fe.RawPatchStrategicMerge != nil {
return tag, fmt.Errorf("a nested foreach cannot contain other declarations")
}
- return m.validateNestedForEach(tag, fe.ForEachMutation)
+ return m.validateNestedForEach(tag, fem)
}
psm := fe.GetPatchStrategicMerge()
@@ -72,13 +71,12 @@ func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation
return "", nil
}
-func (m *Mutate) validateNestedForEach(tag string, j *v1.JSON) (string, error) {
- nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](j)
- if err != nil {
- return tag, fmt.Errorf("invalid foreach syntax: %w", err)
+func (m *Mutate) validateNestedForEach(tag string, j []kyvernov1.ForEachMutation) (string, error) {
+ if j != nil {
+ return m.validateForEach(tag, j)
}
- return m.validateForEach(tag, nestedForeach)
+ return "", nil
}
func (m *Mutate) hasForEach() bool {
diff --git a/pkg/policy/validate/validate.go b/pkg/policy/validate/validate.go
index fa0a079fc2..49e6282df3 100644
--- a/pkg/policy/validate/validate.go
+++ b/pkg/policy/validate/validate.go
@@ -204,7 +204,7 @@ func foreachElemCount(foreach kyvernov1.ForEachValidation) int {
count++
}
- if foreach.ForEachValidation != nil {
+ if foreach.GetForEachValidation() != nil && len(foreach.GetForEachValidation()) > 0 {
count++
}
diff --git a/pkg/utils/api/json.go b/pkg/utils/api/json.go
index 3f3d3f9703..9fdc9fa6c7 100644
--- a/pkg/utils/api/json.go
+++ b/pkg/utils/api/json.go
@@ -8,22 +8,6 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)
-// Deserialize "apiextensions.JSON" to a typed array
-func DeserializeJSONArray[T any](in apiextensions.JSON) ([]T, error) {
- if in == nil {
- return nil, nil
- }
- data, err := json.Marshal(in)
- if err != nil {
- return nil, err
- }
- var res []T
- if err := json.Unmarshal(data, &res); err != nil {
- return nil, err
- }
- return res, nil
-}
-
// ApiextensionsJsonToKyvernoConditions takes in user-provided conditions in abstract apiextensions.JSON form
// and converts it into []kyverno.Condition or kyverno.AnyAllConditions according to its content.
// it also helps in validating the condtions as it returns an error when the conditions are provided wrongfully by the user.
diff --git a/pkg/validation/policy/validate.go b/pkg/validation/policy/validate.go
index 359e013e96..ead41c3d52 100644
--- a/pkg/validation/policy/validate.go
+++ b/pkg/validation/policy/validate.go
@@ -1002,12 +1002,9 @@ func validateValidationForEach(foreach []kyvernov1.ForEachValidation, schemaKey
}
}
}
- if fe.ForEachValidation != nil {
- nestedForEach, err := apiutils.DeserializeJSONArray[kyvernov1.ForEachValidation](fe.ForEachValidation)
- if err != nil {
- return schemaKey, err
- }
- if path, err := validateValidationForEach(nestedForEach, schemaKey); err != nil {
+ fev := fe.GetForEachValidation()
+ if len(fev) > 0 {
+ if path, err := validateValidationForEach(fev, schemaKey); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
@@ -1022,12 +1019,9 @@ func validateMutationForEach(foreach []kyvernov1.ForEachMutation, schemaKey stri
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}
- if fe.ForEachMutation != nil {
- nestedForEach, err := apiutils.DeserializeJSONArray[kyvernov1.ForEachMutation](fe.ForEachMutation)
- if err != nil {
- return schemaKey, err
- }
- if path, err := validateMutationForEach(nestedForEach, schemaKey); err != nil {
+ fem := fe.GetForEachMutation()
+ if len(fem) > 0 {
+ if path, err := validateMutationForEach(fem, schemaKey); err != nil {
return fmt.Sprintf("%s.%s", schemaKey, path), err
}
}