mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 07:26:55 +00:00
feat: allow cloning multiple resource from a namespace (#4384)
This commit is contained in:
parent
560cec329e
commit
1cacd0173d
14 changed files with 670 additions and 166 deletions
|
@ -492,6 +492,18 @@ type Generation struct {
|
|||
// resource will be created with default data only.
|
||||
// +optional
|
||||
Clone CloneFrom `json:"clone,omitempty" yaml:"clone,omitempty"`
|
||||
|
||||
// CloneList specifies the list of source resource used to populate each generated resource.
|
||||
// +optional
|
||||
CloneList CloneList `json:"cloneList,omitempty" yaml:"cloneList,omitempty"`
|
||||
}
|
||||
|
||||
type CloneList struct {
|
||||
// Namespace specifies source resource namespace.
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
|
||||
// Kinds is a list of resource kinds.
|
||||
Kinds []string `json:"kinds,omitempty" yaml:"kinds,omitempty"`
|
||||
}
|
||||
|
||||
func (g *Generation) GetData() apiextensions.JSON {
|
||||
|
|
|
@ -251,6 +251,26 @@ func (in *CloneFrom) DeepCopy() *CloneFrom {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CloneList) DeepCopyInto(out *CloneList) {
|
||||
*out = *in
|
||||
if in.Kinds != nil {
|
||||
in, out := &in.Kinds, &out.Kinds
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloneList.
|
||||
func (in *CloneList) DeepCopy() *CloneList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CloneList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterPolicy) DeepCopyInto(out *ClusterPolicy) {
|
||||
*out = *in
|
||||
|
@ -622,6 +642,7 @@ func (in *Generation) DeepCopyInto(out *Generation) {
|
|||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
out.Clone = in.Clone
|
||||
in.CloneList.DeepCopyInto(&out.CloneList)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generation.
|
||||
|
|
|
@ -540,6 +540,18 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: 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.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
@ -2225,6 +2237,18 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: 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.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
@ -4729,6 +4753,18 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: 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.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
@ -6414,6 +6450,18 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: 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.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
|
|
|
@ -840,6 +840,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -3566,6 +3579,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
|
|
@ -841,6 +841,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -3568,6 +3581,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
|
|
@ -857,6 +857,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -3583,6 +3596,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
@ -7406,6 +7433,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -10133,6 +10173,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
|
|
@ -855,6 +855,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -3581,6 +3594,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
@ -7400,6 +7427,19 @@ spec:
|
|||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source resource
|
||||
used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration used
|
||||
to populate each generated resource. At most one of Data
|
||||
|
@ -10127,6 +10167,20 @@ spec:
|
|||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
cloneList:
|
||||
description: CloneList specifies the list of source
|
||||
resource used to populate each generated resource.
|
||||
properties:
|
||||
kinds:
|
||||
description: Kinds is a list of resource kinds.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace specifies source resource
|
||||
namespace.
|
||||
type: string
|
||||
type: object
|
||||
data:
|
||||
description: Data provides the resource declaration
|
||||
used to populate each generated resource. At most
|
||||
|
|
|
@ -1848,6 +1848,20 @@ At most one of Data or Clone can be specified. If neither are provided, the gene
|
|||
resource will be created with default data only.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>cloneList</code><br/>
|
||||
<em>
|
||||
<a href="#kyverno.io/v1.CloneList">
|
||||
CloneList
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>CloneList specifies the list of source resource used to populate each generated resource.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
kyvernoutils "github.com/kyverno/kyverno/pkg/utils"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -271,18 +272,17 @@ func (c *GenerateController) getPolicySpec(ur kyvernov1beta1.UpdateRequest) (kyv
|
|||
return policy, err
|
||||
}
|
||||
return *policyObj, err
|
||||
} else {
|
||||
npolicyObj, err := c.npolicyLister.Policies(pNamespace).Get(pName)
|
||||
if err != nil {
|
||||
return policy, err
|
||||
}
|
||||
return kyvernov1.ClusterPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: pName,
|
||||
},
|
||||
Spec: npolicyObj.Spec,
|
||||
}, nil
|
||||
}
|
||||
npolicyObj, err := c.npolicyLister.Policies(pNamespace).Get(pName)
|
||||
if err != nil {
|
||||
return policy, err
|
||||
}
|
||||
return kyvernov1.ClusterPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: pName,
|
||||
},
|
||||
Spec: npolicyObj.Spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateStatus(statusControl common.StatusControlInterface, ur kyvernov1beta1.UpdateRequest, err error, genResources []kyvernov1.ResourceSpec, precreatedResource bool) error {
|
||||
|
@ -326,7 +326,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
|
|||
|
||||
startTime := time.Now()
|
||||
processExisting = false
|
||||
var genResource kyvernov1.ResourceSpec
|
||||
var genResource []kyvernov1.ResourceSpec
|
||||
|
||||
if len(rule.MatchResources.Kinds) > 0 {
|
||||
if len(rule.MatchResources.Annotations) == 0 && rule.MatchResources.Selector == nil {
|
||||
|
@ -359,7 +359,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
|
|||
return nil, processExisting, err
|
||||
}
|
||||
ruleNameToProcessingTime[rule.Name] = time.Since(startTime)
|
||||
genResources = append(genResources, genResource)
|
||||
genResources = append(genResources, genResource...)
|
||||
}
|
||||
|
||||
if policy.GetSpec().IsGenerateExistingOnPolicyUpdate() {
|
||||
|
@ -392,23 +392,190 @@ func getResourceInfo(object map[string]interface{}) (kind, name, namespace, apiv
|
|||
return
|
||||
}
|
||||
|
||||
func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy kyvernov1.PolicyInterface, ur kyvernov1beta1.UpdateRequest) (kyvernov1.ResourceSpec, error) {
|
||||
var rdata map[string]interface{}
|
||||
func getResourceInfoForDataAndClone(rule kyvernov1.Rule) (kind, name, namespace, apiversion string, err error) {
|
||||
if len(rule.Generation.CloneList.Kinds) == 0 {
|
||||
if kind = rule.Generation.Kind; kind == "" {
|
||||
return "", "", "", "", fmt.Errorf("%s", "kind can not be empty")
|
||||
}
|
||||
if name = rule.Generation.Name; name == "" {
|
||||
return "", "", "", "", fmt.Errorf("%s", "name can not be empty")
|
||||
}
|
||||
}
|
||||
namespace = rule.Generation.Namespace
|
||||
apiversion = rule.Generation.APIVersion
|
||||
return
|
||||
}
|
||||
|
||||
func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy kyvernov1.PolicyInterface, ur kyvernov1beta1.UpdateRequest) ([]kyvernov1.ResourceSpec, error) {
|
||||
rdatas := []GenerateResponse{}
|
||||
var cresp, dresp map[string]interface{}
|
||||
var err error
|
||||
var mode ResourceMode
|
||||
var noGenResource kyvernov1.ResourceSpec
|
||||
genUnst, err := GetUnstrRule(rule.Generation.DeepCopy())
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
var newGenResources []kyvernov1.ResourceSpec
|
||||
|
||||
genKind, genName, genNamespace, genAPIVersion, err := getResourceInfo(genUnst.Object)
|
||||
genKind, genName, genNamespace, genAPIVersion, err := getResourceInfoForDataAndClone(rule)
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
|
||||
logger := log.WithValues("genKind", genKind, "genAPIVersion", genAPIVersion, "genNamespace", genNamespace, "genName", genName)
|
||||
|
||||
if rule.Generation.Clone.Name != "" {
|
||||
cresp, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, policy.GetName(), rule.Generation, client)
|
||||
rdatas = append(rdatas, GenerateResponse{
|
||||
Data: cresp,
|
||||
Action: mode,
|
||||
GenName: genName,
|
||||
GenKind: genKind,
|
||||
GenNamespace: genNamespace,
|
||||
GenAPIVersion: genAPIVersion,
|
||||
Error: err,
|
||||
})
|
||||
} else if len(rule.Generation.CloneList.Kinds) != 0 {
|
||||
rdatas = manageCloneList(logger, genNamespace, policy.GetName(), rule.Generation, client)
|
||||
} else {
|
||||
dresp, mode, err = manageData(logger, genAPIVersion, genKind, genNamespace, genName, rule.Generation.RawData, client)
|
||||
rdatas = append(rdatas, GenerateResponse{
|
||||
Data: dresp,
|
||||
Action: mode,
|
||||
GenName: genName,
|
||||
GenKind: genKind,
|
||||
GenNamespace: genNamespace,
|
||||
GenAPIVersion: genAPIVersion,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
|
||||
for _, rdata := range rdatas {
|
||||
if rdata.Error != nil {
|
||||
logger.Error(err, "failed to generate resource", "mode", rdata.Action)
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
|
||||
logger.V(3).Info("applying generate rule", "mode", rdata.Action)
|
||||
|
||||
// skip processing the response in case of skip action
|
||||
if rdata.Action == Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
if rdata.Data == nil && rdata.Action == Update {
|
||||
logger.V(4).Info("no changes required for generate target resource")
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, nil
|
||||
}
|
||||
|
||||
// build the resource template
|
||||
newResource := &unstructured.Unstructured{}
|
||||
newResource.SetUnstructuredContent(rdata.Data)
|
||||
newResource.SetName(rdata.GenName)
|
||||
newResource.SetNamespace(rdata.GenNamespace)
|
||||
if newResource.GetKind() == "" {
|
||||
newResource.SetKind(rdata.GenKind)
|
||||
}
|
||||
|
||||
newResource.SetAPIVersion(rdata.GenAPIVersion)
|
||||
// manage labels
|
||||
// - app.kubernetes.io/managed-by: kyverno
|
||||
// "kyverno.io/generated-by-kind": kind (trigger resource)
|
||||
// "kyverno.io/generated-by-namespace": namespace (trigger resource)
|
||||
// "kyverno.io/generated-by-name": name (trigger resource)
|
||||
common.ManageLabels(newResource, resource)
|
||||
// Add Synchronize label
|
||||
label := newResource.GetLabels()
|
||||
|
||||
// Add background gen-rule label if generate rule applied on existing resource
|
||||
if policy.GetSpec().IsGenerateExistingOnPolicyUpdate() {
|
||||
label["kyverno.io/background-gen-rule"] = rule.Name
|
||||
}
|
||||
|
||||
label["policy.kyverno.io/policy-name"] = policy.GetName()
|
||||
label["policy.kyverno.io/gr-name"] = ur.Name
|
||||
if rdata.Action == Create {
|
||||
if rule.Generation.Synchronize {
|
||||
label["policy.kyverno.io/synchronize"] = "enable"
|
||||
} else {
|
||||
label["policy.kyverno.io/synchronize"] = "disable"
|
||||
}
|
||||
|
||||
// Reset resource version
|
||||
newResource.SetResourceVersion("")
|
||||
newResource.SetLabels(label)
|
||||
|
||||
// Create the resource
|
||||
_, err = client.CreateResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
|
||||
if err != nil {
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
}
|
||||
logger.V(2).Info("created generate target resource")
|
||||
newGenResources = append(newGenResources, newGenResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName))
|
||||
} else if rdata.Action == Update {
|
||||
generatedObj, err := client.GetResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("generated resource not found name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
|
||||
logger.V(2).Info(fmt.Sprintf("creating generate resource name:name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
|
||||
_, err = client.CreateResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
|
||||
if err != nil {
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
newGenResources = append(newGenResources, newGenResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName))
|
||||
} else {
|
||||
// if synchronize is true - update the label and generated resource with generate policy data
|
||||
if rule.Generation.Synchronize {
|
||||
logger.V(4).Info("updating existing resource")
|
||||
label["policy.kyverno.io/synchronize"] = "enable"
|
||||
newResource.SetLabels(label)
|
||||
|
||||
if rdata.GenAPIVersion == "" {
|
||||
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
|
||||
newResource.SetAPIVersion(generatedResourceAPIVersion)
|
||||
}
|
||||
if rdata.GenNamespace == "" {
|
||||
newResource.SetNamespace("default")
|
||||
}
|
||||
|
||||
if _, err := ValidateResourceWithPattern(logger, generatedObj.Object, newResource.Object); err != nil {
|
||||
_, err = client.UpdateResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update resource")
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentGeneratedResourcelabel := generatedObj.GetLabels()
|
||||
currentSynclabel := currentGeneratedResourcelabel["policy.kyverno.io/synchronize"]
|
||||
|
||||
// update only if the labels mismatches
|
||||
if (!rule.Generation.Synchronize && currentSynclabel == "enable") ||
|
||||
(rule.Generation.Synchronize && currentSynclabel == "disable") {
|
||||
logger.V(4).Info("updating label in existing resource")
|
||||
currentGeneratedResourcelabel["policy.kyverno.io/synchronize"] = "disable"
|
||||
generatedObj.SetLabels(currentGeneratedResourcelabel)
|
||||
|
||||
_, err = client.UpdateResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, generatedObj, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update label in existing resource")
|
||||
newGenResources = append(newGenResources, noGenResource)
|
||||
return newGenResources, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.V(3).Info("updated generate target resource")
|
||||
}
|
||||
}
|
||||
return newGenResources, nil
|
||||
}
|
||||
|
||||
func newGenResource(genAPIVersion, genKind, genNamespace, genName string) kyvernov1.ResourceSpec {
|
||||
// Resource to be generated
|
||||
newGenResource := kyvernov1.ResourceSpec{
|
||||
APIVersion: genAPIVersion,
|
||||
|
@ -416,139 +583,19 @@ func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, r
|
|||
Namespace: genNamespace,
|
||||
Name: genName,
|
||||
}
|
||||
|
||||
genData, _, err := unstructured.NestedMap(genUnst.Object, "data")
|
||||
if err != nil {
|
||||
return noGenResource, fmt.Errorf("failed to read `data`: %v", err.Error())
|
||||
}
|
||||
|
||||
genClone, _, err := unstructured.NestedMap(genUnst.Object, "clone")
|
||||
if err != nil {
|
||||
return noGenResource, fmt.Errorf("failed to read `clone`: %v", err.Error())
|
||||
}
|
||||
|
||||
if len(genClone) != 0 {
|
||||
rdata, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, policy.GetName(), genClone, client)
|
||||
} else {
|
||||
rdata, mode, err = manageData(logger, genAPIVersion, genKind, genNamespace, genName, genData, client)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to generate resource", "mode", mode)
|
||||
return newGenResource, err
|
||||
}
|
||||
|
||||
logger.V(3).Info("applying generate rule", "mode", mode)
|
||||
|
||||
if rdata == nil && mode == Update {
|
||||
logger.V(4).Info("no changes required for generate target resource")
|
||||
return newGenResource, nil
|
||||
}
|
||||
|
||||
// build the resource template
|
||||
newResource := &unstructured.Unstructured{}
|
||||
newResource.SetUnstructuredContent(rdata)
|
||||
newResource.SetName(genName)
|
||||
newResource.SetNamespace(genNamespace)
|
||||
if newResource.GetKind() == "" {
|
||||
newResource.SetKind(genKind)
|
||||
}
|
||||
|
||||
newResource.SetAPIVersion(genAPIVersion)
|
||||
// manage labels
|
||||
// - app.kubernetes.io/managed-by: kyverno
|
||||
// "kyverno.io/generated-by-kind": kind (trigger resource)
|
||||
// "kyverno.io/generated-by-namespace": namespace (trigger resource)
|
||||
// "kyverno.io/generated-by-name": name (trigger resource)
|
||||
common.ManageLabels(newResource, resource)
|
||||
// Add Synchronize label
|
||||
label := newResource.GetLabels()
|
||||
|
||||
// Add background gen-rule label if generate rule applied on existing resource
|
||||
if policy.GetSpec().IsGenerateExistingOnPolicyUpdate() {
|
||||
label["kyverno.io/background-gen-rule"] = rule.Name
|
||||
}
|
||||
|
||||
label["policy.kyverno.io/policy-name"] = policy.GetName()
|
||||
label["policy.kyverno.io/gr-name"] = ur.Name
|
||||
if mode == Create {
|
||||
if rule.Generation.Synchronize {
|
||||
label["policy.kyverno.io/synchronize"] = "enable"
|
||||
} else {
|
||||
label["policy.kyverno.io/synchronize"] = "disable"
|
||||
}
|
||||
|
||||
// Reset resource version
|
||||
newResource.SetResourceVersion("")
|
||||
newResource.SetLabels(label)
|
||||
// Create the resource
|
||||
_, err = client.CreateResource(genAPIVersion, genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
|
||||
logger.V(2).Info("created generate target resource")
|
||||
} else if mode == Update {
|
||||
generatedObj, err := client.GetResource(genAPIVersion, genKind, genNamespace, genName)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("generated resource not found name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
|
||||
logger.V(2).Info(fmt.Sprintf("creating generate resource name:name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
|
||||
_, err = client.CreateResource(genAPIVersion, genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
} else {
|
||||
// if synchronize is true - update the label and generated resource with generate policy data
|
||||
if rule.Generation.Synchronize {
|
||||
logger.V(4).Info("updating existing resource")
|
||||
label["policy.kyverno.io/synchronize"] = "enable"
|
||||
newResource.SetLabels(label)
|
||||
|
||||
if genAPIVersion == "" {
|
||||
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
|
||||
newResource.SetAPIVersion(generatedResourceAPIVersion)
|
||||
}
|
||||
if genNamespace == "" {
|
||||
newResource.SetNamespace("default")
|
||||
}
|
||||
|
||||
if _, err := ValidateResourceWithPattern(logger, generatedObj.Object, newResource.Object); err != nil {
|
||||
_, err = client.UpdateResource(genAPIVersion, genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update resource")
|
||||
return noGenResource, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentGeneratedResourcelabel := generatedObj.GetLabels()
|
||||
currentSynclabel := currentGeneratedResourcelabel["policy.kyverno.io/synchronize"]
|
||||
|
||||
// update only if the labels mismatches
|
||||
if (!rule.Generation.Synchronize && currentSynclabel == "enable") ||
|
||||
(rule.Generation.Synchronize && currentSynclabel == "disable") {
|
||||
logger.V(4).Info("updating label in existing resource")
|
||||
currentGeneratedResourcelabel["policy.kyverno.io/synchronize"] = "disable"
|
||||
generatedObj.SetLabels(currentGeneratedResourcelabel)
|
||||
|
||||
_, err = client.UpdateResource(genAPIVersion, genKind, genNamespace, generatedObj, false)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to update label in existing resource")
|
||||
return noGenResource, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.V(3).Info("updated generate target resource")
|
||||
}
|
||||
|
||||
return newGenResource, nil
|
||||
return newGenResource
|
||||
}
|
||||
|
||||
func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data map[string]interface{}, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
|
||||
func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data interface{}, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
|
||||
resource, err := kyvernoutils.ToMap(data)
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
}
|
||||
|
||||
obj, err := client.GetResource(apiVersion, kind, namespace, name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return data, Create, nil
|
||||
return resource, Create, nil
|
||||
}
|
||||
|
||||
log.Error(err, "failed to get resource")
|
||||
|
@ -562,20 +609,21 @@ func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data
|
|||
}
|
||||
|
||||
updateObj := &unstructured.Unstructured{}
|
||||
updateObj.SetUnstructuredContent(data)
|
||||
updateObj.SetUnstructuredContent(resource)
|
||||
updateObj.SetResourceVersion(obj.GetResourceVersion())
|
||||
return updateObj.UnstructuredContent(), Update, nil
|
||||
}
|
||||
|
||||
func manageClone(log logr.Logger, apiVersion, kind, namespace, name, policy string, clone map[string]interface{}, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
|
||||
rNamespace, _, err := unstructured.NestedString(clone, "namespace")
|
||||
if err != nil {
|
||||
return nil, Skip, fmt.Errorf("failed to find source namespace: %v", err)
|
||||
func manageClone(log logr.Logger, apiVersion, kind, namespace, name, policy string, clone kyvernov1.Generation, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
|
||||
// resource namespace can be nil in case of clusters scope resource
|
||||
rNamespace := clone.Clone.Namespace
|
||||
if rNamespace == "" {
|
||||
log.V(4).Info("resource namespace %s , optional in case of cluster scope resource", rNamespace)
|
||||
}
|
||||
|
||||
rName, _, err := unstructured.NestedString(clone, "name")
|
||||
if err != nil {
|
||||
return nil, Skip, fmt.Errorf("failed to find source name: %v", err)
|
||||
rName := clone.Clone.Name
|
||||
if rName == "" {
|
||||
return nil, Skip, fmt.Errorf("failed to find source name")
|
||||
}
|
||||
|
||||
if rNamespace == namespace && rName == name {
|
||||
|
@ -611,6 +659,110 @@ func manageClone(log logr.Logger, apiVersion, kind, namespace, name, policy stri
|
|||
return obj.UnstructuredContent(), Create, nil
|
||||
}
|
||||
|
||||
func manageCloneList(log logr.Logger, namespace, policy string, clone kyvernov1.Generation, client dclient.Interface) []GenerateResponse {
|
||||
var response []GenerateResponse
|
||||
|
||||
rNamespace := clone.CloneList.Namespace
|
||||
if rNamespace == "" {
|
||||
log.V(4).Info("resource namespace %s , optional in case of cluster scope resource", rNamespace)
|
||||
}
|
||||
|
||||
kinds := clone.CloneList.Kinds
|
||||
if len(kinds) == 0 {
|
||||
response = append(response, GenerateResponse{
|
||||
Data: nil,
|
||||
Action: Skip,
|
||||
Error: fmt.Errorf("failed to find kinds list"),
|
||||
})
|
||||
}
|
||||
|
||||
for _, kind := range kinds {
|
||||
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
|
||||
resources, err := client.ListResource(apiVersion, kind, rNamespace, nil)
|
||||
if err != nil {
|
||||
response = append(response, GenerateResponse{
|
||||
Data: nil,
|
||||
Action: Skip,
|
||||
Error: fmt.Errorf("failed to list resource %s %s/%s. %v", apiVersion, kind, rNamespace, err),
|
||||
})
|
||||
}
|
||||
|
||||
for _, rName := range resources.Items {
|
||||
if rNamespace == namespace {
|
||||
log.V(4).Info("skip resource self-clone")
|
||||
response = append(response, GenerateResponse{
|
||||
Data: nil,
|
||||
Action: Skip,
|
||||
Error: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// check if the resource as reference in clone exists?
|
||||
obj, err := client.GetResource(apiVersion, kind, rNamespace, rName.GetName())
|
||||
if err != nil {
|
||||
log.Error(err, "failed to get resoruce", apiVersion, "apiVersion", kind, "kind", rNamespace, "rNamespace", rName.GetName(), "name")
|
||||
response = append(response, GenerateResponse{
|
||||
Data: nil,
|
||||
Action: Skip,
|
||||
Error: fmt.Errorf("source resource %s %s/%s/%s not found. %v", apiVersion, kind, rNamespace, rName.GetName(), err),
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
// remove ownerReferences when cloning resources to other namespace
|
||||
if rNamespace != namespace && obj.GetOwnerReferences() != nil {
|
||||
obj.SetOwnerReferences(nil)
|
||||
}
|
||||
|
||||
// check if resource to be generated exists
|
||||
newResource, err := client.GetResource(apiVersion, kind, namespace, rName.GetName())
|
||||
if err == nil && newResource != nil {
|
||||
obj.SetUID(newResource.GetUID())
|
||||
obj.SetSelfLink(newResource.GetSelfLink())
|
||||
obj.SetCreationTimestamp(newResource.GetCreationTimestamp())
|
||||
obj.SetManagedFields(newResource.GetManagedFields())
|
||||
obj.SetResourceVersion(newResource.GetResourceVersion())
|
||||
|
||||
if reflect.DeepEqual(obj, newResource) {
|
||||
response = append(response, GenerateResponse{
|
||||
Data: nil,
|
||||
Action: Skip,
|
||||
Error: nil,
|
||||
})
|
||||
} else {
|
||||
response = append(response, GenerateResponse{
|
||||
Data: obj.UnstructuredContent(),
|
||||
Action: Update,
|
||||
GenKind: kind,
|
||||
GenName: rName.GetName(),
|
||||
GenNamespace: namespace,
|
||||
GenAPIVersion: apiVersion,
|
||||
Error: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
// create the resource based on the reference clone
|
||||
response = append(response, GenerateResponse{
|
||||
Data: obj.UnstructuredContent(),
|
||||
Action: Create,
|
||||
GenKind: kind,
|
||||
GenName: rName.GetName(),
|
||||
GenNamespace: namespace,
|
||||
GenAPIVersion: apiVersion,
|
||||
Error: nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
type GenerateResponse struct {
|
||||
Data map[string]interface{}
|
||||
Action ResourceMode
|
||||
GenKind, GenName, GenNamespace, GenAPIVersion string
|
||||
Error error
|
||||
}
|
||||
|
||||
// ResourceMode defines the mode for generated resource
|
||||
type ResourceMode string
|
||||
|
||||
|
|
|
@ -40,18 +40,24 @@ func (g *Generate) Validate() (string, error) {
|
|||
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")
|
||||
}
|
||||
|
||||
kind, name, namespace := rule.Kind, rule.Name, rule.Namespace
|
||||
|
||||
if name == "" {
|
||||
return "name", fmt.Errorf("name cannot be empty")
|
||||
}
|
||||
if kind == "" {
|
||||
return "kind", fmt.Errorf("kind cannot be empty")
|
||||
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")
|
||||
}
|
||||
}
|
||||
// Can I generate resource
|
||||
|
||||
if !reflect.DeepEqual(rule.Clone, kyvernov1.CloneFrom{}) {
|
||||
if path, err := g.validateClone(rule.Clone, kind); err != nil {
|
||||
if path, err := g.validateClone(rule.Clone, rule.CloneList, kind); err != nil {
|
||||
return fmt.Sprintf("clone.%s", path), err
|
||||
}
|
||||
}
|
||||
|
@ -74,9 +80,11 @@ func (g *Generate) Validate() (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func (g *Generate) validateClone(c kyvernov1.CloneFrom, kind string) (string, error) {
|
||||
if c.Name == "" {
|
||||
return "name", fmt.Errorf("name cannot be empty")
|
||||
func (g *Generate) validateClone(c kyvernov1.CloneFrom, cl kyvernov1.CloneList, kind string) (string, error) {
|
||||
if len(cl.Kinds) == 0 {
|
||||
if c.Name == "" {
|
||||
return "name", fmt.Errorf("name cannot be empty")
|
||||
}
|
||||
}
|
||||
|
||||
namespace := c.Namespace
|
||||
|
|
|
@ -31,6 +31,9 @@ var (
|
|||
// ConfigMap GVR
|
||||
cmGVR = e2e.GetGVR("", "v1", "configmaps")
|
||||
|
||||
// Secret GVR
|
||||
secretGVR = e2e.GetGVR("", "v1", "secrets")
|
||||
|
||||
// NetworkPolicy GVR
|
||||
npGVR = e2e.GetGVR("networking.k8s.io", "v1", "networkpolicies")
|
||||
)
|
||||
|
@ -278,3 +281,37 @@ var generatePolicyDeletionforCloneTests = []testCase{
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
var generatePolicyMultipleCloneTests = []testCase{
|
||||
{
|
||||
TestName: "test-multiple-clone-resources",
|
||||
ClusterPolicy: clusterPolicy(genMultipleClonePolicyYaml),
|
||||
SourceResources: resources(
|
||||
configMap("default", cloneSourceResource),
|
||||
secret("default", cloneSecretSourceResource),
|
||||
),
|
||||
TriggerResource: namespace(namespaceYaml),
|
||||
ExpectedResources: expectations(
|
||||
expectation(idConfigMap("test", "game-demo")),
|
||||
expectation(idSecret("test", "secret-basic-auth")),
|
||||
),
|
||||
Steps: []testCaseStep{
|
||||
stepExpectResource(cmGVR, "test", "game-demo"),
|
||||
stepBy("verify generated resource data in configMap"),
|
||||
stepExpectResource(cmGVR, "test", "game-demo", func(resource *unstructured.Unstructured) {
|
||||
element, _, err := unstructured.NestedMap(resource.UnstructuredContent(), "data")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(element["initial_lives"]).To(Equal("2"))
|
||||
}),
|
||||
|
||||
stepBy("verify generated resource data in secret"),
|
||||
stepExpectResource(secretGVR, "test", "secret-basic-auth"),
|
||||
|
||||
stepBy("deleted source -> generated resource not deleted"),
|
||||
stepDeleteResource(cmGVR, "default", "game-demo"),
|
||||
stepDeleteResource(secretGVR, "default", "secret-basic-auth"),
|
||||
stepExpectResource(cmGVR, "test", "game-demo"),
|
||||
stepExpectResource(secretGVR, "test", "secret-basic-auth"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -110,3 +110,7 @@ func Test_Source_Resource_Update_Replication(t *testing.T) {
|
|||
func Test_Generate_Policy_Deletion_for_Clone(t *testing.T) {
|
||||
runTestCases(t, generatePolicyDeletionforCloneTests...)
|
||||
}
|
||||
|
||||
func Test_Generate_Multiple_Clone(t *testing.T) {
|
||||
runTestCases(t, generatePolicyMultipleCloneTests...)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ func resources(resources ...resource) []resource { return resources }
|
|||
func role(ns string, raw []byte) resource { return namespaced(rGVR, ns, raw) }
|
||||
func roleBinding(ns string, raw []byte) resource { return namespaced(rbGVR, ns, raw) }
|
||||
func configMap(ns string, raw []byte) resource { return namespaced(cmGVR, ns, raw) }
|
||||
func secret(ns string, raw []byte) resource { return namespaced(secretGVR, ns, raw) }
|
||||
func clusterPolicy(raw []byte) resource { return clustered(clPolGVR, raw) }
|
||||
func clusterRole(raw []byte) resource { return clustered(crGVR, raw) }
|
||||
func clusterRoleBinding(raw []byte) resource { return clustered(crbGVR, raw) }
|
||||
|
@ -47,6 +48,7 @@ func id(gvr schema.GroupVersionResource, ns string, name string) _id {
|
|||
func idRole(ns, name string) _id { return id(rGVR, ns, name) }
|
||||
func idRoleBinding(ns, name string) _id { return id(rbGVR, ns, name) }
|
||||
func idConfigMap(ns, name string) _id { return id(cmGVR, ns, name) }
|
||||
func idSecret(ns, name string) _id { return id(secretGVR, ns, name) }
|
||||
func idNetworkPolicy(ns, name string) _id { return id(npGVR, ns, name) }
|
||||
func idClusterRole(name string) _id { return id(crGVR, "", name) }
|
||||
func idClusterRoleBinding(name string) _id { return id(crbGVR, "", name) }
|
||||
|
|
|
@ -377,6 +377,17 @@ data:
|
|||
initial_lives: "2"
|
||||
`)
|
||||
|
||||
var cloneSecretSourceResource = []byte(`
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-basic-auth
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: admin
|
||||
password: t0p-Secret
|
||||
`)
|
||||
|
||||
var genCloneConfigMapPolicyYaml = []byte(`
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
|
@ -405,3 +416,36 @@ spec:
|
|||
namespace: default
|
||||
name: game-demo
|
||||
`)
|
||||
|
||||
var genMultipleClonePolicyYaml = []byte(`
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: sync-secret-with-multi-clone
|
||||
spec:
|
||||
generateExistingOnPolicyUpdate: true
|
||||
rules:
|
||||
- name: sync-secret
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Namespace
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- default
|
||||
- kube-public
|
||||
- kyverno
|
||||
generate:
|
||||
kind: Secret
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize : true
|
||||
cloneList:
|
||||
namespace: default
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- v1/ConfigMap
|
||||
`)
|
||||
|
|
Loading…
Add table
Reference in a new issue