mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-06 16:06:56 +00:00
* chore: add chainsaw tests for foreach clone Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: update webhooks for foreach generate Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: rename generatePattern Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: chainsaw tests for generateExisting Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add missing files Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add chainsaw tests for foreach clone, sync=true Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: linter issues Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add chainsaw test foreach clonelist, sync=true, delete source Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: sync deletion for cloneList Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
349 lines
13 KiB
Go
349 lines
13 KiB
Go
package generation
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/go-logr/logr"
|
|
"github.com/kyverno/kyverno/api/kyverno"
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
|
"github.com/kyverno/kyverno/pkg/background/common"
|
|
generateutils "github.com/kyverno/kyverno/pkg/background/generate"
|
|
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
|
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
|
|
kyvernov2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2"
|
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
|
"github.com/kyverno/kyverno/pkg/engine"
|
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
|
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
|
"github.com/kyverno/kyverno/pkg/event"
|
|
"github.com/kyverno/kyverno/pkg/metrics"
|
|
utils "github.com/kyverno/kyverno/pkg/utils/engine"
|
|
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
|
|
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
|
)
|
|
|
|
type GenerationHandler interface {
|
|
Handle(context.Context, admissionv1.AdmissionRequest, []kyvernov1.PolicyInterface, *engine.PolicyContext)
|
|
}
|
|
|
|
func NewGenerationHandler(
|
|
log logr.Logger,
|
|
engine engineapi.Engine,
|
|
client dclient.Interface,
|
|
kyvernoClient versioned.Interface,
|
|
nsLister corev1listers.NamespaceLister,
|
|
urLister kyvernov2listers.UpdateRequestNamespaceLister,
|
|
cpolLister kyvernov1listers.ClusterPolicyLister,
|
|
polLister kyvernov1listers.PolicyLister,
|
|
urGenerator webhookgenerate.Generator,
|
|
eventGen event.Interface,
|
|
metrics metrics.MetricsConfigManager,
|
|
backgroundServiceAccountName string,
|
|
reportsServiceAccountName string,
|
|
) GenerationHandler {
|
|
return &generationHandler{
|
|
log: log,
|
|
engine: engine,
|
|
client: client,
|
|
kyvernoClient: kyvernoClient,
|
|
nsLister: nsLister,
|
|
urLister: urLister,
|
|
cpolLister: cpolLister,
|
|
polLister: polLister,
|
|
urGenerator: urGenerator,
|
|
eventGen: eventGen,
|
|
metrics: metrics,
|
|
backgroundServiceAccountName: backgroundServiceAccountName,
|
|
reportsServiceAccountName: reportsServiceAccountName,
|
|
}
|
|
}
|
|
|
|
type generationHandler struct {
|
|
log logr.Logger
|
|
engine engineapi.Engine
|
|
client dclient.Interface
|
|
kyvernoClient versioned.Interface
|
|
nsLister corev1listers.NamespaceLister
|
|
urLister kyvernov2listers.UpdateRequestNamespaceLister
|
|
cpolLister kyvernov1listers.ClusterPolicyLister
|
|
polLister kyvernov1listers.PolicyLister
|
|
urGenerator webhookgenerate.Generator
|
|
eventGen event.Interface
|
|
metrics metrics.MetricsConfigManager
|
|
backgroundServiceAccountName string
|
|
reportsServiceAccountName string
|
|
}
|
|
|
|
func (h *generationHandler) Handle(
|
|
ctx context.Context,
|
|
request admissionv1.AdmissionRequest,
|
|
policies []kyvernov1.PolicyInterface,
|
|
policyContext *engine.PolicyContext,
|
|
) {
|
|
h.log.V(6).Info("handle admission request for generate")
|
|
if len(policies) != 0 {
|
|
h.handleTrigger(ctx, request, policies, policyContext)
|
|
}
|
|
|
|
if h.backgroundServiceAccountName == policyContext.AdmissionInfo().AdmissionUserInfo.Username {
|
|
return
|
|
}
|
|
h.handleNonTrigger(ctx, policyContext)
|
|
}
|
|
|
|
func getAppliedRules(policy kyvernov1.PolicyInterface, applied []engineapi.RuleResponse) []kyvernov1.Rule {
|
|
rules := []kyvernov1.Rule{}
|
|
for _, rule := range policy.GetSpec().Rules {
|
|
if !rule.HasGenerate() {
|
|
continue
|
|
}
|
|
for _, applied := range applied {
|
|
if applied.Name() == rule.Name && applied.RuleType() == engineapi.Generation {
|
|
rules = append(rules, rule)
|
|
}
|
|
}
|
|
}
|
|
return rules
|
|
}
|
|
|
|
func (h *generationHandler) handleTrigger(
|
|
ctx context.Context,
|
|
request admissionv1.AdmissionRequest,
|
|
policies []kyvernov1.PolicyInterface,
|
|
policyContext *engine.PolicyContext,
|
|
) {
|
|
h.log.V(4).Info("handle trigger resource operation for generate", "policies", len(policies))
|
|
for _, policy := range policies {
|
|
var appliedRules, failedRules []engineapi.RuleResponse
|
|
policyContext := policyContext.WithPolicy(policy)
|
|
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
|
|
policyContext = policyContext.WithNamespaceLabels(utils.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, h.log))
|
|
}
|
|
engineResponse := h.engine.ApplyBackgroundChecks(ctx, policyContext)
|
|
for _, rule := range engineResponse.PolicyResponse.Rules {
|
|
if rule.Status() == engineapi.RuleStatusPass {
|
|
appliedRules = append(appliedRules, rule)
|
|
} else if rule.Status() == engineapi.RuleStatusFail {
|
|
failedRules = append(failedRules, rule)
|
|
}
|
|
}
|
|
|
|
h.applyGeneration(ctx, request, policy, appliedRules, policyContext)
|
|
h.syncTriggerAction(ctx, request, policy, failedRules, policyContext)
|
|
}
|
|
}
|
|
|
|
func (h *generationHandler) handleNonTrigger(
|
|
ctx context.Context,
|
|
policyContext *engine.PolicyContext,
|
|
) {
|
|
resource := policyContext.OldResource()
|
|
labels := resource.GetLabels()
|
|
if _, ok := labels[common.GenerateTypeCloneSourceLabel]; ok || labels[common.GeneratePolicyLabel] != "" {
|
|
h.log.V(4).Info("handle non-trigger resource operation for generate")
|
|
if err := h.processRequest(ctx, policyContext); err != nil {
|
|
h.log.Error(err, "failed to create the UR on non-trigger admission request")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *generationHandler) applyGeneration(
|
|
ctx context.Context,
|
|
request admissionv1.AdmissionRequest,
|
|
policy kyvernov1.PolicyInterface,
|
|
appliedRules []engineapi.RuleResponse,
|
|
policyContext *engine.PolicyContext,
|
|
) {
|
|
if len(appliedRules) == 0 {
|
|
return
|
|
}
|
|
|
|
pKey := common.PolicyKey(policy.GetNamespace(), policy.GetName())
|
|
trigger := policyContext.NewResource()
|
|
triggerSpec := kyvernov1.ResourceSpec{
|
|
APIVersion: trigger.GetAPIVersion(),
|
|
Kind: trigger.GetKind(),
|
|
Namespace: trigger.GetNamespace(),
|
|
Name: trigger.GetName(),
|
|
UID: trigger.GetUID(),
|
|
}
|
|
|
|
rules := getAppliedRules(policy, appliedRules)
|
|
h.log.V(4).Info("creating the UR to generate downstream on trigger's operation", "operation", request.Operation, "policy", pKey)
|
|
urSpec := buildURSpecNew(kyvernov2.Generate, pKey, rules, triggerSpec, false)
|
|
urSpec.Context = buildURContext(request, policyContext)
|
|
if err := h.urGenerator.Apply(ctx, urSpec); err != nil {
|
|
h.log.Error(err, "failed to create the UR to create downstream on trigger's operation", "operation", request.Operation, "policy", pKey)
|
|
e := event.NewFailedEvent(err, pKey, "", event.GeneratePolicyController,
|
|
kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()})
|
|
h.eventGen.Add(e)
|
|
}
|
|
}
|
|
|
|
// handleFailedRules sync changes of the trigger to the downstream
|
|
// it can be 1. trigger deletion; 2. trigger no longer matches, when a rule fails or is skipped
|
|
func (h *generationHandler) syncTriggerAction(
|
|
ctx context.Context,
|
|
request admissionv1.AdmissionRequest,
|
|
policy kyvernov1.PolicyInterface,
|
|
failedRules []engineapi.RuleResponse,
|
|
policyContext *engine.PolicyContext,
|
|
) {
|
|
if len(failedRules) == 0 {
|
|
return
|
|
}
|
|
|
|
pKey := common.PolicyKey(policy.GetNamespace(), policy.GetName())
|
|
trigger := policyContext.OldResource()
|
|
triggerSpec := kyvernov1.ResourceSpec{
|
|
APIVersion: trigger.GetAPIVersion(),
|
|
Kind: trigger.GetKind(),
|
|
Namespace: trigger.GetNamespace(),
|
|
Name: trigger.GetName(),
|
|
UID: trigger.GetUID(),
|
|
}
|
|
|
|
rules := getAppliedRules(policy, failedRules)
|
|
urSpec := kyvernov2.UpdateRequestSpec{
|
|
Type: kyvernov2.Generate,
|
|
Policy: pKey,
|
|
RuleContext: make([]kyvernov2.RuleContext, 0),
|
|
Context: buildURContext(request, policyContext),
|
|
}
|
|
for _, rule := range rules {
|
|
// fire generation on trigger deletion
|
|
if (request.Operation == admissionv1.Delete) && webhookutils.MatchDeleteOperation(rule) {
|
|
h.log.V(4).Info("creating the UR to generate downstream on trigger's deletion", "operation", request.Operation, "rule", rule.Name, "trigger", triggerSpec.String())
|
|
ruleCtx := buildRuleContext(rule, triggerSpec, false)
|
|
urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx)
|
|
continue
|
|
}
|
|
|
|
// delete downstream on trigger deletion
|
|
if rule.Generation.Synchronize {
|
|
h.log.V(4).Info("creating the UR to delete downstream on trigger's event", "operation", request.Operation, "rule", rule.Name, "trigger", triggerSpec.String())
|
|
ruleCtx := buildRuleContext(rule, triggerSpec, true)
|
|
urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx)
|
|
}
|
|
}
|
|
|
|
if err := h.urGenerator.Apply(ctx, urSpec); err != nil {
|
|
h.log.Error(err, "failed to create the UR on trigger's event", "operation", request.Operation, "policy", pKey)
|
|
e := event.NewFailedEvent(err, pKey, "", event.GeneratePolicyController,
|
|
kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()})
|
|
h.eventGen.Add(e)
|
|
}
|
|
}
|
|
|
|
// processRequest determine if it needs to re-apply the generate rule to the source or the target changes
|
|
func (h *generationHandler) processRequest(ctx context.Context, policyContext *engine.PolicyContext) (err error) {
|
|
var policy kyvernov1.PolicyInterface
|
|
var labelsList []map[string]string
|
|
var deleteDownstream bool
|
|
|
|
new := policyContext.NewResource()
|
|
old := policyContext.OldResource()
|
|
labels := old.GetLabels()
|
|
managedBy := labels[kyverno.LabelAppManagedBy] == kyverno.ValueKyvernoApp
|
|
|
|
// clone source changes
|
|
if !managedBy {
|
|
if new.Object == nil {
|
|
// clone source deletion
|
|
deleteDownstream = true
|
|
}
|
|
// fetch targets that have the source name label
|
|
targetSelector := map[string]string{
|
|
common.GenerateSourceGroupLabel: old.GroupVersionKind().Group,
|
|
common.GenerateSourceVersionLabel: old.GroupVersionKind().Version,
|
|
common.GenerateSourceKindLabel: old.GetKind(),
|
|
common.GenerateSourceNSLabel: old.GetNamespace(),
|
|
common.GenerateSourceNameLabel: old.GetName(),
|
|
}
|
|
targets, err := common.FindDownstream(h.client, old.GetAPIVersion(), old.GetKind(), targetSelector)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list targets resources: %v", err)
|
|
}
|
|
|
|
for i := range targets.Items {
|
|
l := targets.Items[i].GetLabels()
|
|
labelsList = append(labelsList, l)
|
|
}
|
|
|
|
// fetch targets that have the source UID label
|
|
targetSelector = map[string]string{
|
|
common.GenerateSourceGroupLabel: old.GroupVersionKind().Group,
|
|
common.GenerateSourceVersionLabel: old.GroupVersionKind().Version,
|
|
common.GenerateSourceKindLabel: old.GetKind(),
|
|
common.GenerateSourceNSLabel: old.GetNamespace(),
|
|
common.GenerateSourceUIDLabel: string(old.GetUID()),
|
|
}
|
|
targets, err = common.FindDownstream(h.client, old.GetAPIVersion(), old.GetKind(), targetSelector)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list targets resources: %v", err)
|
|
}
|
|
|
|
for i := range targets.Items {
|
|
l := targets.Items[i].GetLabels()
|
|
labelsList = append(labelsList, l)
|
|
}
|
|
} else {
|
|
labelsList = append(labelsList, labels)
|
|
}
|
|
|
|
for _, labels := range labelsList {
|
|
pName := labels[common.GeneratePolicyLabel]
|
|
pNamespace := labels[common.GeneratePolicyNamespaceLabel]
|
|
pRuleName := labels[common.GenerateRuleLabel]
|
|
|
|
if pNamespace != "" {
|
|
policy, err = h.polLister.Policies(pNamespace).Get(pName)
|
|
} else {
|
|
policy, err = h.cpolLister.Get(pName)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pKey := common.PolicyKey(pNamespace, pName)
|
|
urSpec := kyvernov2.UpdateRequestSpec{
|
|
Type: kyvernov2.Generate,
|
|
Policy: pKey,
|
|
RuleContext: make([]kyvernov2.RuleContext, 0),
|
|
}
|
|
|
|
for _, rule := range policy.GetSpec().Rules {
|
|
if rule.Name == pRuleName && rule.Generation.Synchronize {
|
|
gvk, subresource := policyContext.ResourceKind()
|
|
if err := engineutils.MatchesResourceDescription(
|
|
old,
|
|
rule,
|
|
policyContext.AdmissionInfo(),
|
|
policyContext.NamespaceLabels(),
|
|
policy.GetNamespace(),
|
|
gvk,
|
|
subresource,
|
|
policyContext.Operation(),
|
|
); err == nil {
|
|
h.log.V(4).Info("skip creating UR as the admission resource is both the source and the trigger")
|
|
continue
|
|
}
|
|
|
|
ruleCtx := buildRuleContext(rule, generateutils.TriggerFromLabels(labels), deleteDownstream)
|
|
urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx)
|
|
}
|
|
}
|
|
if err := h.urGenerator.Apply(ctx, urSpec); err != nil {
|
|
e := event.NewBackgroundFailedEvent(err, policy, "", event.GeneratePolicyController,
|
|
kyvernov1.ResourceSpec{Kind: new.GetKind(), Namespace: new.GetNamespace(), Name: new.GetName()})
|
|
h.eventGen.Add(e...)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|