@@ -68,6 +68,7 @@ import (
6868 "github.com/operator-framework/operator-controller/internal/operator-controller/controllers"
6969 "github.com/operator-framework/operator-controller/internal/operator-controller/features"
7070 "github.com/operator-framework/operator-controller/internal/operator-controller/finalizers"
71+ "github.com/operator-framework/operator-controller/internal/operator-controller/proxy"
7172 "github.com/operator-framework/operator-controller/internal/operator-controller/resolve"
7273 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety"
7374 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
@@ -219,6 +220,7 @@ func validateMetricsFlags() error {
219220 }
220221 return nil
221222}
223+
222224func run () error {
223225 setupLog .Info ("starting up the controller" , "version info" , version .String ())
224226
@@ -474,11 +476,17 @@ func run() error {
474476 }
475477
476478 certProvider := getCertificateProvider ()
479+
480+ // Create proxy configuration from environment variables
481+ // The fingerprint is calculated once during construction and cached
482+ proxyConfig := proxy .NewFromEnv ()
483+
477484 regv1ManifestProvider := & applier.RegistryV1ManifestProvider {
478485 BundleRenderer : registryv1 .Renderer ,
479486 CertificateProvider : certProvider ,
480487 IsWebhookSupportEnabled : certProvider != nil ,
481488 IsSingleOwnNamespaceEnabled : features .OperatorControllerFeatureGate .Enabled (features .SingleOwnNamespaceInstallSupport ),
489+ Proxy : proxyConfig ,
482490 }
483491 var cerCfg reconcilerConfigurator
484492 if features .OperatorControllerFeatureGate .Enabled (features .BoxcutterRuntime ) {
@@ -540,6 +548,73 @@ func run() error {
540548 return err
541549 }
542550
551+ // Add a runnable to trigger reconciliation of all ClusterExtensions on startup.
552+ // This ensures existing deployments get updated when proxy configuration changes
553+ // (added, modified, or removed).
554+ if err := mgr .Add (manager .RunnableFunc (func (ctx context.Context ) error {
555+ // Wait for the cache to sync
556+ if ! mgr .GetCache ().WaitForCacheSync (ctx ) {
557+ return fmt .Errorf ("failed to wait for cache sync" )
558+ }
559+
560+ // Always trigger reconciliation on startup to handle proxy config changes
561+ if proxyConfig != nil {
562+ setupLog .Info ("proxy configuration detected, triggering reconciliation of all ClusterExtensions" ,
563+ "httpProxy" , proxy .SanitizeURL (proxyConfig .HTTPProxy ), "httpsProxy" , proxy .SanitizeURL (proxyConfig .HTTPSProxy ), "noProxy" , proxyConfig .NoProxy )
564+ } else {
565+ setupLog .Info ("no proxy configuration detected, triggering reconciliation to remove proxy vars from existing deployments" )
566+ }
567+
568+ extList := & ocv1.ClusterExtensionList {}
569+ if err := cl .List (ctx , extList ); err != nil {
570+ setupLog .Error (err , "failed to list ClusterExtensions for proxy update" )
571+ return nil // Don't fail startup
572+ }
573+
574+ for i := range extList .Items {
575+ ext := & extList .Items [i ]
576+
577+ // Get current and desired proxy hash
578+ currentHash , exists := ext .Annotations [proxy .ConfigHashKey ]
579+ desiredHash := proxyConfig .Fingerprint ()
580+
581+ // Skip if annotation matches desired state
582+ if exists && currentHash == desiredHash {
583+ continue
584+ }
585+ // Skip if neither proxy nor annotation exists
586+ if ! exists && desiredHash == "" {
587+ continue
588+ }
589+
590+ // Use Patch instead of Update to avoid resourceVersion conflicts
591+ // This ensures the annotation is set even if the ClusterExtension is modified concurrently
592+ patch := client .MergeFrom (ext .DeepCopy ())
593+ if ext .Annotations == nil {
594+ ext .Annotations = make (map [string ]string )
595+ }
596+
597+ // Delete annotation if no proxy is configured, otherwise set it
598+ if desiredHash == "" {
599+ delete (ext .Annotations , proxy .ConfigHashKey )
600+ } else {
601+ ext .Annotations [proxy .ConfigHashKey ] = desiredHash
602+ }
603+
604+ if err := cl .Patch (ctx , ext , patch ); err != nil {
605+ setupLog .Error (err , "failed to set proxy hash on ClusterExtension" , "name" , ext .Name )
606+ // Continue with other ClusterExtensions
607+ }
608+ }
609+
610+ setupLog .Info ("triggered reconciliation for existing ClusterExtensions" , "count" , len (extList .Items ))
611+
612+ return nil
613+ })); err != nil {
614+ setupLog .Error (err , "unable to add startup reconciliation trigger" )
615+ return err
616+ }
617+
543618 setupLog .Info ("starting manager" )
544619 ctx := ctrl .SetupSignalHandler ()
545620 if err := mgr .Start (ctx ); err != nil {
@@ -625,13 +700,18 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
625700 ActionClientGetter : acg ,
626701 RevisionGenerator : rg ,
627702 }
703+ // Get the ManifestProvider to extract proxy fingerprint
704+ regv1Provider , ok := c .regv1ManifestProvider .(* applier.RegistryV1ManifestProvider )
705+ if ! ok {
706+ return fmt .Errorf ("manifest provider is not of type *applier.RegistryV1ManifestProvider" )
707+ }
628708 ceReconciler .ReconcileSteps = []controllers.ReconcileStepFunc {
629709 controllers .HandleFinalizers (c .finalizers ),
630710 controllers .MigrateStorage (storageMigrator ),
631711 controllers .RetrieveRevisionStates (revisionStatesGetter ),
632712 controllers .ResolveBundle (c .resolver , c .mgr .GetClient ()),
633713 controllers .UnpackBundle (c .imagePuller , c .imageCache ),
634- controllers .ApplyBundleWithBoxcutter (appl .Apply ),
714+ controllers .ApplyBundleWithBoxcutter (appl .Apply , regv1Provider . ProxyFingerprint ),
635715 }
636716
637717 baseDiscoveryClient , err := discovery .NewDiscoveryClientForConfig (c .mgr .GetConfig ())
0 commit comments