1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 18:38:40 +00:00

feat(json): unmarshal once per policy (#10701)

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>
Co-authored-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Khaled Emara 2024-07-30 13:52:41 +03:00 committed by GitHub
parent 74e17cc629
commit d173752041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 345 additions and 77 deletions

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -1615,6 +1615,7 @@ string
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ForEachMutationWrapper">ForEachMutationWrapper</a>,
<a href="#kyverno.io/v1.Mutation">Mutation</a>)
</p>
<p>
@ -1718,8 +1719,8 @@ See <a href="https://tools.ietf.org/html/rfc6902">https://tools.ietf.org/html/rf
<td>
<code>foreach</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
Kubernetes apiextensions/v1.JSON
<a href="#kyverno.io/v1.ForEachMutationWrapper">
ForEachMutationWrapper
</a>
</em>
</td>
@ -1731,10 +1732,45 @@ Kubernetes apiextensions/v1.JSON
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ForEachMutationWrapper">ForEachMutationWrapper
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ForEachMutation">ForEachMutation</a>)
</p>
<p>
<p>ForEachMutationWrapper contains a list of ForEach descriptors.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>-</code><br/>
<em>
<a href="#kyverno.io/v1.ForEachMutation">
[]ForEachMutation
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Item is a descriptor on how to iterate over the list of items.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ForEachValidation">ForEachValidation
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ForEachValidationWrapper">ForEachValidationWrapper</a>,
<a href="#kyverno.io/v1.Validation">Validation</a>,
<a href="#kyverno.io/v2beta1.Validation">Validation</a>)
</p>
@ -1852,8 +1888,8 @@ Deny
<td>
<code>foreach</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
Kubernetes apiextensions/v1.JSON
<a href="#kyverno.io/v1.ForEachValidationWrapper">
ForEachValidationWrapper
</a>
</em>
</td>
@ -1865,6 +1901,40 @@ Kubernetes apiextensions/v1.JSON
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ForEachValidationWrapper">ForEachValidationWrapper
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ForEachValidation">ForEachValidation</a>)
</p>
<p>
<p>ForEachValidationWrapper contains a list of ForEach descriptors.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>-</code><br/>
<em>
<a href="#kyverno.io/v1.ForEachValidation">
[]ForEachValidation
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Item is a descriptor on how to iterate over the list of items.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ForeachOrder">ForeachOrder
(<code>string</code> alias)</p></h3>
<p>

View file

@ -3318,6 +3318,7 @@ Dryrun requires additional permissions. See config/dryrun/dryrun_rbac.yaml</p>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-ForEachMutationWrapper">ForEachMutationWrapper</a>,
<a href="#kyverno-io-v1-Mutation">Mutation</a>)
</p>
@ -3529,7 +3530,9 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
<span style="font-family: monospace">k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON</span>
<a href="#kyverno-io-v1-ForEachMutationWrapper">
<span style="font-family: monospace">ForEachMutationWrapper</span>
</a>
</td>
@ -3548,6 +3551,71 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
</tbody>
</table>
<H3 id="kyverno-io-v1-ForEachMutationWrapper">ForEachMutationWrapper
</H3>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-ForEachMutation">ForEachMutation</a>)
</p>
<p><p>ForEachMutationWrapper contains a list of ForEach descriptors.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-</code>
</br>
<a href="#kyverno-io-v1-ForEachMutation">
<span style="font-family: monospace">[]ForEachMutation</span>
</a>
</td>
<td>
<p>Item is a descriptor on how to iterate over the list of items.</p>
</td>
</tr>
</tbody>
</table>
@ -3558,6 +3626,7 @@ See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/r
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-ForEachValidationWrapper">ForEachValidationWrapper</a>,
<a href="#kyverno-io-v1-Validation">Validation</a>)
</p>
@ -3795,7 +3864,9 @@ must be satisfied for the validation rule to succeed.</p>
<span style="font-family: monospace">k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON</span>
<a href="#kyverno-io-v1-ForEachValidationWrapper">
<span style="font-family: monospace">ForEachValidationWrapper</span>
</a>
</td>
@ -3814,6 +3885,71 @@ must be satisfied for the validation rule to succeed.</p>
</tbody>
</table>
<H3 id="kyverno-io-v1-ForEachValidationWrapper">ForEachValidationWrapper
</H3>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-ForEachValidation">ForEachValidation</a>)
</p>
<p><p>ForEachValidationWrapper contains a list of ForEach descriptors.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-</code>
</br>
<a href="#kyverno-io-v1-ForEachValidation">
<span style="font-family: monospace">[]ForEachValidation</span>
</a>
</td>
<td>
<p>Item is a descriptor on how to iterate over the list of items.</p>
</td>
</tr>
</tbody>
</table>

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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,
}

View file

@ -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
}

View file

@ -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 {

View file

@ -204,7 +204,7 @@ func foreachElemCount(foreach kyvernov1.ForEachValidation) int {
count++
}
if foreach.ForEachValidation != nil {
if foreach.GetForEachValidation() != nil && len(foreach.GetForEachValidation()) > 0 {
count++
}

View file

@ -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.

View file

@ -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
}
}