You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2022/06/29 07:50:03 UTC

[camel-k] 01/06: feat(gc): Use SelfSubjectRulesReview to scan for garbage collectable resources

This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch release-1.9.x
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit b5dab9b69ededf03dc50bb70633677ce0a0efa89
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Wed Jun 1 16:10:58 2022 +0200

    feat(gc): Use SelfSubjectRulesReview to scan for garbage collectable resources
    
    (cherry picked from commit 8caeee372112d2dd60e9f5121af84e52de5355e4)
---
 pkg/trait/gc.go | 100 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 60 insertions(+), 40 deletions(-)

diff --git a/pkg/trait/gc.go b/pkg/trait/gc.go
index 1ff3204f5..b11802900 100644
--- a/pkg/trait/gc.go
+++ b/pkg/trait/gc.go
@@ -19,6 +19,7 @@ package trait
 
 import (
 	"context"
+	"fmt"
 	"path/filepath"
 	"regexp"
 	"strconv"
@@ -26,6 +27,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/apache/camel-k/pkg/util"
+	authorization "k8s.io/api/authorization/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -35,7 +38,6 @@ import (
 	"k8s.io/client-go/discovery"
 	"k8s.io/client-go/discovery/cached/disk"
 	"k8s.io/client-go/discovery/cached/memory"
-
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -81,8 +83,7 @@ func (t *garbageCollectorTrait) Configure(e *Environment) (bool, error) {
 		t.DiscoveryCache = &s
 	}
 
-	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(),
-		nil
+	return e.IntegrationInPhase(v1.IntegrationPhaseInitialization) || e.IntegrationInRunningPhases(), nil
 }
 
 func (t *garbageCollectorTrait) Apply(e *Environment) error {
@@ -92,12 +93,9 @@ func (t *garbageCollectorTrait) Apply(e *Environment) error {
 		// Register a post action that deletes the existing resources that are labelled
 		// with the previous integration generations.
 		// TODO: this should be refined so that it's run when all the replicas for the newer generation
-		// are ready. This is to be added when the integration scale status is refined with ready replicas
+		// are ready.
 		e.PostActions = append(e.PostActions, func(env *Environment) error {
-			// The collection and deletion are performed asynchronously to avoid blocking
-			// the reconciliation loop.
-			go t.garbageCollectResources(env)
-			return nil
+			return t.garbageCollectResources(env)
 		})
 
 		fallthrough
@@ -121,43 +119,40 @@ func (t *garbageCollectorTrait) Apply(e *Environment) error {
 	return nil
 }
 
-func (t *garbageCollectorTrait) garbageCollectResources(e *Environment) {
+func (t *garbageCollectorTrait) garbageCollectResources(e *Environment) error {
+	deletableGVKs, err := t.getDeletableTypes(e)
+	if err != nil {
+		return fmt.Errorf("cannot discover GVK types: %v", err)
+	}
+
 	integration, _ := labels.NewRequirement(v1.IntegrationLabel, selection.Equals, []string{e.Integration.Name})
 	generation, err := labels.NewRequirement("camel.apache.org/generation", selection.LessThan, []string{strconv.FormatInt(e.Integration.GetGeneration(), 10)})
 	if err != nil {
-		t.L.ForIntegration(e.Integration).Errorf(err, "cannot determine generation requirement")
-		return
+		return fmt.Errorf("cannot determine generation requirement: %v", err)
 	}
 	selector := labels.NewSelector().
 		Add(*integration).
 		Add(*generation)
 
-	deletableGVKs, err := t.getDeletableTypes(e)
-	if err != nil {
-		t.L.ForIntegration(e.Integration).Errorf(err, "cannot discover GVK types")
-		return
-	}
-
-	t.deleteEachOf(deletableGVKs, e, selector)
+	return t.deleteEachOf(e.Ctx, deletableGVKs, e, selector)
 }
 
-func (t *garbageCollectorTrait) deleteEachOf(gvks map[schema.GroupVersionKind]struct{}, e *Environment, selector labels.Selector) {
-	for gvk := range gvks {
+func (t *garbageCollectorTrait) deleteEachOf(ctx context.Context, GVKs map[schema.GroupVersionKind]struct{}, e *Environment, selector labels.Selector) error {
+	for GVK := range GVKs {
 		resources := unstructured.UnstructuredList{
 			Object: map[string]interface{}{
-				"apiVersion": gvk.GroupVersion().String(),
-				"kind":       gvk.Kind,
+				"apiVersion": GVK.GroupVersion().String(),
+				"kind":       GVK.Kind,
 			},
 		}
 		options := []ctrl.ListOption{
 			ctrl.InNamespace(e.Integration.Namespace),
 			ctrl.MatchingLabelsSelector{Selector: selector},
 		}
-		if err := t.Client.List(context.TODO(), &resources, options...); err != nil {
-			if !k8serrors.IsNotFound(err) && !k8serrors.IsForbidden(err) {
-				t.L.ForIntegration(e.Integration).Errorf(err, "cannot list child resources: %v", gvk)
+		if err := t.Client.List(ctx, &resources, options...); err != nil {
+			if !k8serrors.IsNotFound(err) {
+				return fmt.Errorf("cannot list child resources: %v", err)
 			}
-
 			continue
 		}
 
@@ -166,7 +161,7 @@ func (t *garbageCollectorTrait) deleteEachOf(gvks map[schema.GroupVersionKind]st
 			if !t.canBeDeleted(e, r) {
 				continue
 			}
-			err := t.Client.Delete(context.TODO(), &r, ctrl.PropagationPolicy(metav1.DeletePropagationBackground))
+			err := t.Client.Delete(ctx, &r, ctrl.PropagationPolicy(metav1.DeletePropagationBackground))
 			if err != nil {
 				// The resource may have already been deleted
 				if !k8serrors.IsNotFound(err) {
@@ -177,6 +172,8 @@ func (t *garbageCollectorTrait) deleteEachOf(gvks map[schema.GroupVersionKind]st
 			}
 		}
 	}
+
+	return nil
 }
 
 func (t *garbageCollectorTrait) canBeDeleted(e *Environment, u unstructured.Unstructured) bool {
@@ -192,7 +189,7 @@ func (t *garbageCollectorTrait) canBeDeleted(e *Environment, u unstructured.Unst
 func (t *garbageCollectorTrait) getDeletableTypes(e *Environment) (map[schema.GroupVersionKind]struct{}, error) {
 	// We rely on the discovery API to retrieve all the resources GVK,
 	// that results in an unbounded set that can impact garbage collection latency when scaling up.
-	discoveryClient, err := t.discoveryClient(e)
+	discoveryClient, err := t.discoveryClient()
 	if err != nil {
 		return nil, err
 	}
@@ -206,21 +203,45 @@ func (t *garbageCollectorTrait) getDeletableTypes(e *Environment) (map[schema.Gr
 
 	// We only take types that support the "delete" verb,
 	// to prevents from performing queries that we know are going to return "MethodNotAllowed".
-	return groupVersionKinds(discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)),
-		nil
-}
+	APIResourceLists := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
+
+	// Retrieve the permissions granted to the operator service account.
+	// We assume the operator has only to garbage collect the resources it has created.
+	srr := &authorization.SelfSubjectRulesReview{
+		Spec: authorization.SelfSubjectRulesReviewSpec{
+			Namespace: e.Integration.Namespace,
+		},
+	}
+	res, err := e.Client.AuthorizationV1().SelfSubjectRulesReviews().Create(e.Ctx, srr, metav1.CreateOptions{})
+	if err != nil {
+		return nil, err
+	}
 
-func groupVersionKinds(rls []*metav1.APIResourceList) map[schema.GroupVersionKind]struct{} {
-	GVKs := map[schema.GroupVersionKind]struct{}{}
-	for _, rl := range rls {
-		for _, r := range rl.APIResources {
-			GVKs[schema.FromAPIVersionAndKind(rl.GroupVersion, r.Kind)] = struct{}{}
+	GVKs := make(map[schema.GroupVersionKind]struct{})
+	for _, APIResourceList := range APIResourceLists {
+		for _, resource := range APIResourceList.APIResources {
+		rule:
+			for _, rule := range res.Status.ResourceRules {
+				if !util.StringSliceContainsAnyOf(rule.Verbs, "delete", "*") {
+					continue
+				}
+				for _, group := range rule.APIGroups {
+					for _, name := range rule.Resources {
+						if (resource.Group == group || group == "*") && (resource.Name == name || name == "*") {
+							GVK := schema.FromAPIVersionAndKind(APIResourceList.GroupVersion, resource.Kind)
+							GVKs[GVK] = struct{}{}
+							break rule
+						}
+					}
+				}
+			}
 		}
 	}
-	return GVKs
+
+	return GVKs, nil
 }
 
-func (t *garbageCollectorTrait) discoveryClient(e *Environment) (discovery.DiscoveryInterface, error) {
+func (t *garbageCollectorTrait) discoveryClient() (discovery.DiscoveryInterface, error) {
 	discoveryClientLock.Lock()
 	defer discoveryClientLock.Unlock()
 
@@ -247,7 +268,6 @@ func (t *garbageCollectorTrait) discoveryClient(e *Environment) (discovery.Disco
 		return t.Client.Discovery(), nil
 
 	default:
-		t.L.ForIntegration(e.Integration).Infof("unsupported discovery cache type: %s", *t.DiscoveryCache)
-		return t.Client.Discovery(), nil
+		return nil, fmt.Errorf("unsupported discovery cache type: %s", *t.DiscoveryCache)
 	}
 }