2020-08-05 09:11:23 -07:00
|
|
|
package mutate
|
|
|
|
|
|
|
|
import (
|
2022-01-04 17:36:33 -08:00
|
|
|
"fmt"
|
2023-06-07 11:45:11 +02:00
|
|
|
"strings"
|
2022-01-04 17:36:33 -08:00
|
|
|
|
2020-08-05 09:11:23 -07:00
|
|
|
"github.com/go-logr/logr"
|
2022-05-17 13:12:43 +02:00
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
2023-01-30 12:41:09 +01:00
|
|
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
2020-10-07 11:12:31 -07:00
|
|
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
2022-01-04 17:36:33 -08:00
|
|
|
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
|
|
|
|
"github.com/kyverno/kyverno/pkg/engine/variables"
|
2022-04-06 21:04:08 +02:00
|
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
2020-08-05 09:11:23 -07:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
)
|
|
|
|
|
2022-01-04 17:36:33 -08:00
|
|
|
type Response struct {
|
2023-01-30 12:41:09 +01:00
|
|
|
Status engineapi.RuleStatus
|
2022-01-04 17:36:33 -08:00
|
|
|
PatchedResource unstructured.Unstructured
|
|
|
|
Message string
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
|
|
|
|
2023-06-07 11:45:11 +02:00
|
|
|
func NewResponse(status engineapi.RuleStatus, resource unstructured.Unstructured, msg string) *Response {
|
2022-01-04 17:36:33 -08:00
|
|
|
return &Response{
|
|
|
|
Status: status,
|
|
|
|
PatchedResource: resource,
|
|
|
|
Message: msg,
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-28 09:31:12 +02:00
|
|
|
func NewErrorResponse(msg string, err error) *Response {
|
|
|
|
if err != nil {
|
|
|
|
msg = fmt.Sprintf("%s: %v", msg, err)
|
|
|
|
}
|
2023-06-07 11:45:11 +02:00
|
|
|
return NewResponse(engineapi.RuleStatusError, unstructured.Unstructured{}, msg)
|
2023-04-28 09:31:12 +02:00
|
|
|
}
|
|
|
|
|
2022-05-17 13:12:43 +02:00
|
|
|
func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
|
2022-01-04 17:36:33 -08:00
|
|
|
updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
|
|
|
|
if err != nil {
|
2022-12-12 07:20:20 -08:00
|
|
|
return NewErrorResponse("variable substitution failed", err)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2024-09-11 03:32:10 +02:00
|
|
|
mutation := updatedRule.Mutation
|
|
|
|
if mutation == nil {
|
|
|
|
return NewErrorResponse("empty mutate rule", nil)
|
|
|
|
}
|
|
|
|
patcher := NewPatcher(mutation.GetPatchStrategicMerge(), mutation.PatchesJSON6902)
|
2022-01-04 17:36:33 -08:00
|
|
|
if patcher == nil {
|
2023-04-28 09:31:12 +02:00
|
|
|
return NewErrorResponse("empty mutate rule", nil)
|
2021-10-06 18:31:20 -07:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
|
|
|
|
patchedResource := resource.DeepCopy()
|
|
|
|
resourceBytes, err := patchedResource.MarshalJSON()
|
2023-04-28 09:31:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return NewErrorResponse("failed to marshal resource", err)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2023-06-07 11:45:11 +02:00
|
|
|
patchedBytes, err := patcher.Patch(logger, resourceBytes)
|
2023-04-28 09:31:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return NewErrorResponse("failed to patch resource", err)
|
|
|
|
}
|
2023-06-07 11:45:11 +02:00
|
|
|
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
|
|
|
|
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
|
2023-04-28 09:31:12 +02:00
|
|
|
return NewErrorResponse("failed to unmarshal patched resource", err)
|
|
|
|
}
|
2023-12-22 21:07:17 +08:00
|
|
|
if rule.HasMutateExisting() {
|
2023-12-03 23:35:36 -08:00
|
|
|
if err := ctx.SetTargetResource(patchedResource.Object); err != nil {
|
|
|
|
return NewErrorResponse("failed to update patched target resource in the JSON context", err)
|
2023-01-04 19:35:46 +08:00
|
|
|
}
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2020-09-03 08:54:37 -07:00
|
|
|
|
2023-03-30 02:23:29 +08:00
|
|
|
func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engineapi.PolicyContext, resource unstructured.Unstructured, element interface{}, logger logr.Logger) *Response {
|
|
|
|
ctx := policyContext.JSONContext()
|
2022-01-04 17:36:33 -08:00
|
|
|
fe, err := substituteAllInForEach(foreach, ctx, logger)
|
2021-02-24 18:11:55 -08:00
|
|
|
if err != nil {
|
2022-12-12 07:20:20 -08:00
|
|
|
return NewErrorResponse("variable substitution failed", err)
|
2021-02-24 18:11:55 -08:00
|
|
|
}
|
2024-08-09 14:12:20 +03:00
|
|
|
patcher := NewPatcher(fe["patchStrategicMerge"], fe["patchesJson6902"].(string))
|
2022-01-04 17:36:33 -08:00
|
|
|
if patcher == nil {
|
2023-04-28 09:31:12 +02:00
|
|
|
return NewErrorResponse("empty mutate rule", nil)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
|
|
|
|
patchedResource := resource.DeepCopy()
|
|
|
|
resourceBytes, err := patchedResource.MarshalJSON()
|
2023-04-28 09:31:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return NewErrorResponse("failed to marshal resource", err)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2023-06-07 11:45:11 +02:00
|
|
|
patchedBytes, err := patcher.Patch(logger, resourceBytes)
|
2023-04-28 09:31:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return NewErrorResponse("failed to patch resource", err)
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
2023-06-07 11:45:11 +02:00
|
|
|
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
|
|
|
|
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
|
2023-04-28 09:31:12 +02:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
|
2023-04-28 09:31:12 +02:00
|
|
|
return NewErrorResponse("failed to unmarshal patched resource", err)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2023-12-03 23:35:36 -08:00
|
|
|
|
|
|
|
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
|
|
|
|
2024-08-09 14:12:20 +03:00
|
|
|
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (map[string]interface{}, error) {
|
|
|
|
patchesMap := make(map[string]interface{})
|
|
|
|
patchesMap["patchStrategicMerge"] = fe.GetPatchStrategicMerge()
|
|
|
|
patchesMap["patchesJson6902"] = fe.PatchesJSON6902
|
2020-08-05 09:11:23 -07:00
|
|
|
|
2024-08-09 14:12:20 +03:00
|
|
|
subedPatchesMap, err := variables.SubstituteAll(logger, ctx, patchesMap)
|
2022-01-04 17:36:33 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-05 09:11:23 -07:00
|
|
|
|
2024-08-09 14:12:20 +03:00
|
|
|
typedMap, ok := subedPatchesMap.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("failed to convert patched map to map[string]interface{}")
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
2021-10-06 18:31:20 -07:00
|
|
|
|
2024-08-09 14:12:20 +03:00
|
|
|
return typedMap, nil
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|
|
|
|
|
2023-04-28 09:31:12 +02:00
|
|
|
func NewPatcher(strategicMergePatch apiextensions.JSON, jsonPatch string) patch.Patcher {
|
2022-01-04 17:36:33 -08:00
|
|
|
if strategicMergePatch != nil {
|
2023-04-28 09:31:12 +02:00
|
|
|
return patch.NewPatchStrategicMerge(strategicMergePatch)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
|
|
|
if len(jsonPatch) > 0 {
|
2023-04-28 09:31:12 +02:00
|
|
|
return patch.NewPatchesJSON6902(jsonPatch)
|
2022-01-04 17:36:33 -08:00
|
|
|
}
|
|
|
|
return nil
|
2020-08-05 09:11:23 -07:00
|
|
|
}
|