mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
fix: synchronize source resource update to clone list resource (#5317)
* fix: synchronize source resource update to clone list target resource Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com> * add kuttl test to verify the clone list synchronized behavior Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com> * refactor functions parameters Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com> * fix the kuttl test description and behavior README Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com> * Use entire content to compare Signed-off-by: prateekpandey14 <prateek.pandey@nirmata.com>
This commit is contained in:
parent
79d18d1ed6
commit
2b4ff1ef6d
18 changed files with 234 additions and 33 deletions
|
@ -822,6 +822,7 @@ func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface
|
|||
if rule.HasGenerate() {
|
||||
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
|
||||
matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind)
|
||||
matchedGVK = append(matchedGVK, rule.Generation.CloneList.Kinds...)
|
||||
continue
|
||||
}
|
||||
if (updateValidate && rule.HasValidate() || rule.HasImagesValidationChecks()) ||
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
@ -382,49 +383,80 @@ func Validate(policy kyvernov1.PolicyInterface, client dclient.Interface, mock b
|
|||
logging.Error(err, fmt.Sprintf("source resource %s/%s/%s not found.", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
updateSource := true
|
||||
label := obj.GetLabels()
|
||||
|
||||
if len(label) == 0 {
|
||||
label = make(map[string]string)
|
||||
label["generate.kyverno.io/clone-policy-name"] = policy.GetName()
|
||||
} else {
|
||||
if label["generate.kyverno.io/clone-policy-name"] != "" {
|
||||
policyNames := label["generate.kyverno.io/clone-policy-name"]
|
||||
if !strings.Contains(policyNames, policy.GetName()) {
|
||||
policyNames = policyNames + "," + policy.GetName()
|
||||
label["generate.kyverno.io/clone-policy-name"] = policyNames
|
||||
} else {
|
||||
updateSource = false
|
||||
}
|
||||
} else {
|
||||
label["generate.kyverno.io/clone-policy-name"] = policy.GetName()
|
||||
}
|
||||
err = UpdateSourceResource(client, rule.Generation.Kind, rule.Generation.Clone.Namespace, policy.GetName(), obj)
|
||||
if err != nil {
|
||||
logging.Error(err, "failed to update source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
continue
|
||||
}
|
||||
|
||||
if updateSource {
|
||||
logging.V(4).Info("updating existing clone source")
|
||||
obj.SetLabels(label)
|
||||
_, err = client.UpdateResource(obj.GetAPIVersion(), rule.Generation.Kind, rule.Generation.Clone.Namespace, obj, false)
|
||||
logging.V(4).Info("updated source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
}
|
||||
if !mock && len(rule.Generation.CloneList.Kinds) != 0 {
|
||||
for _, kind := range rule.Generation.CloneList.Kinds {
|
||||
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
|
||||
resources, err := client.ListResource(apiVersion, kind, rule.Generation.CloneList.Namespace, rule.Generation.CloneList.Selector)
|
||||
if err != nil {
|
||||
logging.Error(err, "failed to update source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
logging.Error(err, fmt.Sprintf("failed to list resources %s/%s.", kind, rule.Generation.CloneList.Namespace))
|
||||
continue
|
||||
}
|
||||
logging.V(4).Info("updated source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
for _, rName := range resources.Items {
|
||||
obj, err := client.GetResource(apiVersion, kind, rule.Generation.CloneList.Namespace, rName.GetName())
|
||||
if err != nil {
|
||||
logging.Error(err, fmt.Sprintf("source resource %s/%s/%s not found.", kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name))
|
||||
continue
|
||||
}
|
||||
err = UpdateSourceResource(client, kind, rule.Generation.CloneList.Namespace, policy.GetName(), obj)
|
||||
if err != nil {
|
||||
logging.Error(err, "failed to update source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mock && (spec.SchemaValidation == nil || *spec.SchemaValidation) {
|
||||
if err := openApiManager.ValidatePolicyMutation(policy); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func UpdateSourceResource(client dclient.Interface, kind, namespace string, policyName string, obj *unstructured.Unstructured) error {
|
||||
updateSource := true
|
||||
label := obj.GetLabels()
|
||||
|
||||
if len(label) == 0 {
|
||||
label = make(map[string]string)
|
||||
label["generate.kyverno.io/clone-policy-name"] = policyName
|
||||
} else {
|
||||
if label["generate.kyverno.io/clone-policy-name"] != "" {
|
||||
policyNames := label["generate.kyverno.io/clone-policy-name"]
|
||||
if !strings.Contains(policyNames, policyName) {
|
||||
policyNames = policyNames + "," + policyName
|
||||
label["generate.kyverno.io/clone-policy-name"] = policyNames
|
||||
} else {
|
||||
updateSource = false
|
||||
}
|
||||
} else {
|
||||
label["generate.kyverno.io/clone-policy-name"] = policyName
|
||||
}
|
||||
}
|
||||
|
||||
if updateSource {
|
||||
logging.V(4).Info("updating existing clone source labels")
|
||||
obj.SetLabels(label)
|
||||
obj.SetResourceVersion("")
|
||||
|
||||
_, err := client.UpdateResource(obj.GetAPIVersion(), kind, namespace, obj, false)
|
||||
if err != nil {
|
||||
logging.Error(err, "failed to update source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
return err
|
||||
}
|
||||
logging.V(4).Info("updated source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVariables(p kyvernov1.PolicyInterface, backgroundMode bool) error {
|
||||
vars := hasVariables(p)
|
||||
if backgroundMode {
|
||||
|
|
|
@ -91,7 +91,7 @@ func reconcile(ctx context.Context, logger logr.Logger, obj interface{}, r recon
|
|||
}
|
||||
}
|
||||
logger = logger.WithValues("key", k, "namespace", ns, "name", n)
|
||||
logger.Info("reconciling ...")
|
||||
defer logger.Info("done", "duration", time.Since(start).String())
|
||||
logger.V(6).Info("reconciling ...")
|
||||
defer logger.V(6).Info("done", "duration", time.Since(start).String())
|
||||
return r(ctx, logger, k, ns, n)
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ func (h *generationHandler) Handle(
|
|||
policyContext *engine.PolicyContext,
|
||||
admissionRequestTimestamp time.Time,
|
||||
) {
|
||||
h.log.V(6).Info("update request")
|
||||
h.log.V(6).Info("update request for generate policy")
|
||||
|
||||
var engineResponses []*response.EngineResponse
|
||||
if (request.Operation == admissionv1.Create || request.Operation == admissionv1.Update) && len(policies) != 0 {
|
||||
|
|
|
@ -23,8 +23,6 @@ func (h *handlers) createUpdateRequests(logger logr.Logger, request *admissionv1
|
|||
}
|
||||
|
||||
func (h *handlers) handleMutateExisting(logger logr.Logger, request *admissionv1.AdmissionRequest, policies []kyvernov1.PolicyInterface, policyContext *engine.PolicyContext, admissionRequestTimestamp time.Time) {
|
||||
logger.V(4).Info("update request")
|
||||
|
||||
if request.Operation == admissionv1.Delete {
|
||||
policyContext.NewResource = policyContext.OldResource
|
||||
}
|
||||
|
@ -39,6 +37,7 @@ func (h *handlers) handleMutateExisting(logger logr.Logger, request *admissionv1
|
|||
if !policy.GetSpec().IsMutateExisting() {
|
||||
continue
|
||||
}
|
||||
logger.V(4).Info("update request for mutateExisting policy")
|
||||
|
||||
var rules []response.RuleResponse
|
||||
policyContext.Policy = policy
|
||||
|
|
|
@ -14,6 +14,8 @@ func ExcludeKyvernoResources(kind string) bool {
|
|||
return true
|
||||
case "ClusterBackgroundScanReport":
|
||||
return true
|
||||
case "UpdateRequest":
|
||||
return true
|
||||
case "GenerateRequest":
|
||||
return true
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- manifests.yaml
|
||||
- cluster-policy.yaml
|
||||
assert:
|
||||
- cluster-policy-ready.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- ns.yaml
|
||||
assert:
|
||||
- resource-assert.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- ns.yaml
|
||||
assert:
|
||||
- resource-assert.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- update-source.yaml
|
||||
assert:
|
||||
- synchronized-target.yaml
|
|
@ -0,0 +1,11 @@
|
|||
## Description
|
||||
|
||||
This test verifies the synchronize behavior of generated resource, if the selected source resources using a matched label selector `allowedToBeCloned: "true"` gets changed, the update should be synchronized with the target resource as well.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
This test ensures that update of source resource(ConfigMap) match selected using `allowedToBeCloned: "true"` label get synchronized with target resource created by a ClusterPolicy `generate.cloneList` rule, otherwise the test fails.
|
||||
|
||||
## Reference Issue(s)
|
||||
|
||||
#4930
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: sync-secret-with-multi-clone
|
||||
status:
|
||||
conditions:
|
||||
- reason: Succeeded
|
||||
status: "True"
|
||||
type: Ready
|
|
@ -0,0 +1,32 @@
|
|||
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:
|
||||
namespace: "{{request.object.metadata.name}}"
|
||||
synchronize : true
|
||||
cloneList:
|
||||
namespace: default
|
||||
kinds:
|
||||
- v1/Secret
|
||||
- v1/ConfigMap
|
||||
selector:
|
||||
matchLabels:
|
||||
allowedToBeCloned: "true"
|
|
@ -0,0 +1,21 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bootstrap-config
|
||||
namespace: default
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
data:
|
||||
initial_lives: "15"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: image-secret
|
||||
namespace: default
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: admin
|
||||
password: t0p-Secret-super
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: prod
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
password: dDBwLVNlY3JldC1zdXBlcg==
|
||||
username: YWRtaW4=
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
app.kubernetes.io/managed-by: kyverno
|
||||
generate.kyverno.io/clone-policy-name: sync-secret-with-multi-clone
|
||||
kyverno.io/background-gen-rule: sync-secret
|
||||
kyverno.io/generated-by-kind: Namespace
|
||||
kyverno.io/generated-by-name: prod
|
||||
kyverno.io/generated-by-namespace: ""
|
||||
policy.kyverno.io/policy-name: sync-secret-with-multi-clone
|
||||
policy.kyverno.io/synchronize: enable
|
||||
name: image-secret
|
||||
namespace: prod
|
||||
type: kubernetes.io/basic-auth
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
initial_lives: "15"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
app.kubernetes.io/managed-by: kyverno
|
||||
generate.kyverno.io/clone-policy-name: sync-secret-with-multi-clone
|
||||
kyverno.io/background-gen-rule: sync-secret
|
||||
kyverno.io/generated-by-kind: Namespace
|
||||
kyverno.io/generated-by-name: prod
|
||||
kyverno.io/generated-by-namespace: ""
|
||||
policy.kyverno.io/policy-name: sync-secret-with-multi-clone
|
||||
policy.kyverno.io/synchronize: enable
|
||||
name: bootstrap-config
|
||||
namespace: prod
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
initial_lives: "50"
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
app.kubernetes.io/managed-by: kyverno
|
||||
generate.kyverno.io/clone-policy-name: sync-secret-with-multi-clone
|
||||
kyverno.io/background-gen-rule: sync-secret
|
||||
kyverno.io/generated-by-kind: Namespace
|
||||
kyverno.io/generated-by-name: prod
|
||||
kyverno.io/generated-by-namespace: ""
|
||||
policy.kyverno.io/policy-name: sync-secret-with-multi-clone
|
||||
policy.kyverno.io/synchronize: enable
|
||||
name: bootstrap-config
|
||||
namespace: prod
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bootstrap-config
|
||||
namespace: default
|
||||
labels:
|
||||
allowedToBeCloned: "true"
|
||||
data:
|
||||
initial_lives: "50"
|
Loading…
Add table
Reference in a new issue