diff --git a/pkg/engine/handlers/mutation/load_targets.go b/pkg/engine/handlers/mutation/load_targets.go index c85f0551fe..0b2507c1e8 100644 --- a/pkg/engine/handlers/mutation/load_targets.go +++ b/pkg/engine/handlers/mutation/load_targets.go @@ -2,6 +2,7 @@ package mutation import ( "context" + "encoding/json" "fmt" "github.com/go-logr/logr" @@ -57,31 +58,32 @@ func loadTargets(ctx context.Context, client engineapi.Client, targets []kyverno } func resolveSpec(i int, target kyvernov1.TargetResourceSpec, ctx engineapi.PolicyContext, logger logr.Logger) (kyvernov1.TargetSelector, error) { - kind, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Kind) + var s kyvernov1.TargetSelector + jsonData, err := json.Marshal(target.TargetSelector) if err != nil { - return kyvernov1.TargetSelector{}, fmt.Errorf("failed to substitute variables in target[%d].Kind %s, value: %v, err: %v", i, target.Kind, kind, err) + return kyvernov1.TargetSelector{}, fmt.Errorf("failed to marshal the mutation target to JSON: %s", err) } - apiversion, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.APIVersion) + + var result map[string]interface{} + if err := json.Unmarshal(jsonData, &result); err != nil { + return kyvernov1.TargetSelector{}, err + } + + selector, err := variables.SubstituteAll(logger, ctx.JSONContext(), result) + if err != nil || selector == nil { + return kyvernov1.TargetSelector{}, fmt.Errorf("failed to substitute variables in target[%d]: %v", i, err) + } + + substitutedJson, err := json.Marshal(selector) if err != nil { - return kyvernov1.TargetSelector{}, fmt.Errorf("failed to substitute variables in target[%d].APIVersion %s, value: %v, err: %v", i, target.APIVersion, apiversion, err) + return kyvernov1.TargetSelector{}, err } - namespace, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Namespace) - if err != nil || namespace == nil { - return kyvernov1.TargetSelector{}, fmt.Errorf("failed to substitute variables in target[%d].Namespace %s, value: %v, err: %v", i, target.Namespace, namespace, err) + + if err := json.Unmarshal(substitutedJson, &s); err != nil { + return kyvernov1.TargetSelector{}, err } - name, err := variables.SubstituteAll(logger, ctx.JSONContext(), target.Name) - if err != nil || name == nil { - return kyvernov1.TargetSelector{}, fmt.Errorf("failed to substitute variables in target[%d].Name %s, value: %v, err: %v", i, target.Name, name, err) - } - return kyvernov1.TargetSelector{ - ResourceSpec: kyvernov1.ResourceSpec{ - APIVersion: apiversion.(string), - Kind: kind.(string), - Namespace: namespace.(string), - Name: name.(string), - }, - Selector: target.Selector, - }, nil + + return s, nil } func getTargets(ctx context.Context, client engineapi.Client, target kyvernov1.ResourceSpec, policyCtx engineapi.PolicyContext, lselector *metav1.LabelSelector) ([]resourceInfo, error) { diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index 7ff1b5ace1..12d3c99064 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -1521,6 +1521,163 @@ func Test_mutate_existing_resources(t *testing.T) { }`)}, targetList: "DeploymentList", }, + { + name: "test-labelselector-variables", + policy: []byte(`{ + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "test-post-mutation" + }, + "spec": { + "rules": [ + { + "name": "mutate-deploy-on-configmap-update", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "ConfigMap" + ], + "names": [ + "dictionary" + ], + "namespaces": [ + "staging" + ] + } + } + ] + }, + "preconditions": { + "any": [ + { + "key": "{{ request.object.data.foo }}", + "operator": "Equals", + "value": "bar" + } + ] + }, + "mutate": { + "targets": [ + { + "apiVersion": "v1", + "kind": "Deployment", + "namespace": "staging", + "selector": { + "matchLabels": { + "parent": "{{ request.object.metadata.name }}" + } + } + } + ], + "patchStrategicMerge": { + "metadata": { + "labels": { + "foo": "bar" + } + } + } + } + } + ] + } + }`), + trigger: []byte(`{ + "apiVersion": "v1", + "data": { + "foo": "bar" + }, + "kind": "ConfigMap", + "metadata": { + "name": "dictionary", + "namespace": "staging" + } + }`), + targets: [][]byte{[]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "example-A", + "namespace": "staging", + "labels": { + "parent": "dictionary", + "app": "nginx" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } + }`)}, + patchedTargets: [][]byte{[]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "example-A", + "namespace": "staging", + "labels": { + "app": "nginx", + "parent": "dictionary", + "foo": "bar" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } + }`)}, + targetList: "DeploymentList", + }, { name: "test-different-trigger-target", policy: []byte(`{