Skip to content

Commit

Permalink
fix: synchronize source resource update to clone list resource (kyver…
Browse files Browse the repository at this point in the history
…no#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>
  • Loading branch information
prateekpandey14 authored Nov 11, 2022
1 parent 79d18d1 commit 2b4ff1e
Show file tree
Hide file tree
Showing 18 changed files with 234 additions and 33 deletions.
1 change: 1 addition & 0 deletions pkg/controllers/webhook/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()) ||
Expand Down
88 changes: 60 additions & 28 deletions pkg/policy/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/utils/controller/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion pkg/webhooks/resource/generation/generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 1 addition & 2 deletions pkg/webhooks/resource/updaterequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions pkg/webhooks/utils/exclude.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ func ExcludeKyvernoResources(kind string) bool {
return true
case "ClusterBackgroundScanReport":
return true
case "UpdateRequest":
return true
case "GenerateRequest":
return true
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- manifests.yaml
- cluster-policy.yaml
assert:
- cluster-policy-ready.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- ns.yaml
assert:
- resource-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- ns.yaml
assert:
- resource-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- update-source.yaml
assert:
- synchronized-target.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: prod
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: bootstrap-config
namespace: default
labels:
allowedToBeCloned: "true"
data:
initial_lives: "50"

0 comments on commit 2b4ff1e

Please sign in to comment.