1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-09 02:29:22 +00:00

chore: refactor background controller (#10850)

* chore: refactor

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: add foreach for generate.daya to api

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: refactor

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
shuting 2024-08-14 22:32:49 +08:00 committed by GitHub
parent 03d898330f
commit 5203809b73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 208 additions and 171 deletions

View file

@ -13,7 +13,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) generateResponse {
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, severSideApply bool, rule kyvernov1.Rule, client dclient.Interface) generateResponse {
source := sourceSpec
clone := rule.Generation
if clone.Clone.Name != "" {
@ -29,8 +29,7 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, pol
log.V(4).Info("namespace is optional in case of cluster scope resource", "source namespace", source.GetNamespace())
}
if source.GetNamespace() == target.GetNamespace() && source.GetName() == target.GetName() ||
(rule.Generation.CloneList.Kinds != nil) && (source.GetNamespace() == target.GetNamespace()) {
if source.GetNamespace() == target.GetNamespace() && source.GetName() == target.GetName() {
log.V(4).Info("skip resource self-clone")
return newSkipGenerateResponse(nil, target, nil)
}
@ -65,7 +64,7 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, pol
}
if targetObj != nil {
if !policy.GetSpec().UseServerSideApply {
if !severSideApply {
sourceObjCopy.SetUID(targetObj.GetUID())
sourceObjCopy.SetSelfLink(targetObj.GetSelfLink())
sourceObjCopy.SetCreationTimestamp(targetObj.GetCreationTimestamp())
@ -81,7 +80,7 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, pol
return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil)
}
func manageCloneList(log logr.Logger, targetNamespace string, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) []generateResponse {
func manageCloneList(log logr.Logger, targetNamespace string, severSideApply bool, rule kyvernov1.Rule, client dclient.Interface) []generateResponse {
var responses []generateResponse
cloneList := rule.Generation.CloneList
sourceNamespace := cloneList.Namespace
@ -101,8 +100,14 @@ func manageCloneList(log logr.Logger, targetNamespace string, policy kyvernov1.P
for _, source := range sources.Items {
target := newResourceSpec(source.GetAPIVersion(), source.GetKind(), targetNamespace, source.GetName())
if (cloneList.Kinds != nil) && (source.GetNamespace() == target.GetNamespace()) {
log.V(4).Info("skip resource self-clone")
responses = append(responses, newSkipGenerateResponse(nil, target, nil))
continue
}
responses = append(responses,
manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), policy, rule, client))
manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), severSideApply, rule, client))
}
}
return responses

View file

