1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

feat: support foreach for generate.data (#10875)

* chore: refactor

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: add foreach for generate.daya to api

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: refactor generator

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: linter

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update rule validation

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update rule validation -2

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: support foreach.data

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: policy validation

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: context variables

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: add a chainsaw test

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: sync on policy deletion

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: enable new chainsaw tests in CI

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update code-gen

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: validate targets scope for ns-policies

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: add missing files

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: remove unreasonable test

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update docs

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update install.yaml

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
Co-authored-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
shuting 2024-08-19 14:55:19 +08:00 committed by GitHub
parent c96f224e8e
commit bd71af3291
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 14450 additions and 337 deletions

View file

@ -105,6 +105,7 @@ jobs:
- ^generate$/^clusterpolicy$
- ^generate$/^policy$
- ^generate$/^validation$
- ^generate$/^foreach$
- ^globalcontext$
- ^lease$
- ^mutate$

View file

@ -749,9 +749,6 @@ type Generation struct {
// +optional
GenerateExisting *bool `json:"generateExisting,omitempty" yaml:"generateExisting,omitempty"`
// ResourceSpec contains information to select the resource.
ResourceSpec `json:",omitempty" yaml:",omitempty"`
// Synchronize controls if generated resources should be kept in-sync with their source resource.
// If Synchronize is set to "true" changes to generated resources will be overwritten with resource
// data from Data or the resource specified in the Clone declaration.
@ -766,6 +763,19 @@ type Generation struct {
// +optional
OrphanDownstreamOnPolicyDelete bool `json:"orphanDownstreamOnPolicyDelete,omitempty" yaml:"orphanDownstreamOnPolicyDelete,omitempty"`
// +optional
GeneratePatterns `json:",omitempty" yaml:",omitempty"`
// ForEach applies generate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.
// +optional
ForEachGeneration []ForEachGeneration `json:"foreach,omitempty" yaml:"foreach,omitempty"`
}
type GeneratePatterns struct {
// ResourceSpec contains information to select the resource.
// +kubebuilder:validation:Optional
ResourceSpec `json:",omitempty" yaml:",omitempty"`
// Data provides the resource declaration used to populate each generated resource.
// At most one of Data or Clone must be specified. If neither are provided, the generated
// resource will be created with default data only.
@ -783,6 +793,25 @@ type Generation struct {
CloneList CloneList `json:"cloneList,omitempty" yaml:"cloneList,omitempty"`
}
type ForEachGeneration struct {
// List specifies a JMESPath expression that results in one or more elements
// to which the validation logic is applied.
List string `json:"list,omitempty" yaml:"list,omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
// AnyAllConditions are used to determine if a policy rule should be applied by evaluating a
// set of conditions. The declaration can contain nested `any` or `all` statements.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
GeneratePatterns `json:",omitempty" yaml:",omitempty"`
}
type CloneList struct {
// Namespace specifies source resource namespace.
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
@ -797,30 +826,55 @@ type CloneList struct {
}
func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
count := 0
if g.GetData() != nil {
count++
}
if g.Clone != (CloneFrom{}) {
count++
}
if g.CloneList.Kinds != nil {
count++
}
if g.ForEachGeneration != nil {
count++
}
if count > 1 {
errs = append(errs, field.Forbidden(path, "only one of generate patterns(data, clone, cloneList and foreach) can be specified"))
return errs
}
if g.ForEachGeneration != nil {
for i, foreach := range g.ForEachGeneration {
err := foreach.GeneratePatterns.Validate(path.Child("foreach").Index(i), namespaced, policyNamespace, clusterResources)
errs = append(errs, err...)
}
return errs
} else {
return g.GeneratePatterns.Validate(path, namespaced, policyNamespace, clusterResources)
}
}
func (g *GeneratePatterns) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if namespaced {
if err := g.validateNamespacedTargetsScope(clusterResources, policyNamespace); err != nil {
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
errs = append(errs, field.Forbidden(path.Child("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
}
}
if g.GetKind() != "" {
if !clusterResources.Has(g.GetAPIVersion() + "/" + g.GetKind()) {
if g.GetNamespace() == "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), "target namespace must be set for a namespaced resource"))
errs = append(errs, field.Forbidden(path.Child("namespace"), "target namespace must be set for a namespaced resource"))
}
} else {
if g.GetNamespace() != "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("namespace"), "target namespace must not be set for a cluster-wide resource"))
errs = append(errs, field.Forbidden(path.Child("namespace"), "target namespace must not be set for a cluster-wide resource"))
}
}
}
generateType, _, _ := g.GetTypeAndSyncAndOrphanDownstream()
if generateType == Data {
return errs
}
newGeneration := Generation{
newGeneration := GeneratePatterns{
ResourceSpec: ResourceSpec{
Kind: g.ResourceSpec.GetKind(),
APIVersion: g.ResourceSpec.GetAPIVersion(),
@ -830,23 +884,25 @@ func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace
}
if err := regex.ObjectHasVariables(newGeneration); err != nil {
errs = append(errs, field.Forbidden(path.Child("generate").Child("clone/cloneList"), "Generation Rule Clone/CloneList should not have variables"))
errs = append(errs, field.Forbidden(path.Child("clone/cloneList"), "Generation Rule Clone/CloneList should not have variables"))
}
if len(g.CloneList.Kinds) == 0 {
if g.Kind == "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("kind"), "kind can not be empty"))
errs = append(errs, field.Forbidden(path.Child("kind"), "kind can not be empty"))
}
if g.Name == "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("name"), "name can not be empty"))
errs = append(errs, field.Forbidden(path.Child("name"), "name can not be empty"))
}
if g.APIVersion == "" {
errs = append(errs, field.Forbidden(path.Child("apiVersion"), "apiVersion can not be empty"))
}
}
errs = append(errs, g.ValidateCloneList(path.Child("generate"), namespaced, policyNamespace, clusterResources)...)
return errs
return append(errs, g.ValidateCloneList(path, namespaced, policyNamespace, clusterResources)...)
}
func (g *Generation) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (g *GeneratePatterns) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if len(g.CloneList.Kinds) == 0 {
return nil
}
@ -883,15 +939,23 @@ func (g *Generation) ValidateCloneList(path *field.Path, namespaced bool, policy
return errs
}
func (g *Generation) GetData() apiextensions.JSON {
func (g *GeneratePatterns) GetType() GenerateType {
if g.RawData != nil {
return Data
}
return Clone
}
func (g *GeneratePatterns) GetData() apiextensions.JSON {
return FromJSON(g.RawData)
}
func (g *Generation) SetData(in apiextensions.JSON) {
func (g *GeneratePatterns) SetData(in apiextensions.JSON) {
g.RawData = ToJSON(in)
}
func (g *Generation) validateNamespacedTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
func (g *GeneratePatterns) validateNamespacedTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
target := g.ResourceSpec
if clusterResources.Has(target.GetAPIVersion() + "/" + target.GetKind()) {
return fmt.Errorf("the target must be a namespaced resource: %v/%v", target.GetAPIVersion(), target.GetKind())
@ -916,13 +980,6 @@ const (
Clone GenerateType = "Clone"
)
func (g *Generation) GetTypeAndSyncAndOrphanDownstream() (GenerateType, bool, bool) {
if g.RawData != nil {
return Data, g.Synchronize, g.OrphanDownstreamOnPolicyDelete
}
return Clone, g.Synchronize, g.OrphanDownstreamOnPolicyDelete
}
// CloneFrom provides the location of the source resource used to generate target resources.
// The resource kind is derived from the match criteria.
type CloneFrom struct {

View file

@ -179,11 +179,11 @@ func (r *Rule) IsPodSecurity() bool {
return r.Validation.PodSecurity != nil
}
func (r *Rule) GetTypeAndSyncAndOrphanDownstream() (_ GenerateType, sync bool, orphanDownstream bool) {
func (r *Rule) GetSyncAndOrphanDownstream() (sync bool, orphanDownstream bool) {
if !r.HasGenerate() {
return
}
return r.Generation.GetTypeAndSyncAndOrphanDownstream()
return r.Generation.Synchronize, r.Generation.OrphanDownstreamOnPolicyDelete
}
func (r *Rule) GetAnyAllConditions() any {

View file

@ -537,6 +537,35 @@ func (in *DryRunOption) DeepCopy() *DryRunOption {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForEachGeneration) DeepCopyInto(out *ForEachGeneration) {
*out = *in
if in.Context != nil {
in, out := &in.Context, &out.Context
*out = make([]ContextEntry, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AnyAllConditions != nil {
in, out := &in.AnyAllConditions, &out.AnyAllConditions
*out = new(AnyAllConditions)
(*in).DeepCopyInto(*out)
}
in.GeneratePatterns.DeepCopyInto(&out.GeneratePatterns)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachGeneration.
func (in *ForEachGeneration) DeepCopy() *ForEachGeneration {
if in == nil {
return nil
}
out := new(ForEachGeneration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForEachMutation) DeepCopyInto(out *ForEachMutation) {
*out = *in
@ -631,13 +660,8 @@ func (in *ForEachValidation) DeepCopy() *ForEachValidation {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Generation) DeepCopyInto(out *Generation) {
func (in *GeneratePatterns) DeepCopyInto(out *GeneratePatterns) {
*out = *in
if in.GenerateExisting != nil {
in, out := &in.GenerateExisting, &out.GenerateExisting
*out = new(bool)
**out = **in
}
out.ResourceSpec = in.ResourceSpec
if in.RawData != nil {
in, out := &in.RawData, &out.RawData
@ -649,6 +673,35 @@ func (in *Generation) DeepCopyInto(out *Generation) {
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratePatterns.
func (in *GeneratePatterns) DeepCopy() *GeneratePatterns {
if in == nil {
return nil
}
out := new(GeneratePatterns)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Generation) DeepCopyInto(out *Generation) {
*out = *in
if in.GenerateExisting != nil {
in, out := &in.GenerateExisting, &out.GenerateExisting
*out = new(bool)
**out = **in
}
in.GeneratePatterns.DeepCopyInto(&out.GeneratePatterns)
if in.ForEachGeneration != nil {
in, out := &in.ForEachGeneration, &out.ForEachGeneration
*out = make([]ForEachGeneration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generation.
func (in *Generation) DeepCopy() *Generation {
if in == nil {

View file

@ -137,13 +137,6 @@ func (r *Rule) HasGenerate() bool {
return !datautils.DeepEqual(r.Generation, kyvernov1.Generation{})
}
func (r *Rule) GetGenerateTypeAndSync() (_ kyvernov1.GenerateType, sync bool, orphanDownstream bool) {
if !r.HasGenerate() {
return
}
return r.Generation.GetTypeAndSyncAndOrphanDownstream()
}
// ValidateRuleType checks only one type of rule is defined per rule
func (r *Rule) ValidateRuleType(path *field.Path) (errs field.ErrorList) {
ruleTypes := []bool{r.HasMutate(), r.HasValidate(), r.HasGenerate(), r.HasVerifyImages()}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -664,6 +664,7 @@ It&rsquo;s mutually exclusive with the URLPath field.</p>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Attestation">Attestation</a>,
<a href="#kyverno.io/v1.ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno.io/v1.ForEachMutation">ForEachMutation</a>,
<a href="#kyverno.io/v1.ForEachValidation">ForEachValidation</a>)
</p>
@ -1200,7 +1201,7 @@ Timestamps (SCTs). If the value is unset, the default behavior by Cosign is used
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Generation">Generation</a>)
<a href="#kyverno.io/v1.GeneratePatterns">GeneratePatterns</a>)
</p>
<p>
<p>CloneFrom provides the location of the source resource used to generate target resources.
@ -1453,6 +1454,7 @@ of deployments across all namespaces.</p>
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno.io/v1.ForEachMutation">ForEachMutation</a>,
<a href="#kyverno.io/v1.ForEachValidation">ForEachValidation</a>,
<a href="#kyverno.io/v1.Rule">Rule</a>,
@ -1645,6 +1647,79 @@ string
<p>
<p>FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled.</p>
</p>
<h3 id="kyverno.io/v1.ForEachGeneration">ForEachGeneration
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Generation">Generation</a>)
</p>
<p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>list</code><br/>
<em>
string
</em>
</td>
<td>
<p>List specifies a JMESPath expression that results in one or more elements
to which the validation logic is applied.</p>
</td>
</tr>
<tr>
<td>
<code>context</code><br/>
<em>
<a href="#kyverno.io/v1.ContextEntry">
[]ContextEntry
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Context defines variables and data sources that can be used during rule execution.</p>
</td>
</tr>
<tr>
<td>
<code>preconditions</code><br/>
<em>
<a href="#kyverno.io/v1.AnyAllConditions">
AnyAllConditions
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>AnyAllConditions are used to determine if a policy rule should be applied by evaluating a
set of conditions. The declaration can contain nested <code>any</code> or <code>all</code> statements.
See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://kyverno.io/docs/writing-policies/preconditions/</a></p>
</td>
</tr>
<tr>
<td>
<code>GeneratePatterns</code><br/>
<em>
<a href="#kyverno.io/v1.GeneratePatterns">
GeneratePatterns
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ForEachMutation">ForEachMutation
</h3>
<p>
@ -1976,19 +2051,14 @@ ForEachValidationWrapper
<p>
<p>ForeachOrder specifies the iteration order in foreach statements.</p>
</p>
<h3 id="kyverno.io/v1.GenerateType">GenerateType
(<code>string</code> alias)</p></h3>
<p>
</p>
<h3 id="kyverno.io/v1.Generation">Generation
<h3 id="kyverno.io/v1.GeneratePatterns">GeneratePatterns
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Rule">Rule</a>,
<a href="#kyverno.io/v2beta1.Rule">Rule</a>)
<a href="#kyverno.io/v1.ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno.io/v1.Generation">Generation</a>)
</p>
<p>
<p>Generation defines how new resources should be created and managed.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
@ -2000,19 +2070,6 @@ ForEachValidationWrapper
<tbody>
<tr>
<td>
<code>generateExisting</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>GenerateExisting controls whether to trigger the rule in existing resources
If is set to &ldquo;true&rdquo; the rule will be triggered and applied to existing matched resources.</p>
</td>
</tr>
<tr>
<td>
<code>ResourceSpec</code><br/>
<em>
<a href="#kyverno.io/v1.ResourceSpec">
@ -2026,36 +2083,6 @@ ResourceSpec
</tr>
<tr>
<td>
<code>synchronize</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Synchronize controls if generated resources should be kept in-sync with their source resource.
If Synchronize is set to &ldquo;true&rdquo; changes to generated resources will be overwritten with resource
data from Data or the resource specified in the Clone declaration.
Optional. Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
</tr>
<tr>
<td>
<code>orphanDownstreamOnPolicyDelete</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>OrphanDownstreamOnPolicyDelete controls whether generated resources should be deleted when the rule that generated
them is deleted with synchronization enabled. This option is only applicable to generate rules of the data type.
See <a href="https://kyverno.io/docs/writing-policies/generate/#data-examples">https://kyverno.io/docs/writing-policies/generate/#data-examples</a>.
Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
</tr>
<tr>
<td>
<code>data</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
@ -2103,6 +2130,101 @@ CloneList
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.GenerateType">GenerateType
(<code>string</code> alias)</p></h3>
<p>
</p>
<h3 id="kyverno.io/v1.Generation">Generation
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Rule">Rule</a>,
<a href="#kyverno.io/v2beta1.Rule">Rule</a>)
</p>
<p>
<p>Generation defines how new resources should be created and managed.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>generateExisting</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>GenerateExisting controls whether to trigger the rule in existing resources
If is set to &ldquo;true&rdquo; the rule will be triggered and applied to existing matched resources.</p>
</td>
</tr>
<tr>
<td>
<code>synchronize</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Synchronize controls if generated resources should be kept in-sync with their source resource.
If Synchronize is set to &ldquo;true&rdquo; changes to generated resources will be overwritten with resource
data from Data or the resource specified in the Clone declaration.
Optional. Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
</tr>
<tr>
<td>
<code>orphanDownstreamOnPolicyDelete</code><br/>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>OrphanDownstreamOnPolicyDelete controls whether generated resources should be deleted when the rule that generated
them is deleted with synchronization enabled. This option is only applicable to generate rules of the data type.
See <a href="https://kyverno.io/docs/writing-policies/generate/#data-examples">https://kyverno.io/docs/writing-policies/generate/#data-examples</a>.
Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
</tr>
<tr>
<td>
<code>GeneratePatterns</code><br/>
<em>
<a href="#kyverno.io/v1.GeneratePatterns">
GeneratePatterns
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>foreach</code><br/>
<em>
<a href="#kyverno.io/v1.ForEachGeneration">
[]ForEachGeneration
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ForEach applies generate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.GlobalContextEntryReference">GlobalContextEntryReference
</h3>
<p>
@ -3577,7 +3699,7 @@ ResourceDescription
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Generation">Generation</a>,
<a href="#kyverno.io/v1.GeneratePatterns">GeneratePatterns</a>,
<a href="#kyverno.io/v1.TargetResourceSpec">TargetResourceSpec</a>,
<a href="#kyverno.io/v1beta1.UpdateRequestSpec">UpdateRequestSpec</a>,
<a href="#kyverno.io/v1beta1.UpdateRequestStatus">UpdateRequestStatus</a>,

View file

@ -1332,6 +1332,7 @@ It's mutually exclusive with the URLPath field.</p>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Attestation">Attestation</a>,
<a href="#kyverno-io-v1-ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno-io-v1-ForEachMutation">ForEachMutation</a>,
<a href="#kyverno-io-v1-ForEachValidation">ForEachValidation</a>)
</p>
@ -2454,7 +2455,7 @@ Timestamps (SCTs). If the value is unset, the default behavior by Cosign is used
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Generation">Generation</a>)
<a href="#kyverno-io-v1-GeneratePatterns">GeneratePatterns</a>)
</p>
@ -2977,6 +2978,7 @@ of deployments across all namespaces.</p>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno-io-v1-ForEachMutation">ForEachMutation</a>,
<a href="#kyverno-io-v1-ForEachValidation">ForEachValidation</a>,
<a href="#kyverno-io-v1-Rule">Rule</a>,
@ -3379,6 +3381,161 @@ Dryrun requires additional permissions. See config/dryrun/dryrun_rbac.yaml</p>
<H3 id="kyverno-io-v1-ForEachGeneration">ForEachGeneration
</H3>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Generation">Generation</a>)
</p>
<p></p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>list</code>
<span style="color:blue;"> *</span>
</br>
<span style="font-family: monospace">string</span>
</td>
<td>
<p>List specifies a JMESPath expression that results in one or more elements
to which the validation logic is applied.</p>
</td>
</tr>
<tr>
<td><code>context</code>
</br>
<a href="#kyverno-io-v1-ContextEntry">
<span style="font-family: monospace">[]ContextEntry</span>
</a>
</td>
<td>
<p>Context defines variables and data sources that can be used during rule execution.</p>
</td>
</tr>
<tr>
<td><code>preconditions</code>
</br>
<a href="#kyverno-io-v1-AnyAllConditions">
<span style="font-family: monospace">AnyAllConditions</span>
</a>
</td>
<td>
<p>AnyAllConditions are used to determine if a policy rule should be applied by evaluating a
set of conditions. The declaration can contain nested <code>any</code> or <code>all</code> statements.
See: https://kyverno.io/docs/writing-policies/preconditions/</p>
</td>
</tr>
<tr>
<td><code>GeneratePatterns</code>
<span style="color:blue;"> *</span>
</br>
<a href="#kyverno-io-v1-GeneratePatterns">
<span style="font-family: monospace">GeneratePatterns</span>
</a>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<H3 id="kyverno-io-v1-ForEachMutation">ForEachMutation
</H3>
@ -4036,18 +4193,18 @@ must be satisfied for the validation rule to succeed.</p>
<H3 id="kyverno-io-v1-Generation">Generation
<H3 id="kyverno-io-v1-GeneratePatterns">GeneratePatterns
</H3>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Rule">Rule</a>)
<a href="#kyverno-io-v1-ForEachGeneration">ForEachGeneration</a>,
<a href="#kyverno-io-v1-Generation">Generation</a>)
</p>
<p><p>Generation defines how new resources should be created and managed.</p>
</p>
<p></p>
<table class="table table-striped">
@ -4068,34 +4225,6 @@ must be satisfied for the validation rule to succeed.</p>
<tr>
<td><code>generateExisting</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>GenerateExisting controls whether to trigger the rule in existing resources
If is set to &quot;true&quot; the rule will be triggered and applied to existing matched resources.</p>
</td>
</tr>
<tr>
<td><code>ResourceSpec</code>
@ -4127,66 +4256,6 @@ If is set to &quot;true&quot; the rule will be triggered and applied to existing
<tr>
<td><code>synchronize</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>Synchronize controls if generated resources should be kept in-sync with their source resource.
If Synchronize is set to &quot;true&quot; changes to generated resources will be overwritten with resource
data from Data or the resource specified in the Clone declaration.
Optional. Defaults to &quot;false&quot; if not specified.</p>
</td>
</tr>
<tr>
<td><code>orphanDownstreamOnPolicyDelete</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>OrphanDownstreamOnPolicyDelete controls whether generated resources should be deleted when the rule that generated
them is deleted with synchronization enabled. This option is only applicable to generate rules of the data type.
See https://kyverno.io/docs/writing-policies/generate/#data-examples.
Defaults to &quot;false&quot; if not specified.</p>
</td>
</tr>
<tr>
<td><code>data</code>
@ -4276,6 +4345,187 @@ resource will be created with default data only.</p>
</tbody>
</table>
<H3 id="kyverno-io-v1-Generation">Generation
</H3>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Rule">Rule</a>)
</p>
<p><p>Generation defines how new resources should be created and managed.</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>generateExisting</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>GenerateExisting controls whether to trigger the rule in existing resources
If is set to &quot;true&quot; the rule will be triggered and applied to existing matched resources.</p>
</td>
</tr>
<tr>
<td><code>synchronize</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>Synchronize controls if generated resources should be kept in-sync with their source resource.
If Synchronize is set to &quot;true&quot; changes to generated resources will be overwritten with resource
data from Data or the resource specified in the Clone declaration.
Optional. Defaults to &quot;false&quot; if not specified.</p>
</td>
</tr>
<tr>
<td><code>orphanDownstreamOnPolicyDelete</code>
</br>
<span style="font-family: monospace">bool</span>
</td>
<td>
<p>OrphanDownstreamOnPolicyDelete controls whether generated resources should be deleted when the rule that generated
them is deleted with synchronization enabled. This option is only applicable to generate rules of the data type.
See https://kyverno.io/docs/writing-policies/generate/#data-examples.
Defaults to &quot;false&quot; if not specified.</p>
</td>
</tr>
<tr>
<td><code>GeneratePatterns</code>
</br>
<a href="#kyverno-io-v1-GeneratePatterns">
<span style="font-family: monospace">GeneratePatterns</span>
</a>
</td>
<td>
</td>
</tr>
<tr>
<td><code>foreach</code>
</br>
<a href="#kyverno-io-v1-ForEachGeneration">
<span style="font-family: monospace">[]ForEachGeneration</span>
</a>
</td>
<td>
<p>ForEach applies generate rules to a list of sub-elements by creating a context for each entry in the list and looping over it to apply the specified logic.</p>
</td>
</tr>
</tbody>
</table>
@ -7080,7 +7330,7 @@ does not match an empty label set.</p>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-Generation">Generation</a>,
<a href="#kyverno-io-v1-GeneratePatterns">GeneratePatterns</a>,
<a href="#kyverno-io-v1-TargetResourceSpec">TargetResourceSpec</a>)
</p>

View file

@ -13,15 +13,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, severSideApply bool, rule kyvernov1.Rule, client dclient.Interface) generateResponse {
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, severSideApply bool, pattern kyvernov1.GeneratePatterns, client dclient.Interface) generateResponse {
source := sourceSpec
clone := rule.Generation
if clone.Clone.Name != "" {
if pattern.Clone.Name != "" {
source = kyvernov1.ResourceSpec{
APIVersion: target.GetAPIVersion(),
Kind: target.GetKind(),
Namespace: clone.Clone.Namespace,
Name: clone.Clone.Name,
Namespace: pattern.Clone.Namespace,
Name: pattern.Clone.Name,
}
}
@ -80,14 +79,13 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, sev
return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil)
}
func manageCloneList(log logr.Logger, targetNamespace string, severSideApply bool, rule kyvernov1.Rule, client dclient.Interface) []generateResponse {
func manageCloneList(log logr.Logger, targetNamespace string, severSideApply bool, pattern kyvernov1.GeneratePatterns, client dclient.Interface) []generateResponse {
var responses []generateResponse
cloneList := rule.Generation.CloneList
sourceNamespace := cloneList.Namespace
kinds := cloneList.Kinds
sourceNamespace := pattern.CloneList.Namespace
kinds := pattern.CloneList.Kinds
for _, kind := range kinds {
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
sources, err := client.ListResource(context.TODO(), apiVersion, kind, sourceNamespace, cloneList.Selector)
sources, err := client.ListResource(context.TODO(), apiVersion, kind, sourceNamespace, pattern.CloneList.Selector)
if err != nil {
responses = append(responses,
newSkipGenerateResponse(
@ -101,13 +99,13 @@ func manageCloneList(log logr.Logger, targetNamespace string, severSideApply boo
for _, source := range sources.Items {
target := newResourceSpec(source.GetAPIVersion(), source.GetKind(), targetNamespace, source.GetName())
if (cloneList.Kinds != nil) && (source.GetNamespace() == target.GetNamespace()) {
if (pattern.CloneList.Kinds != nil) && (source.GetNamespace() == target.GetNamespace()) {
log.V(4).Info("skip resource self-clone")
responses = append(responses, newSkipGenerateResponse(nil, target, nil))
continue
}
responses = append(responses,
manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), severSideApply, rule, client))
manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), severSideApply, pattern, client))
}
}
return responses

View file

@ -10,6 +10,7 @@ import (
"time"
"github.com/go-logr/logr"
gojmespath "github.com/kyverno/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
"github.com/kyverno/kyverno/pkg/background/common"
@ -21,7 +22,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/variables"
regex "github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/event"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
@ -278,6 +278,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
ruleNameToProcessingTime := make(map[string]time.Duration)
applyRules := policy.GetSpec().GetApplyRules()
applyCount := 0
log = log.WithValues("policy", policy.GetName(), "trigger", resource.GetNamespace()+"/"+resource.GetName())
for _, rule := range policy.GetSpec().Rules {
var err error
@ -310,21 +311,26 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
break
}
logger := log.WithValues("rule", rule.Name)
// add configmap json data to context
if err := c.engine.ContextLoader(policy, rule)(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil {
log.Error(err, "cannot add configmaps to context")
return nil, err
contextLoader := c.engine.ContextLoader(policy, rule)
if err := contextLoader(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load rule level context", "reason", err.Error())
} else {
logger.Error(err, "failed to load rule level context")
}
return nil, fmt.Errorf("failed to load rule level context: %v", err)
}
if rule, err = variables.SubstituteAllInRule(log, policyContext.JSONContext(), rule); err != nil {
log.Error(err, "variable substitution failed for rule", "rule", rule.Name)
return nil, err
if rule.Generation.ForEachGeneration != nil {
g := newForeachGenerator(c.client, logger, policyContext, policy, rule, rule.Context, rule.GetAnyAllConditions(), policyContext.NewResource(), rule.Generation.ForEachGeneration, contextLoader)
genResource, err = g.generateForeach()
} else {
g := newGenerator(c.client, logger, policyContext, policy, rule, rule.Context, rule.GetAnyAllConditions(), policyContext.NewResource(), rule.Generation.GeneratePatterns, contextLoader)
genResource, err = g.generate()
}
g := newGenerator(c.client, logger, policy, rule, resource)
genResource, err = g.generate()
if err != nil {
log.Error(err, "failed to apply generate rule", "policy", policy.GetName(), "rule", rule.Name, "resource", resource.GetName())
log.Error(err, "failed to apply generate rule")
return nil, err
}
ruleNameToProcessingTime[rule.Name] = time.Since(startTime)

View file

@ -2,31 +2,83 @@ package generate
import (
"context"
"fmt"
"github.com/go-logr/logr"
gojmespath "github.com/kyverno/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/background/common"
"github.com/kyverno/kyverno/pkg/clients/dclient"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/engine/variables"
"go.uber.org/multierr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type generator struct {
client dclient.Interface
logger logr.Logger
policy kyvernov1.PolicyInterface
rule kyvernov1.Rule
trigger unstructured.Unstructured
client dclient.Interface
logger logr.Logger
policyContext engineapi.PolicyContext
policy kyvernov1.PolicyInterface
rule kyvernov1.Rule
contextEntries []kyvernov1.ContextEntry
anyAllConditions any
trigger unstructured.Unstructured
forEach []kyvernov1.ForEachGeneration
pattern kyvernov1.GeneratePatterns
contextLoader engineapi.EngineContextLoader
}
func newGenerator(client dclient.Interface, logger logr.Logger, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, trigger unstructured.Unstructured) *generator {
func newGenerator(client dclient.Interface,
logger logr.Logger,
policyContext engineapi.PolicyContext,
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
contextEntries []kyvernov1.ContextEntry,
anyAllConditions any,
trigger unstructured.Unstructured,
pattern kyvernov1.GeneratePatterns,
contextLoader engineapi.EngineContextLoader,
) *generator {
return &generator{
client: client,
logger: logger,
policy: policy,
rule: rule,
trigger: trigger,
client: client,
logger: logger,
policyContext: policyContext,
policy: policy,
rule: rule,
contextEntries: contextEntries,
anyAllConditions: anyAllConditions,
trigger: trigger,
pattern: pattern,
contextLoader: contextLoader,
}
}
func newForeachGenerator(client dclient.Interface,
logger logr.Logger,
policyContext engineapi.PolicyContext,
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
contextEntries []kyvernov1.ContextEntry,
anyAllConditions any,
trigger unstructured.Unstructured,
forEach []kyvernov1.ForEachGeneration,
contextLoader engineapi.EngineContextLoader,
) *generator {
return &generator{
client: client,
logger: logger,
policyContext: policyContext,
policy: policy,
rule: rule,
contextEntries: contextEntries,
anyAllConditions: anyAllConditions,
trigger: trigger,
forEach: forEach,
contextLoader: contextLoader,
}
}
@ -35,16 +87,41 @@ func (g *generator) generate() ([]kyvernov1.ResourceSpec, error) {
var err error
var newGenResources []kyvernov1.ResourceSpec
target := g.rule.Generation.ResourceSpec
if err := g.loadContext(context.TODO()); err != nil {
return newGenResources, fmt.Errorf("failed to load context: %v", err)
}
typeConditions, err := engineutils.TransformConditions(g.anyAllConditions)
if err != nil {
return newGenResources, fmt.Errorf("failed to parse preconditions: %v", err)
}
preconditionsPassed, msg, err := variables.EvaluateConditions(g.logger, g.policyContext.JSONContext(), typeConditions)
if err != nil {
return newGenResources, fmt.Errorf("failed to evaluate preconditions: %v", err)
}
if !preconditionsPassed {
g.logger.V(2).Info("preconditions not met", "msg", msg)
return newGenResources, nil
}
pattern, err := variables.SubstituteAllInType(g.logger, g.policyContext.JSONContext(), &g.pattern)
if err != nil {
g.logger.Error(err, "variable substitution failed for rule", "rule", g.rule.Name)
return nil, err
}
target := pattern.ResourceSpec
logger := g.logger.WithValues("target", target.String())
if g.rule.Generation.Clone.Name != "" {
resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, g.policy.GetSpec().UseServerSideApply, g.rule, g.client)
if pattern.Clone.Name != "" {
resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, g.policy.GetSpec().UseServerSideApply, *pattern, g.client)
responses = append(responses, resp)
} else if len(g.rule.Generation.CloneList.Kinds) != 0 {
responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), g.policy.GetSpec().UseServerSideApply, g.rule, g.client)
} else if len(pattern.CloneList.Kinds) != 0 {
responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), g.policy.GetSpec().UseServerSideApply, *pattern, g.client)
} else {
resp := manageData(logger.WithValues("type", "data"), target, g.rule.Generation.RawData, g.rule.Generation.Synchronize, g.client)
resp := manageData(logger.WithValues("type", "data"), target, pattern.RawData, g.rule.Generation.Synchronize, g.client)
responses = append(responses, resp)
}
@ -138,3 +215,76 @@ func (g *generator) generate() ([]kyvernov1.ResourceSpec, error) {
}
return newGenResources, nil
}
func (g *generator) generateForeach() ([]kyvernov1.ResourceSpec, error) {
var errors []error
var genResources []kyvernov1.ResourceSpec
for i, foreach := range g.forEach {
elements, err := engineutils.EvaluateList(foreach.List, g.policyContext.JSONContext())
if err != nil {
errors = append(errors, fmt.Errorf("failed to evaluate %v foreach list: %v", i, err))
continue
}
gen, err := g.generateElements(foreach, elements, nil)
if err != nil {
errors = append(errors, fmt.Errorf("failed to process %v foreach in rule %s: %v", i, g.rule.Name, err))
}
if gen != nil {
genResources = append(genResources, gen...)
}
}
return genResources, multierr.Combine(errors...)
}
func (g *generator) generateElements(foreach kyvernov1.ForEachGeneration, elements []interface{}, elementScope *bool) ([]kyvernov1.ResourceSpec, error) {
var errors []error
var genResources []kyvernov1.ResourceSpec
g.policyContext.JSONContext().Checkpoint()
defer g.policyContext.JSONContext().Restore()
for index, element := range elements {
if element == nil {
continue
}
g.policyContext.JSONContext().Reset()
policyContext := g.policyContext.Copy()
if err := engineutils.AddElementToContext(policyContext, element, index, 0, elementScope); err != nil {
g.logger.Error(err, "")
errors = append(errors, fmt.Errorf("failed to add %v element to context: %v", index, err))
continue
}
gen, err := newGenerator(g.client,
g.logger,
policyContext,
g.policy,
g.rule,
foreach.Context,
foreach.AnyAllConditions,
g.trigger,
foreach.GeneratePatterns,
g.contextLoader).
generate()
if err != nil {
errors = append(errors, fmt.Errorf("failed to process %v element: %v", index, err))
}
if gen != nil {
genResources = append(genResources, gen...)
}
}
return genResources, multierr.Combine(errors...)
}
func (g *generator) loadContext(ctx context.Context) error {
if err := g.contextLoader(ctx, g.contextEntries, g.policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
g.logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
g.logger.Error(err, "failed to load context")
}
return err
}
return nil
}

View file

@ -0,0 +1,152 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
types "k8s.io/apimachinery/pkg/types"
)
// ForEachGenerationApplyConfiguration represents an declarative configuration of the ForEachGeneration type for use
// with apply.
type ForEachGenerationApplyConfiguration struct {
List *string `json:"list,omitempty"`
Context []ContextEntryApplyConfiguration `json:"context,omitempty"`
AnyAllConditions *AnyAllConditionsApplyConfiguration `json:"preconditions,omitempty"`
*GeneratePatternsApplyConfiguration `json:"GeneratePatterns,omitempty"`
}
// ForEachGenerationApplyConfiguration constructs an declarative configuration of the ForEachGeneration type for use with
// apply.
func ForEachGeneration() *ForEachGenerationApplyConfiguration {
return &ForEachGenerationApplyConfiguration{}
}
// WithList sets the List 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 List field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithList(value string) *ForEachGenerationApplyConfiguration {
b.List = &value
return b
}
// WithContext adds the given value to the Context field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Context field.
func (b *ForEachGenerationApplyConfiguration) WithContext(values ...*ContextEntryApplyConfiguration) *ForEachGenerationApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithContext")
}
b.Context = append(b.Context, *values[i])
}
return b
}
// WithAnyAllConditions sets the AnyAllConditions 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 AnyAllConditions field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithAnyAllConditions(value *AnyAllConditionsApplyConfiguration) *ForEachGenerationApplyConfiguration {
b.AnyAllConditions = value
return b
}
// WithAPIVersion sets the APIVersion 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 APIVersion field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithAPIVersion(value string) *ForEachGenerationApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.APIVersion = &value
return b
}
// WithKind sets the Kind 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 Kind field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithKind(value string) *ForEachGenerationApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Kind = &value
return b
}
// WithNamespace sets the Namespace 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 Namespace field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithNamespace(value string) *ForEachGenerationApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Namespace = &value
return b
}
// WithName sets the Name 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 Name field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithName(value string) *ForEachGenerationApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Name = &value
return b
}
// WithUID sets the UID 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 UID field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithUID(value types.UID) *ForEachGenerationApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.UID = &value
return b
}
func (b *ForEachGenerationApplyConfiguration) ensureResourceSpecApplyConfigurationExists() {
if b.ResourceSpecApplyConfiguration == nil {
b.ResourceSpecApplyConfiguration = &ResourceSpecApplyConfiguration{}
}
}
// WithRawData sets the RawData 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 RawData field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithRawData(value apiextensionsv1.JSON) *ForEachGenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.RawData = &value
return b
}
// WithClone sets the Clone 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 Clone field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithClone(value *CloneFromApplyConfiguration) *ForEachGenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.Clone = value
return b
}
// WithCloneList sets the CloneList 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 CloneList field is set to the value of the last call.
func (b *ForEachGenerationApplyConfiguration) WithCloneList(value *CloneListApplyConfiguration) *ForEachGenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.CloneList = value
return b
}
func (b *ForEachGenerationApplyConfiguration) ensureGeneratePatternsApplyConfigurationExists() {
if b.GeneratePatternsApplyConfiguration == nil {
b.GeneratePatternsApplyConfiguration = &GeneratePatternsApplyConfiguration{}
}
}

View file

@ -0,0 +1,114 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
types "k8s.io/apimachinery/pkg/types"
)
// GeneratePatternsApplyConfiguration represents an declarative configuration of the GeneratePatterns type for use
// with apply.
type GeneratePatternsApplyConfiguration struct {
*ResourceSpecApplyConfiguration `json:"ResourceSpec,omitempty"`
RawData *apiextensionsv1.JSON `json:"data,omitempty"`
Clone *CloneFromApplyConfiguration `json:"clone,omitempty"`
CloneList *CloneListApplyConfiguration `json:"cloneList,omitempty"`
}
// GeneratePatternsApplyConfiguration constructs an declarative configuration of the GeneratePatterns type for use with
// apply.
func GeneratePatterns() *GeneratePatternsApplyConfiguration {
return &GeneratePatternsApplyConfiguration{}
}
// WithAPIVersion sets the APIVersion 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 APIVersion field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithAPIVersion(value string) *GeneratePatternsApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.APIVersion = &value
return b
}
// WithKind sets the Kind 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 Kind field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithKind(value string) *GeneratePatternsApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Kind = &value
return b
}
// WithNamespace sets the Namespace 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 Namespace field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithNamespace(value string) *GeneratePatternsApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Namespace = &value
return b
}
// WithName sets the Name 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 Name field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithName(value string) *GeneratePatternsApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Name = &value
return b
}
// WithUID sets the UID 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 UID field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithUID(value types.UID) *GeneratePatternsApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.UID = &value
return b
}
func (b *GeneratePatternsApplyConfiguration) ensureResourceSpecApplyConfigurationExists() {
if b.ResourceSpecApplyConfiguration == nil {
b.ResourceSpecApplyConfiguration = &ResourceSpecApplyConfiguration{}
}
}
// WithRawData sets the RawData 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 RawData field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithRawData(value apiextensionsv1.JSON) *GeneratePatternsApplyConfiguration {
b.RawData = &value
return b
}
// WithClone sets the Clone 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 Clone field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithClone(value *CloneFromApplyConfiguration) *GeneratePatternsApplyConfiguration {
b.Clone = value
return b
}
// WithCloneList sets the CloneList 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 CloneList field is set to the value of the last call.
func (b *GeneratePatternsApplyConfiguration) WithCloneList(value *CloneListApplyConfiguration) *GeneratePatternsApplyConfiguration {
b.CloneList = value
return b
}

View file

@ -26,13 +26,11 @@ import (
// GenerationApplyConfiguration represents an declarative configuration of the Generation type for use
// with apply.
type GenerationApplyConfiguration struct {
GenerateExisting *bool `json:"generateExisting,omitempty"`
*ResourceSpecApplyConfiguration `json:"ResourceSpec,omitempty"`
Synchronize *bool `json:"synchronize,omitempty"`
OrphanDownstreamOnPolicyDelete *bool `json:"orphanDownstreamOnPolicyDelete,omitempty"`
RawData *apiextensionsv1.JSON `json:"data,omitempty"`
Clone *CloneFromApplyConfiguration `json:"clone,omitempty"`
CloneList *CloneListApplyConfiguration `json:"cloneList,omitempty"`
GenerateExisting *bool `json:"generateExisting,omitempty"`
Synchronize *bool `json:"synchronize,omitempty"`
OrphanDownstreamOnPolicyDelete *bool `json:"orphanDownstreamOnPolicyDelete,omitempty"`
*GeneratePatternsApplyConfiguration `json:"GeneratePatterns,omitempty"`
ForEachGeneration []ForEachGenerationApplyConfiguration `json:"foreach,omitempty"`
}
// GenerationApplyConfiguration constructs an declarative configuration of the Generation type for use with
@ -49,6 +47,22 @@ func (b *GenerationApplyConfiguration) WithGenerateExisting(value bool) *Generat
return b
}
// WithSynchronize sets the Synchronize 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 Synchronize field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithSynchronize(value bool) *GenerationApplyConfiguration {
b.Synchronize = &value
return b
}
// WithOrphanDownstreamOnPolicyDelete sets the OrphanDownstreamOnPolicyDelete 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 OrphanDownstreamOnPolicyDelete field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithOrphanDownstreamOnPolicyDelete(value bool) *GenerationApplyConfiguration {
b.OrphanDownstreamOnPolicyDelete = &value
return b
}
// WithAPIVersion sets the APIVersion 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 APIVersion field is set to the value of the last call.
@ -100,26 +114,11 @@ func (b *GenerationApplyConfiguration) ensureResourceSpecApplyConfigurationExist
}
}
// WithSynchronize sets the Synchronize 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 Synchronize field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithSynchronize(value bool) *GenerationApplyConfiguration {
b.Synchronize = &value
return b
}
// WithOrphanDownstreamOnPolicyDelete sets the OrphanDownstreamOnPolicyDelete 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 OrphanDownstreamOnPolicyDelete field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithOrphanDownstreamOnPolicyDelete(value bool) *GenerationApplyConfiguration {
b.OrphanDownstreamOnPolicyDelete = &value
return b
}
// WithRawData sets the RawData 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 RawData field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithRawData(value apiextensionsv1.JSON) *GenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.RawData = &value
return b
}
@ -128,6 +127,7 @@ func (b *GenerationApplyConfiguration) WithRawData(value apiextensionsv1.JSON) *
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Clone field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithClone(value *CloneFromApplyConfiguration) *GenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.Clone = value
return b
}
@ -136,6 +136,26 @@ func (b *GenerationApplyConfiguration) WithClone(value *CloneFromApplyConfigurat
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the CloneList field is set to the value of the last call.
func (b *GenerationApplyConfiguration) WithCloneList(value *CloneListApplyConfiguration) *GenerationApplyConfiguration {
b.ensureGeneratePatternsApplyConfigurationExists()
b.CloneList = value
return b
}
func (b *GenerationApplyConfiguration) ensureGeneratePatternsApplyConfigurationExists() {
if b.GeneratePatternsApplyConfiguration == nil {
b.GeneratePatternsApplyConfiguration = &GeneratePatternsApplyConfiguration{}
}
}
// WithForEachGeneration adds the given value to the ForEachGeneration field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the ForEachGeneration field.
func (b *GenerationApplyConfiguration) WithForEachGeneration(values ...*ForEachGenerationApplyConfiguration) *GenerationApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithForEachGeneration")
}
b.ForEachGeneration = append(b.ForEachGeneration, *values[i])
}
return b
}

View file

@ -77,10 +77,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &kyvernov1.DenyApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("DryRunOption"):
return &kyvernov1.DryRunOptionApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ForEachGeneration"):
return &kyvernov1.ForEachGenerationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ForEachMutation"):
return &kyvernov1.ForEachMutationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ForEachValidation"):
return &kyvernov1.ForEachValidationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("GeneratePatterns"):
return &kyvernov1.GeneratePatternsApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("Generation"):
return &kyvernov1.GenerationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("GlobalContextEntryReference"):

View file

@ -7,7 +7,7 @@ import (
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
)
// EngineContextLoader provides a function to load context entries from the various clients initialised with the engine ones
// EngineContextLoader provides a function to load context entries from the various clients initialized with the engine ones
type EngineContextLoader = func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error
// EngineContextLoaderFactory provides an EngineContextLoader given a policy and rule name

View file

@ -36,18 +36,25 @@ func (pc *policyController) handleGenerate(policyKey string, policy kyvernov1.Po
func (pc *policyController) syncDataPolicyChanges(policy kyvernov1.PolicyInterface, deleteDownstream bool) error {
var errs []error
var err error
ur := newGenerateUR(policy)
for _, rule := range policy.GetSpec().Rules {
generate := rule.Generation
if !generate.Synchronize {
continue
}
if generate.GetData() == nil {
continue
if generate.GetData() != nil {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, generate.GeneratePatterns, deleteDownstream, false); err != nil {
errs = append(errs, err)
}
}
var err error
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule, deleteDownstream, false); err != nil {
errs = append(errs, err)
for _, foreach := range generate.ForEachGeneration {
if foreach.GetData() != nil {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, foreach.GeneratePatterns, deleteDownstream, false); err != nil {
errs = append(errs, err)
}
}
}
}
@ -137,6 +144,7 @@ func (pc *policyController) handleGenerateForExisting(policy kyvernov1.PolicyInt
func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.PolicyInterface) error {
var errs []error
var err error
rules := autogen.ComputeRules(policy, "")
ur := newGenerateUR(policy)
for _, r := range rules {
@ -144,14 +152,23 @@ func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.Polic
if !generate.Synchronize {
continue
}
if generate.GetData() == nil {
continue
sync, orphanDownstreamOnPolicyDelete := r.GetSyncAndOrphanDownstream()
if generate.GetData() != nil {
if sync && (generate.GetType() == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, r.Generation.GeneratePatterns, true, true); err != nil {
errs = append(errs, err)
}
}
}
generateType, sync, orphanDownstreamOnPolicyDelete := r.GetTypeAndSyncAndOrphanDownstream()
if sync && (generateType == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete {
var err error
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r, true, true); err != nil {
errs = append(errs, err)
for _, foreach := range generate.ForEachGeneration {
if foreach.GetData() != nil {
if sync && (foreach.GetType() == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, foreach.GeneratePatterns, true, true); err != nil {
errs = append(errs, err)
}
}
}
}
}
@ -177,15 +194,15 @@ func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.Polic
return multierr.Combine(errs...)
}
func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, rule kyvernov1.Rule, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) {
func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, ruleName string, pattern kyvernov1.GeneratePatterns, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) {
labels := map[string]string{
common.GeneratePolicyLabel: policy.GetName(),
common.GeneratePolicyNamespaceLabel: policy.GetNamespace(),
common.GenerateRuleLabel: rule.Name,
common.GenerateRuleLabel: ruleName,
kyverno.LabelAppManagedBy: kyverno.ValueKyvernoApp,
}
downstreams, err := common.FindDownstream(pc.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), labels)
downstreams, err := common.FindDownstream(pc.client, pattern.GetAPIVersion(), pattern.GetKind(), labels)
if err != nil {
return ur, err
}
@ -198,7 +215,7 @@ func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInt
for _, downstream := range downstreams.Items {
labels := downstream.GetLabels()
trigger := generateutils.TriggerFromLabels(labels)
addRuleContext(ur, rule.Name, trigger, deleteDownstream)
addRuleContext(ur, ruleName, trigger, deleteDownstream)
if policyDeletion {
addGeneratedResources(ur, downstream)
}

View file

@ -41,35 +41,6 @@ func NewGenerateFactory(client dclient.Interface, rule kyvernov1.Generation, use
// Validate validates the 'generate' rule
func (g *Generate) Validate(ctx context.Context) (string, error) {
rule := g.rule
if rule.GetData() != nil && rule.Clone != (kyvernov1.CloneFrom{}) {
return "", fmt.Errorf("only one of data or clone can be specified")
}
if rule.Clone != (kyvernov1.CloneFrom{}) && len(rule.CloneList.Kinds) != 0 {
return "", fmt.Errorf("only one of clone or cloneList can be specified")
}
apiVersion, kind, name, namespace := rule.ResourceSpec.GetAPIVersion(), rule.ResourceSpec.GetKind(), rule.ResourceSpec.GetName(), rule.ResourceSpec.GetNamespace()
if len(rule.CloneList.Kinds) == 0 {
if name == "" {
return "name", fmt.Errorf("name cannot be empty")
}
if kind == "" {
return "kind", fmt.Errorf("kind cannot be empty")
}
if apiVersion == "" {
return "apiVersion", fmt.Errorf("apiVersion cannot be empty")
}
} else {
if name != "" {
return "name", fmt.Errorf("with cloneList, generate.name. should not be specified")
}
if kind != "" {
return "kind", fmt.Errorf("with cloneList, generate.kind. should not be specified")
}
}
if rule.CloneList.Selector != nil {
if wildcard.ContainsWildcard(rule.CloneList.Selector.String()) {
return "selector", fmt.Errorf("wildcard characters `*/?` not supported")
@ -89,22 +60,33 @@ func (g *Generate) Validate(ctx context.Context) (string, error) {
// instructions to modify the RBAC for kyverno are mentioned at https://github.com/kyverno/kyverno/blob/master/documentation/installation.md
// - operations required: create/update/delete/get
// If kind and namespace contain variables, then we cannot resolve then so we skip the processing
if len(rule.CloneList.Kinds) != 0 {
for _, kind = range rule.CloneList.Kinds {
gvk, sub := parseCloneKind(kind)
if err := g.canIGenerate(ctx, gvk, namespace, sub); err != nil {
return "", err
if rule.ForEachGeneration != nil {
for _, forEach := range rule.ForEachGeneration {
if err := g.canIGeneratePatterns(ctx, forEach.GeneratePatterns); err != nil {
return "foreach", err
}
}
} else {
k, sub := kubeutils.SplitSubresource(kind)
if err := g.canIGenerate(ctx, strings.Join([]string{apiVersion, k}, "/"), namespace, sub); err != nil {
if err := g.canIGeneratePatterns(ctx, rule.GeneratePatterns); err != nil {
return "", err
}
}
return "", nil
}
func (g *Generate) canIGeneratePatterns(ctx context.Context, generate kyvernov1.GeneratePatterns) error {
if len(generate.CloneList.Kinds) != 0 {
for _, kind := range generate.CloneList.Kinds {
gvk, sub := parseCloneKind(kind)
return g.canIGenerate(ctx, gvk, generate.Namespace, sub)
}
} else {
k, sub := kubeutils.SplitSubresource(generate.Kind)
return g.canIGenerate(ctx, strings.Join([]string{generate.APIVersion, k}, "/"), generate.Namespace, sub)
}
return nil
}
// canIGenerate returns a error if kyverno cannot perform operations
func (g *Generate) canIGenerate(ctx context.Context, gvk, namespace, subresource string) error {
// Skip if there is variable defined

View file

@ -9,37 +9,6 @@ import (
"gotest.tools/assert"
)
func Test_Validate_Generate(t *testing.T) {
rawGenerate := []byte(`
{
"kind": "NetworkPolicy",
"name": "defaultnetworkpolicy",
"data": {
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{}
],
"egress": [
{}
]
}
}
}`)
var genRule kyverno.Generation
err := json.Unmarshal(rawGenerate, &genRule)
assert.NilError(t, err)
checker := NewFakeGenerate(genRule)
_, err = checker.Validate(context.TODO())
t.Log(err)
assert.Assert(t, err != nil)
}
func Test_Validate_Generate_HasAnchors(t *testing.T) {
var err error
rawGenerate := []byte(`

View file

@ -726,6 +726,9 @@ func buildContext(rule *kyvernov1.Rule, background bool, target bool) *enginecon
for _, fe := range rule.Mutation.Targets {
addContextVariables(fe.Context, ctx)
}
for _, fe := range rule.Generation.ForEachGeneration {
addContextVariables(fe.Context, ctx)
}
return ctx
}

View file

@ -0,0 +1,52 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: zk-kafka-address-foreach
spec:
rules:
- match:
any:
- resources:
kinds:
- ConfigMap
name: k-kafka-address
context:
- name: configmapns
variable:
jmesPath: request.object.metadata.namespace
preconditions:
any:
- key: '{{configmapns}}'
operator: Equals
value: 'default'
generate:
generateExisting: false
synchronize: true
foreach:
- list: request.object.data.namespaces | split(@, ',')
context:
- name: ns
variable:
jmesPath: element
preconditions:
any:
- key: '{{ ns }}'
operator: AnyIn
value:
- foreach-ns-1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: my-networkpolicy-{{ elementIndex }}-{{ ns }}
namespace: '{{ ns }}'
data:
metadata:
labels:
request.namespace: '{{ request.object.metadata.name }}'
element.namespace: '{{ ns }}'
element.name: '{{ element }}'
elementIndex: '{{ elementIndex }}'
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: zk-kafka-address-foreach
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,17 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-1
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-2
---
kind: ConfigMap
apiVersion: v1
metadata:
name: default-deny
namespace: default
data:
namespaces: foreach-ns-1,foreach-ns-2

View file

@ -0,0 +1,10 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-networkpolicy-0-foreach-ns-1
namespace: foreach-ns-1
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,10 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-networkpolicy-0-foreach-ns-2
namespace: foreach-ns-2
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,11 @@
## Description
This is a basic creation test for a "generate foreach data" policy with preconditions and context variables. It checks that the basic functionality works whereby installation of the policy causes correct evaluation of the match and preconditions blocks.
## Expected Behavior
If only the `foreach-ns-1` Namespace receives a generated NetworkPolicy, the test passes. If either it does not or `foreach-ns-2` receives a NetworkPolicy, the test fails.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,21 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: cpol-data-sync-create
spec:
steps:
- name: step-01
try:
- apply:
file: 1-1-policy.yaml
- assert:
file: 1-2-policy-assert.yaml
- name: step-02
try:
- apply:
file: 2-1-trigger.yaml
- assert:
file: 2-2-netpol.yaml
- error:
file: 2-3-netpol.yaml

View file

@ -0,0 +1,52 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-data-sync-delete-policy
spec:
rules:
- match:
any:
- resources:
kinds:
- ConfigMap
name: k-kafka-address
context:
- name: configmapns
variable:
jmesPath: request.object.metadata.namespace
preconditions:
any:
- key: '{{configmapns}}'
operator: Equals
value: 'default'
generate:
generateExisting: false
synchronize: true
foreach:
- list: request.object.data.namespaces | split(@, ',')
context:
- name: ns
variable:
jmesPath: element
preconditions:
any:
- key: '{{ ns }}'
operator: AnyIn
value:
- foreach-ns-1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: my-networkpolicy-{{ elementIndex }}-{{ ns }}
namespace: '{{ ns }}'
data:
metadata:
labels:
request.namespace: '{{ request.object.metadata.name }}'
element.namespace: '{{ ns }}'
element.name: '{{ element }}'
elementIndex: '{{ elementIndex }}'
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-data-sync-delete-policy
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-1
---
kind: ConfigMap
apiVersion: v1
metadata:
name: default-deny
namespace: default
data:
namespaces: foreach-ns-1

View file

@ -0,0 +1,10 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-networkpolicy-0-foreach-ns-1
namespace: foreach-ns-1
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,10 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-networkpolicy-0-foreach-ns-2
namespace: foreach-ns-2
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,11 @@
## Description
This test checks the synchronize behavior for a "generate foreach data" policy.
## Expected Behavior
The test passes if the generated NetworkPolicy in `foreach-ns-1` Namespace is deleted upon policy deletion.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,28 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: cpol-data-sync-create
spec:
steps:
- name: step-01
try:
- apply:
file: 1-1-policy.yaml
- assert:
file: 1-2-policy-assert.yaml
- name: step-02
try:
- apply:
file: 2-1-trigger.yaml
- assert:
file: 2-2-netpol.yaml
- name: step-03
try:
- delete:
ref:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: foreach-cpol-data-sync-delete-policy
- error:
file: 2-2-netpol.yaml