diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 23db640ec..42b26e080 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -200,13 +200,11 @@ func main() { } clusterExtensionFinalizers := crfinalizer.NewFinalizers() - domain := ocv1alpha1.GroupVersion.Group - cleanupUnpackCacheKey := fmt.Sprintf("%s/cleanup-unpack-cache", domain) - if err := clusterExtensionFinalizers.Register(cleanupUnpackCacheKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + if err := clusterExtensionFinalizers.Register(controllers.ClusterExtensionCleanupUnpackCacheFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { ext := obj.(*ocv1alpha1.ClusterExtension) return crfinalizer.Result{}, os.RemoveAll(filepath.Join(unpacker.BaseCachePath, ext.GetName())) })); err != nil { - setupLog.Error(err, "unable to register finalizer", "finalizerKey", cleanupUnpackCacheKey) + setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterExtensionCleanupUnpackCacheFinalizer) os.Exit(1) } @@ -265,6 +263,23 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") os.Exit(1) } + + clusterCatalogFinalizers := crfinalizer.NewFinalizers() + if err := clusterCatalogFinalizers.Register(controllers.ClusterCatalogCacheDeletionFinalizer, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) { + return crfinalizer.Result{}, cacheFetcher.Remove(obj.GetName()) + })); err != nil { + setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterCatalogCacheDeletionFinalizer) + os.Exit(1) + } + + if err = (&controllers.ClusterCatalogReconciler{ + Client: cl, + Finalizers: clusterCatalogFinalizers, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/base/rbac/role.yaml b/config/base/rbac/role.yaml index f6ed4109e..7fa1b57a3 100644 --- a/config/base/rbac/role.yaml +++ b/config/base/rbac/role.yaml @@ -20,26 +20,35 @@ rules: - olm.operatorframework.io resources: - catalogmetadata - - clustercatalogs verbs: - list - watch - apiGroups: - olm.operatorframework.io resources: - - clusterextensions + - clustercatalogs verbs: - get - list - - patch - update - watch - apiGroups: - olm.operatorframework.io resources: + - clustercatalogs/finalizers - clusterextensions/finalizers verbs: - update +- apiGroups: + - olm.operatorframework.io + resources: + - clusterextensions + verbs: + - get + - list + - patch + - update + - watch - apiGroups: - olm.operatorframework.io resources: diff --git a/internal/catalogmetadata/cache/cache.go b/internal/catalogmetadata/cache/cache.go index 241f11fd8..85f64a275 100644 --- a/internal/catalogmetadata/cache/cache.go +++ b/internal/catalogmetadata/cache/cache.go @@ -168,17 +168,13 @@ func (fsc *filesystemCache) FetchCatalogContents(ctx context.Context, catalog *c } // Remove deletes cache directory for a given catalog from the filesystem -func (fsc *filesystemCache) Remove(catalog *catalogd.ClusterCatalog) error { - if catalog == nil { - return fmt.Errorf("error: provided catalog must be non-nil") - } - - cacheDir := filepath.Join(fsc.cachePath, catalog.Name) +func (fsc *filesystemCache) Remove(catalogName string) error { + cacheDir := filepath.Join(fsc.cachePath, catalogName) fsc.mutex.Lock() defer fsc.mutex.Unlock() - if _, exists := fsc.cacheDataByCatalogName[catalog.Name]; !exists { + if _, exists := fsc.cacheDataByCatalogName[catalogName]; !exists { return nil } diff --git a/internal/controllers/clustercatalog_controller.go b/internal/controllers/clustercatalog_controller.go new file mode 100644 index 000000000..75c28dd11 --- /dev/null +++ b/internal/controllers/clustercatalog_controller.go @@ -0,0 +1,80 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "errors" + "fmt" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer" + + catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1" +) + +const ( + ClusterCatalogCacheDeletionFinalizer = "olm.operatorframework.io/cluster-catalog-cache-deletion" +) + +// ClusterCatalogReconciler reconciles a ClusterCatalog object +type ClusterCatalogReconciler struct { + client.Client + Finalizers crfinalizer.Finalizers +} + +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=get;list;watch;update +//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs/finalizers,verbs=update + +// The operator controller needs to watch all the bundle objects and reconcile accordingly. Though not ideal, but these permissions are required. +// This has been taken from rukpak, and an issue was created before to discuss it: https://github.com/operator-framework/rukpak/issues/800. +func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + existingCatalog := &catalogd.ClusterCatalog{} + if err := r.Client.Get(ctx, req.NamespacedName, existingCatalog); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + finalizeResult, err := r.Finalizers.Finalize(ctx, existingCatalog) + if err != nil { + return ctrl.Result{}, err + } + + var updateError error + if finalizeResult.StatusUpdated { + if err := r.Client.Status().Update(ctx, existingCatalog); err != nil { + updateError = errors.Join(updateError, fmt.Errorf("error updating status: %v", err)) + } + } + + if finalizeResult.Updated { + if err := r.Client.Update(ctx, existingCatalog); err != nil { + updateError = errors.Join(updateError, fmt.Errorf("error updating finalizers: %v", err)) + } + } + + return ctrl.Result{}, updateError +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error { + _, err := ctrl.NewControllerManagedBy(mgr). + For(&catalogd.ClusterCatalog{}). + Build(r) + + return err +} diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index 73debcffa..688e35741 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -57,6 +57,10 @@ import ( rukpaksource "github.com/operator-framework/operator-controller/internal/rukpak/source" ) +const ( + ClusterExtensionCleanupUnpackCacheFinalizer = "olm.operatorframework.io/cleanup-unpack-cache" +) + // ClusterExtensionReconciler reconciles a ClusterExtension object type ClusterExtensionReconciler struct { client.Client