@ -3,6 +3,7 @@ package generate
import (
"context"
"encoding/json"
"errors"
"fmt"
"slices"
"strings"
@ -11,7 +12,6 @@ import (
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/background/common"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
@ -21,7 +21,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/validate"
"github.com/kyverno/kyverno/pkg/engine/variables"
regex "github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/event"
@ -29,7 +28,6 @@ import (
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
validationpolicy "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/pkg/errors"
"go.uber.org/multierr"
admissionv1 "k8s.io/api/admission/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -97,6 +95,11 @@ func (c *GenerateController) ProcessUR(ur *kyvernov2.UpdateRequest) error {
logger.Info("start processing UR", "ur", ur.Name, "resourceVersion", ur.GetResourceVersion())
var failures []error
policy, err := c.getPolicyObject(*ur)
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("error in fetching policy: %v", err)
}
for i := 0; i < len(ur.Spec.RuleContext); i++ {
rule := ur.Spec.RuleContext[i]
trigger, err := c.getTrigger(ur.Spec, i)
@ -106,19 +109,12 @@ func (c *GenerateController) ProcessUR(ur *kyvernov2.UpdateRequest) error {
continue
}
namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger)
genResources, err = c.applyGenerate(*trigger, *ur, i, namespaceLabels)
genResources, err = c.applyGenerate(*trigger, *ur, policy, i)
if err != nil {
if strings.Contains(err.Error(), doesNotApply) {
logger.V(4).Info(fmt.Sprintf("skipping rule %s: %v", rule.Rule, err.Error()))
}
policy, err := c.getPolicyObject(*ur)
if err != nil {
failures = append(failures, fmt.Errorf("rule %v failed: failed to get policy object: %s", rule.Rule, err))
continue
}
events := event.NewBackgroundFailedEvent(err, policy, ur.Spec.RuleContext[i].Rule, event.GeneratePolicyController,
kyvernov1.ResourceSpec{Kind: trigger.GetKind(), Namespace: trigger.GetNamespace(), Name: trigger.GetName()})
c.eventGen.Add(events...)
@ -194,35 +190,22 @@ func (c *GenerateController) getTriggerForCreateOperation(spec kyvernov2.UpdateR
return trigger, err
}
func (c *GenerateController) applyGenerate(trigger unstructured.Unstructured, ur kyvernov2.UpdateRequest, i int, namespaceLabels map[string]string) ([]kyvernov1.ResourceSpec, error) {
func (c *GenerateController) applyGenerate(trigger unstructured.Unstructured, ur kyvernov2.UpdateRequest, policy kyvernov1.PolicyInterface, i int) ([]kyvernov1.ResourceSpec, error) {
logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey())
logger.V(3).Info("applying generate policy")
policy, err := c.getPolicyObject(ur)
if err != nil && !apierrors.IsNotFound(err) {
logger.Error(err, "error in fetching policy")
return nil, err
}
ruleContext := ur.Spec.RuleContext[i]
if ruleContext.DeleteDownstream || apierrors.IsNotFound(err) {
err = c.deleteDownstream(policy, ruleContext, &ur)
return nil, err
if ruleContext.DeleteDownstream || policy == nil {
return nil, c.deleteDownstream(policy, ruleContext, &ur)
}
var rule *kyvernov1.Rule
p := policy.CreateDeepCopy()
for j := range p.GetSpec().Rules {
if p.GetSpec().Rules[j].Name == ruleContext.Rule {
rule = &p.GetSpec().Rules[j]
break
}
}
if rule == nil {
logger.Info("skip rule application as the rule does not exist in the updaterequest", "rule", ruleContext.Rule)
p, ok := buildPolicyWithAppliedRules(policy, ruleContext.Rule)
if !ok {
logger.V(4).Info("skip rule application as the rule does not exist in the updaterequest", "rule", ruleContext.Rule)
return nil, nil
}
p.GetSpec().SetRules([]kyvernov1.Rule{*rule})
namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger)
policyContext, err := common.NewBackgroundContext(logger, c.client, ur.Spec.Context, p, &trigger, c.configuration, c.jp, namespaceLabels)
if err != nil {
return nil, err
@ -288,19 +271,6 @@ func (c *GenerateController) getPolicyObject(ur kyvernov2.UpdateRequest) (kyvern
return npolicyObj, nil
}
func updateStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error, genResources []kyvernov1.ResourceSpec) error {
if err != nil {
if _, err := statusControl.Failed(ur.GetName(), err.Error(), genResources); err != nil {
return err
}
} else {
if _, err := statusControl.Success(ur.GetName(), genResources); err != nil {
return err
}
}
return nil
}
func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, applicableRules []string) (genResources []kyvernov1.ResourceSpec, err error) {
policy := policyContext.Policy()
resource := policyContext.NewResource()
@ -309,7 +279,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
applyRules := policy.GetSpec().GetApplyRules()
applyCount := 0
for _, rule := range autogen.ComputeRules(policy, "") {
for _, rule := range policy.GetSpec().Rules {
var err error
if !rule.HasGenerate() {
continue
@ -341,7 +311,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
}
logger := log.WithValues("rule", rule.Name)
// add configmap json data to context
if err := c.engine.ContextLoader(policyContext.Policy(), rule)(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil {
if err := c.engine.ContextLoader(policy, rule)(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil {
log.Error(err, "cannot add configmaps to context")
return nil, err
}
@ -351,7 +321,8 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
return nil, err
}
genResource, err = applyRule(logger, c.client, rule, resource, policy)
g := newGenerator(c.client, logger, policy, rule, resource)
genResource, err = g.generate()
if err != nil {
log.Error(err, "failed to apply generate rule", "policy", policy.GetName(), "rule", rule.Name, "resource", resource.GetName())
return nil, err
@ -364,123 +335,6 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
return genResources, nil
}
func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, trigger unstructured.Unstructured, policy kyvernov1.PolicyInterface) ([]kyvernov1.ResourceSpec, error) {
responses := []generateResponse{}
var err error
var newGenResources []kyvernov1.ResourceSpec
target := rule.Generation.ResourceSpec
logger := log.WithValues("target", target.String())
if rule.Generation.Clone.Name != "" {
resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, policy, rule, client)
responses = append(responses, resp)
} else if len(rule.Generation.CloneList.Kinds) != 0 {
responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), policy, rule, client)
} else {
resp := manageData(logger.WithValues("type", "data"), target, rule.Generation.RawData, rule.Generation.Synchronize, client)
responses = append(responses, resp)
}
for _, response := range responses {
targetMeta := response.GetTarget()
if response.GetError() != nil {
logger.Error(response.GetError(), "failed to generate resource", "mode", response.GetAction())
return newGenResources, err
}
if response.GetAction() == Skip {
continue
}
logger.V(3).Info("applying generate rule", "mode", response.GetAction())
if response.GetData() == nil && response.GetAction() == Update {
logger.V(4).Info("no changes required for generate target resource")
return newGenResources, nil
}
newResource := &unstructured.Unstructured{}
newResource.SetUnstructuredContent(response.GetData())
newResource.SetName(targetMeta.GetName())
newResource.SetNamespace(targetMeta.GetNamespace())
if newResource.GetKind() == "" {
newResource.SetKind(targetMeta.GetKind())
}
newResource.SetAPIVersion(targetMeta.GetAPIVersion())
common.ManageLabels(newResource, trigger, policy, rule.Name)
if response.GetAction() == Create {
newResource.SetResourceVersion("")
if policy.GetSpec().UseServerSideApply {
_, err = client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
if !apierrors.IsAlreadyExists(err) {
return newGenResources, err
}
}
logger.V(2).Info("created generate target resource")
newGenResources = append(newGenResources, targetMeta)
} else if response.GetAction() == Update {
generatedObj, err := client.GetResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName())
if err != nil {
logger.V(2).Info("creating new target due to the failure when fetching", "err", err.Error())
if policy.GetSpec().UseServerSideApply {
_, err = client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
return newGenResources, err
}
newGenResources = append(newGenResources, targetMeta)
} else {
if !rule.Generation.Synchronize {
logger.V(4).Info("synchronize disabled, skip syncing changes")
continue
}
if err := validate.MatchPattern(logger, newResource.Object, generatedObj.Object); err == nil {
if err := validate.MatchPattern(logger, generatedObj.Object, newResource.Object); err == nil {
logger.V(4).Info("patterns match, skipping updates")
continue
}
}
logger.V(4).Info("updating existing resource")
if targetMeta.GetAPIVersion() == "" {
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
newResource.SetAPIVersion(generatedResourceAPIVersion)
}
if targetMeta.GetNamespace() == "" {
newResource.SetNamespace("default")
}
if policy.GetSpec().UseServerSideApply {
_, err = client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = client.UpdateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
logger.Error(err, "failed to update resource")
return newGenResources, err
}
}
logger.V(3).Info("updated generate target resource")
}
}
return newGenResources, nil
}
func GetUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error) {
ruleData, err := json.Marshal(rule)
if err != nil {
return nil, err
}
return kubeutils.BytesToUnstructured(ruleData)
}
// NewGenerateControllerWithOnlyClient returns an instance of Controller with only the client.
func NewGenerateControllerWithOnlyClient(client dclient.Interface, engine engineapi.Engine) *GenerateController {
c := GenerateController{
@ -498,3 +352,24 @@ func (c *GenerateController) GetUnstrResource(genResourceSpec kyvernov1.Resource
}
return resource, nil
}
func updateStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error, genResources []kyvernov1.ResourceSpec) error {
if err != nil {
if _, err := statusControl.Failed(ur.GetName(), err.Error(), genResources); err != nil {
return err
}
} else {
if _, err := statusControl.Success(ur.GetName(), genResources); err != nil {
return err
}
}
return nil
}
func GetUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error) {
ruleData, err := json.Marshal(rule)
if err != nil {
return nil, err
}
return kubeutils.BytesToUnstructured(ruleData)
}

View file

@ -0,0 +1,140 @@
package generate
import (
"context"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/background/common"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/engine/validate"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type generator struct {
client dclient.Interface
logger logr.Logger
policy kyvernov1.PolicyInterface
rule kyvernov1.Rule
trigger unstructured.Unstructured
}
func newGenerator(client dclient.Interface, logger logr.Logger, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, trigger unstructured.Unstructured) *generator {
return &generator{
client: client,
logger: logger,
policy: policy,
rule: rule,
trigger: trigger,
}
}
func (g *generator) generate() ([]kyvernov1.ResourceSpec, error) {
responses := []generateResponse{}
var err error
var newGenResources []kyvernov1.ResourceSpec
target := g.rule.Generation.ResourceSpec
logger := g.logger.WithValues("target", target.String())
if g.rule.Generation.Clone.Name != "" {
resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, g.policy.GetSpec().UseServerSideApply, g.rule, g.client)
responses = append(responses, resp)
} else if len(g.rule.Generation.CloneList.Kinds) != 0 {
responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), g.policy.GetSpec().UseServerSideApply, g.rule, g.client)
} else {
resp := manageData(logger.WithValues("type", "data"), target, g.rule.Generation.RawData, g.rule.Generation.Synchronize, g.client)
responses = append(responses, resp)
}
for _, response := range responses {
targetMeta := response.GetTarget()
if response.GetError() != nil {
logger.Error(response.GetError(), "failed to generate resource", "mode", response.GetAction())
return newGenResources, err
}
if response.GetAction() == Skip {
continue
}
logger.V(3).Info("applying generate rule", "mode", response.GetAction())
if response.GetData() == nil && response.GetAction() == Update {
logger.V(4).Info("no changes required for generate target resource")
return newGenResources, nil
}
newResource := &unstructured.Unstructured{}
newResource.SetUnstructuredContent(response.GetData())
newResource.SetName(targetMeta.GetName())
newResource.SetNamespace(targetMeta.GetNamespace())
if newResource.GetKind() == "" {
newResource.SetKind(targetMeta.GetKind())
}
newResource.SetAPIVersion(targetMeta.GetAPIVersion())
common.ManageLabels(newResource, g.trigger, g.policy, g.rule.Name)
if response.GetAction() == Create {
newResource.SetResourceVersion("")
if g.policy.GetSpec().UseServerSideApply {
_, err = g.client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = g.client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
if !apierrors.IsAlreadyExists(err) {
return newGenResources, err
}
}
logger.V(2).Info("created generate target resource")
newGenResources = append(newGenResources, targetMeta)
} else if response.GetAction() == Update {
generatedObj, err := g.client.GetResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName())
if err != nil {
logger.V(2).Info("creating new target due to the failure when fetching", "err", err.Error())
if g.policy.GetSpec().UseServerSideApply {
_, err = g.client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = g.client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
return newGenResources, err
}
newGenResources = append(newGenResources, targetMeta)
} else {
if !g.rule.Generation.Synchronize {
logger.V(4).Info("synchronize disabled, skip syncing changes")
continue
}
if err := validate.MatchPattern(logger, newResource.Object, generatedObj.Object); err == nil {
if err := validate.MatchPattern(logger, generatedObj.Object, newResource.Object); err == nil {
logger.V(4).Info("patterns match, skipping updates")
continue
}
}
logger.V(4).Info("updating existing resource")
if targetMeta.GetAPIVersion() == "" {
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
newResource.SetAPIVersion(generatedResourceAPIVersion)
}
if targetMeta.GetNamespace() == "" {
newResource.SetNamespace("default")
}
if g.policy.GetSpec().UseServerSideApply {
_, err = g.client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate")
} else {
_, err = g.client.UpdateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
}
if err != nil {
logger.Error(err, "failed to update resource")
return newGenResources, err
}
}
logger.V(3).Info("updated generate target resource")
}
}
return newGenResources, nil
}

View file

@ -29,3 +29,20 @@ func TriggerFromLabels(labels map[string]string) kyvernov1.ResourceSpec {
APIVersion: apiVersion.String(),
}
}
func buildPolicyWithAppliedRules(policy kyvernov1.PolicyInterface, expect string) (kyvernov1.PolicyInterface, bool) {
var rule *kyvernov1.Rule
p := policy.CreateDeepCopy()
for j := range p.GetSpec().Rules {
if p.GetSpec().Rules[j].Name == expect {
rule = &p.GetSpec().Rules[j]
break
}
}
if rule == nil {
return nil, false
}
p.GetSpec().SetRules([]kyvernov1.Rule{*rule})
return p, true
}