Skip to content

Commit a55377c

Browse files
committed
fix: remove ui plugin finalizers in favor of k8s garbage collection
Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
1 parent 9bd5e3a commit a55377c

4 files changed

Lines changed: 109 additions & 27 deletions

File tree

hack/dev-deploy.sh

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ CAT_NAME=$(oc get catalogsource | grep 'observability-operator' | awk '{print $1
4646
SUB_NAME=$(oc get subscriptions | grep 'observability-operator' | awk '{print $1}') && oc delete subscriptions "${SUB_NAME}"
4747
CSV_NAME=$(oc get clusterserviceversion | grep 'observability-operator' | awk '{print $1}') && oc delete clusterserviceversion "${CSV_NAME}"
4848

49-
# delete uiplugin if hanging by unblock finalizer
50-
kubectl patch uiplugin monitoring --type='merge' -p='{"metadata":{"finalizers":null}}'
51-
52-
# OR Delete the whole operator
49+
# OR Delete the whole operator
5350
operator-sdk cleanup observability-operator -n openshift-operators
5451

5552
# Run the bundle using the fully qualified image tag.

pkg/controllers/uiplugin/controller.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -224,35 +224,19 @@ func (rm resourceManager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
224224
if !plugin.ObjectMeta.DeletionTimestamp.IsZero() {
225225
logger.V(6).Info("deregistering plugin from the console")
226226
if err := rm.deregisterPluginFromConsole(ctx, pluginTypeToConsoleName[plugin.Spec.Type]); err != nil {
227-
return ctrl.Result{}, err
227+
logger.V(3).Info("best-effort console deregistration failed during deletion", "error", err)
228228
}
229229

230-
// Remove finalizer if present
231-
if controllerutil.ContainsFinalizer(plugin, finalizerName) {
232-
patch := client.MergeFrom(plugin.DeepCopy())
233-
controllerutil.RemoveFinalizer(plugin, finalizerName)
234-
if err := rm.k8sClient.Patch(ctx, plugin, patch); err != nil {
235-
if apierrors.IsNotFound(err) {
236-
return ctrl.Result{}, nil
237-
}
238-
return ctrl.Result{}, err
239-
}
230+
if err := rm.removeLegacyFinalizer(ctx, plugin); err != nil {
231+
return ctrl.Result{}, err
240232
}
241233

242234
logger.V(6).Info("skipping reconcile since object is already scheduled for deletion")
243235
return ctrl.Result{}, nil
244236
}
245237

246-
// Add finalizer if not present
247-
if !controllerutil.ContainsFinalizer(plugin, finalizerName) {
248-
patch := client.MergeFrom(plugin.DeepCopy())
249-
controllerutil.AddFinalizer(plugin, finalizerName)
250-
if err := rm.k8sClient.Patch(ctx, plugin, patch); err != nil {
251-
if apierrors.IsNotFound(err) {
252-
return ctrl.Result{}, nil
253-
}
254-
return ctrl.Result{}, err
255-
}
238+
if err := rm.removeLegacyFinalizer(ctx, plugin); err != nil {
239+
return ctrl.Result{}, err
256240
}
257241

258242
compatibilityInfo, err := lookupImageAndFeatures(plugin.Spec.Type, rm.clusterVersion)
@@ -424,6 +408,21 @@ func (rm resourceManager) deregisterPluginFromConsole(ctx context.Context, plugi
424408
return nil
425409
}
426410

411+
func (rm resourceManager) removeLegacyFinalizer(ctx context.Context, plugin *uiv1alpha1.UIPlugin) error {
412+
if !controllerutil.ContainsFinalizer(plugin, finalizerName) {
413+
return nil
414+
}
415+
patch := client.MergeFrom(plugin.DeepCopy())
416+
controllerutil.RemoveFinalizer(plugin, finalizerName)
417+
if err := rm.k8sClient.Patch(ctx, plugin, patch); err != nil {
418+
if apierrors.IsNotFound(err) {
419+
return nil
420+
}
421+
return err
422+
}
423+
return nil
424+
}
425+
427426
func (rm resourceManager) getUIPlugin(ctx context.Context, req ctrl.Request) (*uiv1alpha1.UIPlugin, error) {
428427
logger := rm.logger.WithValues("plugin", req.NamespacedName)
429428

pkg/controllers/uiplugin/plugin_info_builder.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ var pluginTypeToConsoleName = map[uiv1alpha1.UIPluginType]string{
4848
uiv1alpha1.TypeMonitoring: "monitoring-console-plugin",
4949
}
5050

51+
func ConsoleNameForType(pluginType uiv1alpha1.UIPluginType) string {
52+
return pluginTypeToConsoleName[pluginType]
53+
}
54+
5155
func PluginInfoBuilder(ctx context.Context, k client.Client, dk dynamic.Interface, plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfiguration, compatibilityInfo CompatibilityEntry, clusterVersion string, logger logr.Logger) (*UIPluginInfo, error) {
5256
image := pluginConf.Images[compatibilityInfo.ImageKey]
5357
if image == "" {

pkg/operator/operator.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
"slices"
910
"time"
1011

1112
configv1 "github.com/openshift/api/config/v1"
13+
operatorv1 "github.com/openshift/api/operator/v1"
1214
openshifttls "github.com/openshift/controller-runtime-common/pkg/tls"
1315
v1 "k8s.io/api/core/v1"
1416
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1517
"k8s.io/apimachinery/pkg/labels"
1618
"k8s.io/apimachinery/pkg/util/wait"
1719
"k8s.io/apiserver/pkg/server/dynamiccertificates"
1820
"k8s.io/client-go/kubernetes"
21+
"k8s.io/client-go/rest"
1922
"k8s.io/client-go/tools/record"
2023
ctrl "sigs.k8s.io/controller-runtime"
2124
"sigs.k8s.io/controller-runtime/pkg/cache"
@@ -45,6 +48,7 @@ const (
4548
// OpenShift installations).
4649
type Operator struct {
4750
manager manager.Manager
51+
restConfig *rest.Config
4852
servingCertController *dynamiccertificates.DynamicServingCertificateController
4953
clientCAController *dynamiccertificates.ConfigMapCAController
5054
}
@@ -382,11 +386,20 @@ func New(ctx context.Context, cfg *OperatorConfiguration) (*Operator, error) {
382386
return nil, fmt.Errorf("unable to add health probe: %w", err)
383387
}
384388

385-
return &Operator{
389+
op := &Operator{
386390
manager: mgr,
391+
restConfig: restConfig,
387392
servingCertController: servingCertController,
388393
clientCAController: clientCAController,
389-
}, nil
394+
}
395+
396+
if cfg.FeatureGates.OpenShift.Enabled {
397+
if err := mgr.Add(op.newShutdownCleanupRunnable()); err != nil {
398+
return nil, fmt.Errorf("unable to add shutdown cleanup runnable: %w", err)
399+
}
400+
}
401+
402+
return op, nil
390403
}
391404

392405
func (o *Operator) Start(ctx context.Context) error {
@@ -405,6 +418,75 @@ func (o *Operator) Start(ctx context.Context) error {
405418
return nil
406419
}
407420

421+
func (o *Operator) newShutdownCleanupRunnable() manager.Runnable {
422+
return manager.RunnableFunc(func(ctx context.Context) error {
423+
// Block until the manager's context is cancelled (shutdown signal).
424+
<-ctx.Done()
425+
o.cleanupUIPluginsFromConsole()
426+
return nil
427+
})
428+
}
429+
430+
func (o *Operator) cleanupUIPluginsFromConsole() {
431+
logger := ctrl.Log.WithName("shutdown-cleanup")
432+
logger.Info("attempting best-effort UIPlugin console deregistration")
433+
434+
cleanupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
435+
defer cancel()
436+
437+
directClient, err := client.New(o.restConfig, client.Options{
438+
Scheme: o.manager.GetScheme(),
439+
})
440+
if err != nil {
441+
logger.Error(err, "failed to create client for shutdown cleanup")
442+
return
443+
}
444+
445+
pluginList := &uiv1alpha1.UIPluginList{}
446+
if err := directClient.List(cleanupCtx, pluginList); err != nil {
447+
logger.Error(err, "failed to list UIPlugins during shutdown cleanup")
448+
return
449+
}
450+
451+
if len(pluginList.Items) == 0 {
452+
return
453+
}
454+
455+
toRemove := make(map[string]struct{}, len(pluginList.Items))
456+
for _, plugin := range pluginList.Items {
457+
if name := uictrl.ConsoleNameForType(plugin.Spec.Type); name != "" {
458+
toRemove[name] = struct{}{}
459+
}
460+
}
461+
462+
if len(toRemove) == 0 {
463+
return
464+
}
465+
466+
cluster := &operatorv1.Console{}
467+
if err := directClient.Get(cleanupCtx, client.ObjectKey{Name: "cluster"}, cluster); err != nil {
468+
logger.Error(err, "failed to get Console CR during shutdown cleanup")
469+
return
470+
}
471+
472+
original := cluster.DeepCopy()
473+
cluster.Spec.Plugins = slices.DeleteFunc(cluster.Spec.Plugins, func(name string) bool {
474+
_, ok := toRemove[name]
475+
return ok
476+
})
477+
478+
if slices.Equal(cluster.Spec.Plugins, original.Spec.Plugins) {
479+
return
480+
}
481+
482+
patch := client.MergeFrom(original)
483+
if err := directClient.Patch(cleanupCtx, cluster, patch); err != nil {
484+
logger.Error(err, "failed to patch Console CR during shutdown cleanup")
485+
return
486+
}
487+
logger.Info("successfully cleaned up Console CR during shutdown")
488+
}
489+
408490
func (o *Operator) GetClient() client.Client {
409491
return o.manager.GetClient()
410492
}

0 commit comments

Comments
 (0)