1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-13 19:28:55 +00:00

feat: foreach support for clone (#10888)

* chore: add chainsaw tests for foreach clone

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

* fix: update webhooks for foreach generate

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

* chore: rename generatePattern

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

* chore: chainsaw tests for generateExisting

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

* chore: add missing files

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

* chore: add chainsaw tests for foreach clone, sync=true

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

* fix: linter issues

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

* chore: add chainsaw test foreach clonelist, sync=true, delete source

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

* fix: sync deletion for cloneList

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

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
shuting 2024-08-29 19:59:22 +08:00 committed by GitHub
parent d733ea3bb0
commit 2cd462570a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 736 additions and 110 deletions

View file

@ -763,14 +763,14 @@ type Generation struct {
OrphanDownstreamOnPolicyDelete bool `json:"orphanDownstreamOnPolicyDelete,omitempty" yaml:"orphanDownstreamOnPolicyDelete,omitempty"`
// +optional
GeneratePatterns `json:",omitempty" yaml:",omitempty"`
GeneratePattern `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 {
type GeneratePattern struct {
// ResourceSpec contains information to select the resource.
// +kubebuilder:validation:Optional
ResourceSpec `json:",omitempty" yaml:",omitempty"`
@ -808,7 +808,7 @@ type ForEachGeneration struct {
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
GeneratePatterns `json:",omitempty" yaml:",omitempty"`
GeneratePattern `json:",omitempty" yaml:",omitempty"`
}
type CloneList struct {
@ -845,16 +845,16 @@ func (g *Generation) Validate(path *field.Path, namespaced bool, policyNamespace
if g.ForEachGeneration != nil {
for i, foreach := range g.ForEachGeneration {
err := foreach.GeneratePatterns.Validate(path.Child("foreach").Index(i), namespaced, policyNamespace, clusterResources)
err := foreach.GeneratePattern.Validate(path.Child("foreach").Index(i), namespaced, policyNamespace, clusterResources)
errs = append(errs, err...)
}
return errs
} else {
return g.GeneratePatterns.Validate(path, namespaced, policyNamespace, clusterResources)
return g.GeneratePattern.Validate(path, namespaced, policyNamespace, clusterResources)
}
}
func (g *GeneratePatterns) Validate(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (g *GeneratePattern) 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("namespace"), fmt.Sprintf("target resource scope mismatched: %v ", err)))
@ -873,7 +873,7 @@ func (g *GeneratePatterns) Validate(path *field.Path, namespaced bool, policyNam
}
}
newGeneration := GeneratePatterns{
newGeneration := GeneratePattern{
ResourceSpec: ResourceSpec{
Kind: g.ResourceSpec.GetKind(),
APIVersion: g.ResourceSpec.GetAPIVersion(),
@ -901,7 +901,7 @@ func (g *GeneratePatterns) Validate(path *field.Path, namespaced bool, policyNam
return append(errs, g.ValidateCloneList(path, namespaced, policyNamespace, clusterResources)...)
}
func (g *GeneratePatterns) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
func (g *GeneratePattern) ValidateCloneList(path *field.Path, namespaced bool, policyNamespace string, clusterResources sets.Set[string]) (errs field.ErrorList) {
if len(g.CloneList.Kinds) == 0 {
return nil
}
@ -938,7 +938,7 @@ func (g *GeneratePatterns) ValidateCloneList(path *field.Path, namespaced bool,
return errs
}
func (g *GeneratePatterns) GetType() GenerateType {
func (g *GeneratePattern) GetType() GenerateType {
if g.RawData != nil {
return Data
}
@ -946,15 +946,15 @@ func (g *GeneratePatterns) GetType() GenerateType {
return Clone
}
func (g *GeneratePatterns) GetData() apiextensions.JSON {
func (g *GeneratePattern) GetData() apiextensions.JSON {
return FromJSON(g.RawData)
}
func (g *GeneratePatterns) SetData(in apiextensions.JSON) {
func (g *GeneratePattern) SetData(in apiextensions.JSON) {
g.RawData = ToJSON(in)
}
func (g *GeneratePatterns) validateNamespacedTargetsScope(clusterResources sets.Set[string], policyNamespace string) error {
func (g *GeneratePattern) 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())

View file

@ -552,7 +552,7 @@ func (in *ForEachGeneration) DeepCopyInto(out *ForEachGeneration) {
*out = new(AnyAllConditions)
(*in).DeepCopyInto(*out)
}
in.GeneratePatterns.DeepCopyInto(&out.GeneratePatterns)
in.GeneratePattern.DeepCopyInto(&out.GeneratePattern)
return
}
@ -660,7 +660,7 @@ func (in *ForEachValidation) DeepCopy() *ForEachValidation {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GeneratePatterns) DeepCopyInto(out *GeneratePatterns) {
func (in *GeneratePattern) DeepCopyInto(out *GeneratePattern) {
*out = *in
out.ResourceSpec = in.ResourceSpec
if in.RawData != nil {
@ -673,12 +673,12 @@ func (in *GeneratePatterns) DeepCopyInto(out *GeneratePatterns) {
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratePatterns.
func (in *GeneratePatterns) DeepCopy() *GeneratePatterns {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratePattern.
func (in *GeneratePattern) DeepCopy() *GeneratePattern {
if in == nil {
return nil
}
out := new(GeneratePatterns)
out := new(GeneratePattern)
in.DeepCopyInto(out)
return out
}
@ -691,7 +691,7 @@ func (in *Generation) DeepCopyInto(out *Generation) {
*out = new(bool)
**out = **in
}
in.GeneratePatterns.DeepCopyInto(&out.GeneratePatterns)
in.GeneratePattern.DeepCopyInto(&out.GeneratePattern)
if in.ForEachGeneration != nil {
in, out := &in.ForEachGeneration, &out.ForEachGeneration
*out = make([]ForEachGeneration, len(*in))

View file

@ -1201,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.GeneratePatterns">GeneratePatterns</a>)
<a href="#kyverno.io/v1.GeneratePattern">GeneratePattern</a>)
</p>
<p>
<p>CloneFrom provides the location of the source resource used to generate target resources.
@ -1707,10 +1707,10 @@ See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://k
</tr>
<tr>
<td>
<code>GeneratePatterns</code><br/>
<code>GeneratePattern</code><br/>
<em>
<a href="#kyverno.io/v1.GeneratePatterns">
GeneratePatterns
<a href="#kyverno.io/v1.GeneratePattern">
GeneratePattern
</a>
</em>
</td>
@ -2051,7 +2051,7 @@ ForEachValidationWrapper
<p>
<p>ForeachOrder specifies the iteration order in foreach statements.</p>
</p>
<h3 id="kyverno.io/v1.GeneratePatterns">GeneratePatterns
<h3 id="kyverno.io/v1.GeneratePattern">GeneratePattern
</h3>
<p>
(<em>Appears on:</em>
@ -2197,10 +2197,10 @@ Defaults to &ldquo;false&rdquo; if not specified.</p>
</tr>
<tr>
<td>
<code>GeneratePatterns</code><br/>
<code>GeneratePattern</code><br/>
<em>
<a href="#kyverno.io/v1.GeneratePatterns">
GeneratePatterns
<a href="#kyverno.io/v1.GeneratePattern">
GeneratePattern
</a>
</em>
</td>
@ -3699,7 +3699,7 @@ ResourceDescription
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.GeneratePatterns">GeneratePatterns</a>,
<a href="#kyverno.io/v1.GeneratePattern">GeneratePattern</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

@ -2455,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-GeneratePatterns">GeneratePatterns</a>)
<a href="#kyverno-io-v1-GeneratePattern">GeneratePattern</a>)
</p>
@ -3503,7 +3503,7 @@ See: https://kyverno.io/docs/writing-policies/preconditions/</p>
<tr>
<td><code>GeneratePatterns</code>
<td><code>GeneratePattern</code>
<span style="color:blue;"> *</span>
@ -3512,8 +3512,8 @@ See: https://kyverno.io/docs/writing-policies/preconditions/</p>
<a href="#kyverno-io-v1-GeneratePatterns">
<span style="font-family: monospace">GeneratePatterns</span>
<a href="#kyverno-io-v1-GeneratePattern">
<span style="font-family: monospace">GeneratePattern</span>
</a>
@ -4193,7 +4193,7 @@ must be satisfied for the validation rule to succeed.</p>
<H3 id="kyverno-io-v1-GeneratePatterns">GeneratePatterns
<H3 id="kyverno-io-v1-GeneratePattern">GeneratePattern
</H3>
@ -4470,15 +4470,15 @@ Defaults to &quot;false&quot; if not specified.</p>
<tr>
<td><code>GeneratePatterns</code>
<td><code>GeneratePattern</code>
</br>
<a href="#kyverno-io-v1-GeneratePatterns">
<span style="font-family: monospace">GeneratePatterns</span>
<a href="#kyverno-io-v1-GeneratePattern">
<span style="font-family: monospace">GeneratePattern</span>
</a>
@ -7330,7 +7330,7 @@ does not match an empty label set.</p>
<p>
(<em>Appears in:</em>
<a href="#kyverno-io-v1-GeneratePatterns">GeneratePatterns</a>,
<a href="#kyverno-io-v1-GeneratePattern">GeneratePattern</a>,
<a href="#kyverno-io-v1-TargetResourceSpec">TargetResourceSpec</a>)
</p>

View file

@ -56,17 +56,22 @@ func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInter
labels := map[string]string{
common.GeneratePolicyLabel: policy.GetName(),
common.GeneratePolicyNamespaceLabel: policy.GetNamespace(),
common.GenerateRuleLabel: rule.Name,
kyverno.LabelAppManagedBy: kyverno.ValueKyvernoApp,
// common.GenerateRuleLabel: rule.Name,
kyverno.LabelAppManagedBy: kyverno.ValueKyvernoApp,
}
downstreams, err := c.getDownstreams(rule, labels, &ruleContext)
if err != nil {
return fmt.Errorf("failed to fetch downstream resources: %v", err)
}
if len(downstreams) == 0 {
logger.V(4).Info("no downstream resources found by label selectors", "labels", labels)
return nil
}
var errs []error
failedDownstreams := []kyvernov1.ResourceSpec{}
for _, downstream := range downstreams.Items {
for _, downstream := range downstreams {
spec := common.ResourceSpecFromUnstructured(downstream)
if err := c.client.DeleteResource(context.TODO(), downstream.GetAPIVersion(), downstream.GetKind(), downstream.GetNamespace(), downstream.GetName(), false); err != nil && !apierrors.IsNotFound(err) {
failedDownstreams = append(failedDownstreams, spec)
@ -90,7 +95,7 @@ func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInter
return nil
}
func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[string]string, ruleContext *kyvernov2.RuleContext) (*unstructured.UnstructuredList, error) {
func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[string]string, ruleContext *kyvernov2.RuleContext) ([]unstructured.Unstructured, error) {
gv, err := ruleContext.Trigger.GetGroupVersion()
if err != nil {
return nil, err
@ -101,34 +106,43 @@ func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[st
selector[common.GenerateTriggerKindLabel] = ruleContext.Trigger.GetKind()
selector[common.GenerateTriggerGroupLabel] = gv.Group
selector[common.GenerateTriggerVersionLabel] = gv.Version
if rule.Generation.GetKind() != "" {
for _, g := range rule.Generation.ForEachGeneration {
return c.fetch(g.GeneratePattern, selector, ruleContext)
}
return c.fetch(rule.Generation.GeneratePattern, selector, ruleContext)
}
func (c *GenerateController) fetch(generatePattern kyvernov1.GeneratePattern, selector map[string]string, ruleContext *kyvernov2.RuleContext) ([]unstructured.Unstructured, error) {
downstreamResources := []unstructured.Unstructured{}
if generatePattern.GetKind() != "" {
// Fetch downstream resources using trigger uid label
c.log.V(4).Info("fetching downstream resource by the UID", "APIVersion", rule.Generation.GetAPIVersion(), "kind", rule.Generation.GetKind(), "selector", selector)
downstreamList, err := common.FindDownstream(c.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), selector)
c.log.V(4).Info("fetching downstream resource by the UID", "APIVersion", generatePattern.GetAPIVersion(), "kind", generatePattern.GetKind(), "selector", selector)
dsList, err := common.FindDownstream(c.client, generatePattern.GetAPIVersion(), generatePattern.GetKind(), selector)
if err != nil {
return nil, err
}
if len(downstreamList.Items) == 0 {
if len(dsList.Items) == 0 {
// Fetch downstream resources using the trigger name label
delete(selector, common.GenerateTriggerUIDLabel)
selector[common.GenerateTriggerNameLabel] = ruleContext.Trigger.GetName()
c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", rule.Generation.GetAPIVersion(), "kind", rule.Generation.GetKind(), "selector", selector)
dsList, err := common.FindDownstream(c.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), selector)
c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", generatePattern.GetAPIVersion(), "kind", generatePattern.GetKind(), "selector", selector)
dsList, err = common.FindDownstream(c.client, generatePattern.GetAPIVersion(), generatePattern.GetKind(), selector)
if err != nil {
return nil, err
}
downstreamList.Items = append(downstreamList.Items, dsList.Items...)
}
downstreamResources = append(downstreamResources, dsList.Items...)
return downstreamList, err
return downstreamResources, err
}
dsList := &unstructured.UnstructuredList{}
for _, kind := range rule.Generation.CloneList.Kinds {
for _, kind := range generatePattern.CloneList.Kinds {
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
c.log.V(4).Info("fetching downstream cloneList resources by the UID", "APIVersion", apiVersion, "kind", kind, "selector", selector)
dsList, err = common.FindDownstream(c.client, apiVersion, kind, selector)
dsList, err := common.FindDownstream(c.client, apiVersion, kind, selector)
if err != nil {
return nil, err
}
@ -136,12 +150,14 @@ func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[st
if len(dsList.Items) == 0 {
delete(selector, common.GenerateTriggerUIDLabel)
selector[common.GenerateTriggerNameLabel] = ruleContext.Trigger.GetName()
c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", rule.Generation.GetAPIVersion(), "kind", rule.Generation.GetKind(), "selector", selector)
dsList, err = common.FindDownstream(c.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), selector)
c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", generatePattern.GetAPIVersion(), "kind", generatePattern.GetKind(), "selector", selector)
dsList, err = common.FindDownstream(c.client, generatePattern.GetAPIVersion(), generatePattern.GetKind(), selector)
if err != nil {
return nil, err
}
}
downstreamResources = append(downstreamResources, dsList.Items...)
}
return dsList, nil
return downstreamResources, nil
}

View file

@ -13,7 +13,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, severSideApply bool, pattern kyvernov1.GeneratePatterns, client dclient.Interface) generateResponse {
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, severSideApply bool, pattern kyvernov1.GeneratePattern, client dclient.Interface) generateResponse {
source := sourceSpec
if pattern.Clone.Name != "" {
source = kyvernov1.ResourceSpec{
@ -79,7 +79,7 @@ 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, pattern kyvernov1.GeneratePatterns, client dclient.Interface) []generateResponse {
func manageCloneList(log logr.Logger, targetNamespace string, severSideApply bool, pattern kyvernov1.GeneratePattern, client dclient.Interface) []generateResponse {
var responses []generateResponse
sourceNamespace := pattern.CloneList.Namespace
kinds := pattern.CloneList.Kinds

View file

@ -325,7 +325,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
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)
g := newGenerator(c.client, logger, policyContext, policy, rule, rule.Context, rule.GetAnyAllConditions(), policyContext.NewResource(), rule.Generation.GeneratePattern, contextLoader)
genResource, err = g.generate()
}

View file

@ -28,7 +28,7 @@ type generator struct {
anyAllConditions any
trigger unstructured.Unstructured
forEach []kyvernov1.ForEachGeneration
pattern kyvernov1.GeneratePatterns
pattern kyvernov1.GeneratePattern
contextLoader engineapi.EngineContextLoader
}
@ -40,7 +40,7 @@ func newGenerator(client dclient.Interface,
contextEntries []kyvernov1.ContextEntry,
anyAllConditions any,
trigger unstructured.Unstructured,
pattern kyvernov1.GeneratePatterns,
pattern kyvernov1.GeneratePattern,
contextLoader engineapi.EngineContextLoader,
) *generator {
return &generator{
@ -264,7 +264,7 @@ func (g *generator) generateElements(foreach kyvernov1.ForEachGeneration, elemen
foreach.Context,
foreach.AnyAllConditions,
g.trigger,
foreach.GeneratePatterns,
foreach.GeneratePattern,
g.contextLoader).
generate()
if err != nil {

View file

@ -26,10 +26,10 @@ import (
// 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"`
List *string `json:"list,omitempty"`
Context []ContextEntryApplyConfiguration `json:"context,omitempty"`
AnyAllConditions *AnyAllConditionsApplyConfiguration `json:"preconditions,omitempty"`
*GeneratePatternApplyConfiguration `json:"GeneratePattern,omitempty"`
}
// ForEachGenerationApplyConfiguration constructs an declarative configuration of the ForEachGeneration type for use with
@ -122,7 +122,7 @@ func (b *ForEachGenerationApplyConfiguration) ensureResourceSpecApplyConfigurati
// 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.ensureGeneratePatternApplyConfigurationExists()
b.RawData = &value
return b
}
@ -131,7 +131,7 @@ func (b *ForEachGenerationApplyConfiguration) WithRawData(value apiextensionsv1.
// 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.ensureGeneratePatternApplyConfigurationExists()
b.Clone = value
return b
}
@ -140,13 +140,13 @@ func (b *ForEachGenerationApplyConfiguration) WithClone(value *CloneFromApplyCon
// 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.ensureGeneratePatternApplyConfigurationExists()
b.CloneList = value
return b
}
func (b *ForEachGenerationApplyConfiguration) ensureGeneratePatternsApplyConfigurationExists() {
if b.GeneratePatternsApplyConfiguration == nil {
b.GeneratePatternsApplyConfiguration = &GeneratePatternsApplyConfiguration{}
func (b *ForEachGenerationApplyConfiguration) ensureGeneratePatternApplyConfigurationExists() {
if b.GeneratePatternApplyConfiguration == nil {
b.GeneratePatternApplyConfiguration = &GeneratePatternApplyConfiguration{}
}
}

View file

@ -23,25 +23,25 @@ import (
types "k8s.io/apimachinery/pkg/types"
)
// GeneratePatternsApplyConfiguration represents an declarative configuration of the GeneratePatterns type for use
// GeneratePatternApplyConfiguration represents an declarative configuration of the GeneratePattern type for use
// with apply.
type GeneratePatternsApplyConfiguration struct {
type GeneratePatternApplyConfiguration 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
// GeneratePatternApplyConfiguration constructs an declarative configuration of the GeneratePattern type for use with
// apply.
func GeneratePatterns() *GeneratePatternsApplyConfiguration {
return &GeneratePatternsApplyConfiguration{}
func GeneratePattern() *GeneratePatternApplyConfiguration {
return &GeneratePatternApplyConfiguration{}
}
// 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 {
func (b *GeneratePatternApplyConfiguration) WithAPIVersion(value string) *GeneratePatternApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.APIVersion = &value
return b
@ -50,7 +50,7 @@ func (b *GeneratePatternsApplyConfiguration) WithAPIVersion(value string) *Gener
// 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 {
func (b *GeneratePatternApplyConfiguration) WithKind(value string) *GeneratePatternApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Kind = &value
return b
@ -59,7 +59,7 @@ func (b *GeneratePatternsApplyConfiguration) WithKind(value string) *GeneratePat
// 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 {
func (b *GeneratePatternApplyConfiguration) WithNamespace(value string) *GeneratePatternApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Namespace = &value
return b
@ -68,7 +68,7 @@ func (b *GeneratePatternsApplyConfiguration) WithNamespace(value string) *Genera
// 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 {
func (b *GeneratePatternApplyConfiguration) WithName(value string) *GeneratePatternApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.Name = &value
return b
@ -77,13 +77,13 @@ func (b *GeneratePatternsApplyConfiguration) WithName(value string) *GeneratePat
// 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 {
func (b *GeneratePatternApplyConfiguration) WithUID(value types.UID) *GeneratePatternApplyConfiguration {
b.ensureResourceSpecApplyConfigurationExists()
b.UID = &value
return b
}
func (b *GeneratePatternsApplyConfiguration) ensureResourceSpecApplyConfigurationExists() {
func (b *GeneratePatternApplyConfiguration) ensureResourceSpecApplyConfigurationExists() {
if b.ResourceSpecApplyConfiguration == nil {
b.ResourceSpecApplyConfiguration = &ResourceSpecApplyConfiguration{}
}
@ -92,7 +92,7 @@ func (b *GeneratePatternsApplyConfiguration) ensureResourceSpecApplyConfiguratio
// 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 {
func (b *GeneratePatternApplyConfiguration) WithRawData(value apiextensionsv1.JSON) *GeneratePatternApplyConfiguration {
b.RawData = &value
return b
}
@ -100,7 +100,7 @@ func (b *GeneratePatternsApplyConfiguration) WithRawData(value apiextensionsv1.J
// 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 {
func (b *GeneratePatternApplyConfiguration) WithClone(value *CloneFromApplyConfiguration) *GeneratePatternApplyConfiguration {
b.Clone = value
return b
}
@ -108,7 +108,7 @@ func (b *GeneratePatternsApplyConfiguration) WithClone(value *CloneFromApplyConf
// 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 {
func (b *GeneratePatternApplyConfiguration) WithCloneList(value *CloneListApplyConfiguration) *GeneratePatternApplyConfiguration {
b.CloneList = value
return b
}

View file

@ -26,11 +26,11 @@ import (
// GenerationApplyConfiguration represents an declarative configuration of the Generation type for use
// with apply.
type GenerationApplyConfiguration struct {
GenerateExisting *bool `json:"generateExisting,omitempty"`
Synchronize *bool `json:"synchronize,omitempty"`
OrphanDownstreamOnPolicyDelete *bool `json:"orphanDownstreamOnPolicyDelete,omitempty"`
*GeneratePatternsApplyConfiguration `json:"GeneratePatterns,omitempty"`
ForEachGeneration []ForEachGenerationApplyConfiguration `json:"foreach,omitempty"`
GenerateExisting *bool `json:"generateExisting,omitempty"`
Synchronize *bool `json:"synchronize,omitempty"`
OrphanDownstreamOnPolicyDelete *bool `json:"orphanDownstreamOnPolicyDelete,omitempty"`
*GeneratePatternApplyConfiguration `json:"GeneratePattern,omitempty"`
ForEachGeneration []ForEachGenerationApplyConfiguration `json:"foreach,omitempty"`
}
// GenerationApplyConfiguration constructs an declarative configuration of the Generation type for use with
@ -118,7 +118,7 @@ func (b *GenerationApplyConfiguration) ensureResourceSpecApplyConfigurationExist
// 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.ensureGeneratePatternApplyConfigurationExists()
b.RawData = &value
return b
}
@ -127,7 +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.ensureGeneratePatternApplyConfigurationExists()
b.Clone = value
return b
}
@ -136,14 +136,14 @@ 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.ensureGeneratePatternApplyConfigurationExists()
b.CloneList = value
return b
}
func (b *GenerationApplyConfiguration) ensureGeneratePatternsApplyConfigurationExists() {
if b.GeneratePatternsApplyConfiguration == nil {
b.GeneratePatternsApplyConfiguration = &GeneratePatternsApplyConfiguration{}
func (b *GenerationApplyConfiguration) ensureGeneratePatternApplyConfigurationExists() {
if b.GeneratePatternApplyConfiguration == nil {
b.GeneratePatternApplyConfiguration = &GeneratePatternApplyConfiguration{}
}
}

View file

@ -83,8 +83,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &kyvernov1.ForEachMutationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ForEachValidation"):
return &kyvernov1.ForEachValidationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("GeneratePatterns"):
return &kyvernov1.GeneratePatternsApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("GeneratePattern"):
return &kyvernov1.GeneratePatternApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("Generation"):
return &kyvernov1.GenerationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("GlobalContextEntryReference"):

View file

@ -981,11 +981,19 @@ func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface
// matching kinds in generate policies need to be added to both webhook
if rule.HasGenerate() {
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
for _, g := range rule.Generation.ForEachGeneration {
if g.GeneratePattern.ResourceSpec.Kind != "" {
matchedGVK = append(matchedGVK, g.GeneratePattern.ResourceSpec.Kind)
} else {
matchedGVK = append(matchedGVK, g.GeneratePattern.CloneList.Kinds...)
}
}
if rule.Generation.ResourceSpec.Kind != "" {
matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind)
} else {
matchedGVK = append(matchedGVK, rule.Generation.CloneList.Kinds...)
continue
}
matchedGVK = append(matchedGVK, rule.Generation.CloneList.Kinds...)
continue
}
if (updateValidate && rule.HasValidate() || rule.HasVerifyImageChecks()) ||
(updateValidate && rule.HasMutateExisting()) ||

View file

@ -44,14 +44,14 @@ func (pc *policyController) syncDataPolicyChanges(policy kyvernov1.PolicyInterfa
continue
}
if generate.GetData() != nil {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, generate.GeneratePatterns, deleteDownstream, false); err != nil {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, generate.GeneratePattern, 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 {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule.Name, foreach.GeneratePattern, deleteDownstream, false); err != nil {
errs = append(errs, err)
}
}
@ -156,7 +156,7 @@ func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.Polic
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 {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, r.Generation.GeneratePattern, true, true); err != nil {
errs = append(errs, err)
}
}
@ -165,7 +165,7 @@ func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.Polic
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 {
if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r.Name, foreach.GeneratePattern, true, true); err != nil {
errs = append(errs, err)
}
}
@ -194,7 +194,7 @@ func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.Polic
return multierr.Combine(errs...)
}
func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, ruleName string, pattern kyvernov1.GeneratePatterns, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) {
func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, ruleName string, pattern kyvernov1.GeneratePattern, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) {
labels := map[string]string{
common.GeneratePolicyLabel: policy.GetName(),
common.GeneratePolicyNamespaceLabel: policy.GetNamespace(),

View file

@ -59,20 +59,19 @@ func (g *Generate) Validate(ctx context.Context) (warnings []string, path string
// If kind and namespace contain variables, then we cannot resolve then so we skip the processing
if rule.ForEachGeneration != nil {
for _, forEach := range rule.ForEachGeneration {
if err := g.validateAuth(ctx, forEach.GeneratePatterns); err != nil {
if err := g.validateAuth(ctx, forEach.GeneratePattern); err != nil {
return nil, "foreach", err
}
}
} else {
if err := g.validateAuth(ctx, rule.GeneratePatterns); err != nil {
if err := g.validateAuth(ctx, rule.GeneratePattern); err != nil {
return nil, "", err
}
}
return nil, "", nil
}
// validateAuth returns a error if kyverno cannot perform operations
func (g *Generate) validateAuth(ctx context.Context, generate kyvernov1.GeneratePatterns) error {
func (g *Generate) validateAuth(ctx context.Context, generate kyvernov1.GeneratePattern) error {
if len(generate.CloneList.Kinds) != 0 {
for _, kind := range generate.CloneList.Kinds {
gvk, sub := parseCloneKind(kind)

View file

@ -217,7 +217,7 @@ func (h *generationHandler) syncTriggerAction(
for _, rule := range rules {
// fire generation on trigger deletion
if (request.Operation == admissionv1.Delete) && webhookutils.MatchDeleteOperation(rule) {
h.log.V(4).Info("creating the UR to generate downstream on trigger's deletion", "operation", request.Operation, "rule", rule.Name)
h.log.V(4).Info("creating the UR to generate downstream on trigger's deletion", "operation", request.Operation, "rule", rule.Name, "trigger", triggerSpec.String())
ruleCtx := buildRuleContext(rule, triggerSpec, false)
urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx)
continue
@ -225,7 +225,7 @@ func (h *generationHandler) syncTriggerAction(
// delete downstream on trigger deletion
if rule.Generation.Synchronize {
h.log.V(4).Info("creating the UR to delete downstream on trigger's event", "operation", request.Operation, "rule", rule.Name)
h.log.V(4).Info("creating the UR to delete downstream on trigger's event", "operation", request.Operation, "rule", rule.Name, "trigger", triggerSpec.String())
ruleCtx := buildRuleContext(rule, triggerSpec, true)
urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx)
}

View file

@ -4,6 +4,24 @@ features:
omitEvents:
eventTypes: []
admissionController:
extraArgs:
v: 4
rbac:
clusterRole:
extraResources:
- apiGroups:
- '*'
resources:
- secrets
verbs:
- create
- update
- patch
- delete
- get
- list
backgroundController:
extraArgs:
v: 4

View file

@ -0,0 +1,39 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-cpol-clone-list-sync-delete-source-existing-ns
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
labels:
allowedToBeCloned: "true"
location: europe
name: mysecret-1
namespace: foreach-cpol-clone-list-sync-delete-source-existing-ns
type: Opaque
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
labels:
allowedToBeCloned: "false"
location: europe
name: mysecret-2
namespace: foreach-cpol-clone-list-sync-delete-source-existing-ns
type: Opaque
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-cpol-clone-list-sync-delete-source-target-ns-1
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-cpol-clone-list-sync-delete-source-target-ns-2

View file

@ -0,0 +1,46 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-clone-list-sync-delete-source
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: '{{request.object.metadata.namespace}}'
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-cpol-clone-list-sync-delete-source-target-ns-1
apiVersion: v1
kind: Secret
namespace: '{{ ns }}'
cloneList:
kinds:
- v1/Secret
namespace: foreach-cpol-clone-list-sync-delete-source-existing-ns
selector:
matchLabels:
allowedToBeCloned: "true"

View file

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

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-cpol-clone-list-sync-delete-source-trigger-ns
---
kind: ConfigMap
apiVersion: v1
metadata:
name: default-deny
namespace: foreach-cpol-clone-list-sync-delete-source-trigger-ns
data:
namespaces: foreach-cpol-clone-list-sync-delete-source-target-ns-1,foreach-cpol-clone-list-sync-delete-source-target-ns-2

View file

@ -0,0 +1,11 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
labels:
allowedToBeCloned: "true"
location: europe
name: mysecret-1
namespace: foreach-cpol-clone-list-sync-delete-source-target-ns-1
type: Opaque

View file

@ -0,0 +1,11 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
labels:
allowedToBeCloned: "true"
location: europe
name: mysecret-2
namespace: foreach-cpol-clone-list-sync-delete-source-target-ns-2
type: Opaque

View file

@ -0,0 +1,11 @@
## Description
This test ensures the corresponding downstream target is deleted when its trigger is deleted, for a generate foreach cloneList type of policy.
## Expected Behavior
If the downstream resources `mysecret-1` is remained in the namespace `cpol-clone-list-sync-delete-source-trigger-ns-1`, the test fails. If not, the test passes.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,37 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: cpol-clone-list-sync-delete-source
spec:
steps:
- name: step-01
try:
- apply:
file: 1-0-existing.yaml
- apply:
file: 1-1-policy.yaml
- assert:
file: 1-2-policy-assert.yaml
- name: step-02
try:
- apply:
file: 2-1-trigger.yaml
- name: step-03
try:
- assert:
file: 3-1-target-expected.yaml
- error:
file: 3-2-target-none-expected.yaml
- name: step-04
try:
- delete:
ref:
apiVersion: v1
kind: Secret
name: mysecret-1
namespace: foreach-cpol-clone-list-sync-delete-source-existing-ns
- name: step-05
try:
- error:
file: 3-1-target-expected.yaml

View file

@ -0,0 +1,18 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-1
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-2
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: source-secret
namespace: default
type: Opaque

View file

@ -0,0 +1,43 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-clone-sync-create
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: v1
kind: Secret
name: cloned-secret-{{ elementIndex }}-{{ ns }}
namespace: '{{ ns }}'
clone:
namespace: default
name: source-secret

View file

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

View file

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

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-1
namespace: foreach-ns-1
type: Opaque

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-2
namespace: foreach-ns-2
type: Opaque

View file

@ -0,0 +1,11 @@
## Description
This is a basic creation test for a "generate foreach clone" 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 cloned Secret, the test passes. If either it does not or `foreach-ns-2` receives a cloned Secret, the test fails.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,44 @@
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-source.yaml
- name: step-02
try:
- apply:
file: 2-1-policy.yaml
- assert:
file: 2-2-policy-assert.yaml
- name: step-03
try:
- apply:
file: 3-1-trigger.yaml
- name: step-04
try:
- apply:
file: 4-1-cloned-target.yaml
- error:
file: 4-2-no-cloned-target.yaml
- name: step-05
try:
- delete:
ref:
apiVersion: v1
kind: Secret
name: source-secret
namespace: default
- name: step-06
try:
- sleep:
duration: 1s
- name: step-07
try:
- error:
file: 4-1-cloned-target.yaml

View file

@ -0,0 +1,18 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-1
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-2
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: source-secret
namespace: default
type: Opaque

View file

@ -0,0 +1,43 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-clone-sync-create
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: v1
kind: Secret
name: cloned-secret-{{ elementIndex }}-{{ ns }}
namespace: '{{ ns }}'
clone:
namespace: default
name: source-secret

View file

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

View file

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

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-1
namespace: foreach-ns-1
type: Opaque

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-2
namespace: foreach-ns-2
type: Opaque

View file

@ -0,0 +1,11 @@
## Description
This is a basic creation test for a "generate foreach clone" 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 cloned Secret, the test passes. If either it does not or `foreach-ns-2` receives a cloned Secret, the test fails.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,27 @@
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-source.yaml
- name: step-02
try:
- apply:
file: 2-1-policy.yaml
- assert:
file: 2-2-policy-assert.yaml
- name: step-03
try:
- apply:
file: 3-1-trigger.yaml
- name: step-04
try:
- apply:
file: 4-1-cloned-target.yaml
- error:
file: 4-2-no-cloned-target.yaml

View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-1
---
apiVersion: v1
kind: Namespace
metadata:
name: foreach-ns-2
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: source-secret
namespace: default
type: Opaque
---
kind: ConfigMap
apiVersion: v1
metadata:
name: default-deny
namespace: default
data:
namespaces: foreach-ns-1,foreach-ns-2

View file

@ -0,0 +1,43 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: foreach-cpol-clone-sync-create
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: true
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: v1
kind: Secret
name: cloned-secret-{{ elementIndex }}-{{ ns }}
namespace: '{{ ns }}'
clone:
namespace: default
name: source-secret

View file

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

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-1
namespace: foreach-ns-1
type: Opaque

View file

@ -0,0 +1,8 @@
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: cloned-secret-0-foreach-ns-2
namespace: foreach-ns-2
type: Opaque

View file

@ -0,0 +1,11 @@
## Description
This is a basic test for generate existing foreach clone 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 cloned Secret upon policy creation, the test passes. If either it does not or `foreach-ns-2` receives a cloned Secret, the test fails.
## Reference Issue(s)
https://github.com/kyverno/kyverno/issues/3542

View file

@ -0,0 +1,23 @@
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-source.yaml
- name: step-02
try:
- apply:
file: 2-1-policy.yaml
- assert:
file: 2-2-policy-assert.yaml
- name: step-03
try:
- apply:
file: 3-1-cloned-target.yaml
- error:
file: 3-2-no-cloned-target.yaml