From 4efcabffb5133ad6b8462482701adee85f27c349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= <charled.breteche@gmail.com> Date: Fri, 25 Mar 2022 15:43:47 +0100 Subject: [PATCH] refactor: use abstract policy interface in webhookconfig (#3466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> Co-authored-by: shuting <shuting@nirmata.com> --- api/kyverno/v1/clusterpolicy_types.go | 5 + api/kyverno/v1/policy_types.go | 5 + pkg/autogen/autogen.go | 4 +- pkg/autogen/autogen_test.go | 2 +- pkg/webhookconfig/configmanager.go | 180 ++++++++++++-------------- 5 files changed, 94 insertions(+), 102 deletions(-) diff --git a/api/kyverno/v1/clusterpolicy_types.go b/api/kyverno/v1/clusterpolicy_types.go index ef0b24f681..f42cb0a8e7 100644 --- a/api/kyverno/v1/clusterpolicy_types.go +++ b/api/kyverno/v1/clusterpolicy_types.go @@ -31,6 +31,11 @@ type ClusterPolicy struct { Status PolicyStatus `json:"status,omitempty" yaml:"status,omitempty"` } +// GetSpec returns the policy spec +func (p *ClusterPolicy) GetSpec() Spec { + return p.Spec +} + // GetRules returns the policy rules func (p *ClusterPolicy) GetRules() []Rule { return p.Spec.GetRules() diff --git a/api/kyverno/v1/policy_types.go b/api/kyverno/v1/policy_types.go index 744ba15850..2439241a51 100755 --- a/api/kyverno/v1/policy_types.go +++ b/api/kyverno/v1/policy_types.go @@ -32,6 +32,11 @@ type Policy struct { Status PolicyStatus `json:"status,omitempty" yaml:"status,omitempty"` } +// GetSpec returns the policy spec +func (p *Policy) GetSpec() Spec { + return p.Spec +} + // GetRules returns the policy rules func (p *Policy) GetRules() []Rule { return p.Spec.GetRules() diff --git a/pkg/autogen/autogen.go b/pkg/autogen/autogen.go index 872709b8ea..ae0d1d1b5c 100644 --- a/pkg/autogen/autogen.go +++ b/pkg/autogen/autogen.go @@ -118,7 +118,7 @@ func GetSupportedControllers(spec *kyverno.Spec, log logr.Logger) []string { } // GetRequestedControllers returns the requested autogen controllers based on object annotations. -func GetRequestedControllers(meta metav1.ObjectMeta) []string { +func GetRequestedControllers(meta *metav1.ObjectMeta) []string { annotations := meta.GetAnnotations() if annotations == nil { return nil @@ -135,7 +135,7 @@ func GetRequestedControllers(meta metav1.ObjectMeta) []string { // GetControllers computes the autogen controllers that should be applied to a policy. // It returns the requested, supported and effective controllers (intersection of requested and supported ones). -func GetControllers(meta metav1.ObjectMeta, spec *kyverno.Spec, log logr.Logger) ([]string, []string, []string) { +func GetControllers(meta *metav1.ObjectMeta, spec *kyverno.Spec, log logr.Logger) ([]string, []string, []string) { // compute supported and requested controllers supported := GetSupportedControllers(spec, log) requested := GetRequestedControllers(meta) diff --git a/pkg/autogen/autogen_test.go b/pkg/autogen/autogen_test.go index b29da042f7..08c6816bc0 100644 --- a/pkg/autogen/autogen_test.go +++ b/pkg/autogen/autogen_test.go @@ -241,7 +241,7 @@ func Test_GetRequestedControllers(t *testing.T) { } for _, test := range testCases { - controllers := GetRequestedControllers(test.meta) + controllers := GetRequestedControllers(&test.meta) assert.DeepEqual(t, test.expectedControllers, controllers) } } diff --git a/pkg/webhookconfig/configmanager.go b/pkg/webhookconfig/configmanager.go index aba1d445a7..cdaefe1944 100644 --- a/pkg/webhookconfig/configmanager.go +++ b/pkg/webhookconfig/configmanager.go @@ -36,6 +36,12 @@ import ( var DefaultWebhookTimeout int64 = 10 +// policy abstracts the concrete policy type (Policy vs ClusterPolicy) +type policy interface { + metav1.Object + GetSpec() kyverno.Spec +} + // webhookConfigManager manges the webhook configuration dynamically // it is NOT multi-thread safe type webhookConfigManager struct { @@ -139,13 +145,11 @@ func (m *webhookConfigManager) handleErr(err error, key interface{}) { m.queue.Forget(key) return } - if m.queue.NumRequeues(key) < 3 { logger.Error(err, "failed to sync policy", "key", key) m.queue.AddRateLimited(key) return } - utilruntime.HandleError(err) logger.V(2).Info("dropping policy out of queue", "key", key) m.queue.Forget(key) @@ -153,26 +157,22 @@ func (m *webhookConfigManager) handleErr(err error, key interface{}) { func (m *webhookConfigManager) addClusterPolicy(obj interface{}) { p := obj.(*kyverno.ClusterPolicy) - if hasWildcard(p) { + if hasWildcard(&p.Spec) { atomic.AddInt64(&m.wildcardPolicy, int64(1)) } m.enqueue(p) } func (m *webhookConfigManager) updateClusterPolicy(old, cur interface{}) { - oldP := old.(*kyverno.ClusterPolicy) - curP := cur.(*kyverno.ClusterPolicy) - + oldP, curP := old.(*kyverno.ClusterPolicy), cur.(*kyverno.ClusterPolicy) if reflect.DeepEqual(oldP.Spec, curP.Spec) { return } - - if hasWildcard(oldP) && !hasWildcard(curP) { + if hasWildcard(&oldP.Spec) && !hasWildcard(&curP.Spec) { atomic.AddInt64(&m.wildcardPolicy, ^int64(0)) - } else if !hasWildcard(oldP) && hasWildcard(curP) { + } else if !hasWildcard(&oldP.Spec) && hasWildcard(&curP.Spec) { atomic.AddInt64(&m.wildcardPolicy, int64(1)) } - m.enqueue(curP) } @@ -191,8 +191,7 @@ func (m *webhookConfigManager) deleteClusterPolicy(obj interface{}) { } m.log.V(4).Info("Recovered deleted ClusterPolicy '%s' from tombstone", "name", p.GetName()) } - - if hasWildcard(p) { + if hasWildcard(&p.Spec) { atomic.AddInt64(&m.wildcardPolicy, ^int64(0)) } m.enqueue(p) @@ -200,30 +199,23 @@ func (m *webhookConfigManager) deleteClusterPolicy(obj interface{}) { func (m *webhookConfigManager) addPolicy(obj interface{}) { p := obj.(*kyverno.Policy) - if hasWildcard(p) { + if hasWildcard(&p.Spec) { atomic.AddInt64(&m.wildcardPolicy, int64(1)) } - - pol := kyverno.ClusterPolicy(*p) - m.enqueue(&pol) + m.enqueue(p) } func (m *webhookConfigManager) updatePolicy(old, cur interface{}) { - oldP := old.(*kyverno.Policy) - curP := cur.(*kyverno.Policy) - + oldP, curP := old.(*kyverno.Policy), cur.(*kyverno.Policy) if reflect.DeepEqual(oldP.Spec, curP.Spec) { return } - - if hasWildcard(oldP) && !hasWildcard(curP) { + if hasWildcard(&oldP.Spec) && !hasWildcard(&curP.Spec) { atomic.AddInt64(&m.wildcardPolicy, ^int64(0)) - } else if !hasWildcard(oldP) && hasWildcard(curP) { + } else if !hasWildcard(&oldP.Spec) && hasWildcard(&curP.Spec) { atomic.AddInt64(&m.wildcardPolicy, int64(1)) } - - pol := kyverno.ClusterPolicy(*curP) - m.enqueue(&pol) + m.enqueue(curP) } func (m *webhookConfigManager) deletePolicy(obj interface{}) { @@ -239,15 +231,12 @@ func (m *webhookConfigManager) deletePolicy(obj interface{}) { utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type")) return } - m.log.V(4).Info("Recovered deleted ClusterPolicy '%s' from tombstone", "name", p.GetName()) + m.log.V(4).Info("Recovered deleted Policy '%s/%s' from tombstone", "name", p.GetNamespace(), p.GetName()) } - - if hasWildcard(p) { + if hasWildcard(&p.Spec) { atomic.AddInt64(&m.wildcardPolicy, ^int64(0)) } - - pol := kyverno.ClusterPolicy(*p) - m.enqueue(&pol) + m.enqueue(p) } func (m *webhookConfigManager) deleteWebhook(obj interface{}) { @@ -263,19 +252,17 @@ func (m *webhookConfigManager) deleteWebhook(obj interface{}) { func (m *webhookConfigManager) enqueueAllPolicies() { logger := m.log.WithName("enqueueAllPolicies") - policies, err := m.listAllPolicies() if err != nil { logger.Error(err, "unabled to list policies") } - for _, policy := range policies { m.enqueue(policy) logger.V(4).Info("added policy to the queue", "namespace", policy.GetNamespace(), "name", policy.GetName()) } } -func (m *webhookConfigManager) enqueue(policy *kyverno.ClusterPolicy) { +func (m *webhookConfigManager) enqueue(policy interface{}) { logger := m.log key, err := cache.MetaNamespaceKeyFunc(policy) if err != nil { @@ -330,7 +317,6 @@ func (m *webhookConfigManager) processNextWorkItem() bool { defer m.queue.Done(key) err := m.sync(key.(string)) m.handleErr(err, key) - return true } @@ -341,13 +327,11 @@ func (m *webhookConfigManager) sync(key string) error { defer func() { logger.V(4).Info("finished syncing policy", "key", key, "processingTime", time.Since(startTime).String()) }() - namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { logger.Info("invalid resource key", "key", key) return nil } - return m.reconcileWebhook(namespace, name) } @@ -378,7 +362,7 @@ func (m *webhookConfigManager) reconcileWebhook(namespace, name string) error { } } - if err := m.updateStatus(policy, ready); err != nil { + if err := m.updateStatus(namespace, name, ready); err != nil { return errors.Wrapf(err, "failed to update policy status %s/%s", namespace, name) } @@ -388,39 +372,30 @@ func (m *webhookConfigManager) reconcileWebhook(namespace, name string) error { return nil } -func (m *webhookConfigManager) getPolicy(namespace, name string) (*kyverno.ClusterPolicy, error) { +func (m *webhookConfigManager) getPolicy(namespace, name string) (policy, error) { if namespace == "" { return m.pLister.Get(name) + } else { + return m.npLister.Policies(namespace).Get(name) } - - nsPolicy, err := m.npLister.Policies(namespace).Get(name) - if err == nil && nsPolicy != nil { - p := kyverno.ClusterPolicy(*nsPolicy) - return &p, err - } - - return nil, err } -func (m *webhookConfigManager) listAllPolicies() ([]*kyverno.ClusterPolicy, error) { - policies := []*kyverno.ClusterPolicy{} - +func (m *webhookConfigManager) listAllPolicies() ([]policy, error) { + policies := []policy{} polList, err := m.npLister.Policies(metav1.NamespaceAll).List(labels.Everything()) if err != nil { return nil, errors.Wrapf(err, "failed to list Policy") } - - for _, pol := range polList { - p := kyverno.ClusterPolicy(*pol) - policies = append(policies, &p) + for _, p := range polList { + policies = append(policies, p) } - cpolList, err := m.pLister.List(labels.Everything()) if err != nil { return nil, errors.Wrapf(err, "failed to list ClusterPolicy") } - - policies = append(policies, cpolList...) + for _, p := range cpolList { + policies = append(policies, p) + } return policies, nil } @@ -463,19 +438,20 @@ func (m *webhookConfigManager) buildWebhooks(namespace string) (res []*webhook, } for _, p := range policies { - if p.HasValidate() || p.HasGenerate() { - if p.Spec.FailurePolicy != nil && *p.Spec.FailurePolicy == kyverno.Ignore { - m.mergeWebhook(validateIgnore, p, true) + spec := p.GetSpec() + if spec.HasValidate() || spec.HasGenerate() { + if spec.FailurePolicy != nil && *spec.FailurePolicy == kyverno.Ignore { + m.mergeWebhook(validateIgnore, &spec, true) } else { - m.mergeWebhook(validateFail, p, true) + m.mergeWebhook(validateFail, &spec, true) } } - if p.HasMutate() || p.HasVerifyImages() { - if p.Spec.FailurePolicy != nil && *p.Spec.FailurePolicy == kyverno.Ignore { - m.mergeWebhook(mutateIgnore, p, false) + if spec.HasMutate() || spec.HasVerifyImages() { + if spec.FailurePolicy != nil && *spec.FailurePolicy == kyverno.Ignore { + m.mergeWebhook(mutateIgnore, &spec, false) } else { - m.mergeWebhook(mutateFail, p, false) + m.mergeWebhook(mutateFail, &spec, false) } } } @@ -722,29 +698,45 @@ func (m *webhookConfigManager) compareAndUpdateWebhook(webhookKind, webhookName return nil } -func (m *webhookConfigManager) updateStatus(policy *kyverno.ClusterPolicy, status bool) error { - policyCopy := policy.DeepCopy() - requested, supported, activated := autogen.GetControllers(policy.ObjectMeta, &policy.Spec, m.log) - policyCopy.Status.SetReady(status) - policyCopy.Status.Autogen.Requested = requested - policyCopy.Status.Autogen.Supported = supported - policyCopy.Status.Autogen.Activated = activated - policyCopy.Status.Rules = policy.Spec.Rules - if reflect.DeepEqual(policyCopy.Status, policy.Status) { - return nil +func (m *webhookConfigManager) updateStatus(namespace, name string, ready bool) error { + update := func(meta *metav1.ObjectMeta, spec *kyverno.Spec, status *kyverno.PolicyStatus) bool { + copy := status.DeepCopy() + requested, supported, activated := autogen.GetControllers(meta, spec, m.log) + status.SetReady(ready) + status.Autogen.Requested = requested + status.Autogen.Supported = supported + status.Autogen.Activated = activated + status.Rules = spec.Rules + return !reflect.DeepEqual(status, copy) } - if policy.GetNamespace() == "" { - _, err := m.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(context.TODO(), policyCopy, metav1.UpdateOptions{}) - return err + if namespace == "" { + p, err := m.pLister.Get(name) + if err != nil { + return err + } + if update(&p.ObjectMeta, &p.Spec, &p.Status) { + if _, err := m.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(context.TODO(), p, metav1.UpdateOptions{}); err != nil { + return err + } + } + } else { + p, err := m.npLister.Policies(namespace).Get(name) + if err != nil { + return err + } + if update(&p.ObjectMeta, &p.Spec, &p.Status) { + if _, err := m.kyvernoClient.KyvernoV1().Policies(namespace).UpdateStatus(context.TODO(), p, metav1.UpdateOptions{}); err != nil { + return err + } + } } - _, err := m.kyvernoClient.KyvernoV1().Policies(policyCopy.GetNamespace()).UpdateStatus(context.TODO(), (*kyverno.Policy)(policyCopy), metav1.UpdateOptions{}) - return err + return nil } // mergeWebhook merges the matching kinds of the policy to webhook.rule -func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy *kyverno.ClusterPolicy, updateValidate bool) { +func (m *webhookConfigManager) mergeWebhook(dst *webhook, spec *kyverno.Spec, updateValidate bool) { matchedGVK := make([]string, 0) - for _, rule := range policy.GetRules() { + for _, rule := range spec.GetRules() { // matching kinds in generate policies need to be added to both webhook if rule.HasGenerate() { matchedGVK = append(matchedGVK, rule.MatchKinds()...) @@ -826,9 +818,9 @@ func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy *kyverno.Cluste dst.rule[apiVersions] = removeDuplicates(versions) dst.rule[resources] = removeDuplicates(rsrcs) - if policy.Spec.WebhookTimeoutSeconds != nil { - if dst.maxWebhookTimeout < int64(*policy.Spec.WebhookTimeoutSeconds) { - dst.maxWebhookTimeout = int64(*policy.Spec.WebhookTimeoutSeconds) + if spec.WebhookTimeoutSeconds != nil { + if dst.maxWebhookTimeout < int64(*spec.WebhookTimeoutSeconds) { + dst.maxWebhookTimeout = int64(*spec.WebhookTimeoutSeconds) } } } @@ -857,20 +849,10 @@ func webhookKey(webhookKind, failurePolicy string) string { return strings.Join([]string{webhookKind, failurePolicy}, "/") } -func hasWildcard(policy interface{}) bool { - if p, ok := policy.(*kyverno.ClusterPolicy); ok { - for _, rule := range p.GetRules() { - if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") { - return true - } - } - } - - if p, ok := policy.(*kyverno.Policy); ok { - for _, rule := range p.GetRules() { - if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") { - return true - } +func hasWildcard(spec *kyverno.Spec) bool { + for _, rule := range spec.GetRules() { + if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") { + return true } } return false