@@ -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).
4649type 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
392405func (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+
408490func (o * Operator ) GetClient () client.Client {
409491 return o .manager .GetClient ()
410492}
0 commit comments