@@ -23,22 +23,28 @@ import (
2323 "flag"
2424 "fmt"
2525 "os"
26+ "time"
2627
28+ "github.com/go-logr/logr"
2729 snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
2830 configv1 "github.com/openshift/api/config/v1"
31+ consolev1 "github.com/openshift/api/console/v1"
2932 routev1 "github.com/openshift/api/route/v1"
3033 security "github.com/openshift/api/security/v1"
3134 monitor "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
3235 velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
3336 appsv1 "k8s.io/api/apps/v1"
3437 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
38+ "k8s.io/apimachinery/pkg/api/errors"
3539 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3640 "k8s.io/apimachinery/pkg/runtime"
3741 "k8s.io/apimachinery/pkg/types"
42+ "k8s.io/apimachinery/pkg/util/intstr"
3843 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3944 "k8s.io/client-go/discovery"
4045 "k8s.io/client-go/kubernetes"
4146 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
47+
4248 // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
4349 // to ensure that exec-entrypoint and run can make use of them.
4450 _ "k8s.io/client-go/plugin/pkg/client/auth"
@@ -54,6 +60,7 @@ import (
5460 oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1"
5561 "github.com/openshift/oadp-operator/internal/controller"
5662 pkgclient "github.com/openshift/oadp-operator/pkg/client"
63+
5764 //+kubebuilder:scaffold:imports
5865 "github.com/openshift/oadp-operator/pkg/credentials/stsflow"
5966 "github.com/openshift/oadp-operator/pkg/leaderelection"
@@ -86,6 +93,32 @@ func init() {
8693 //+kubebuilder:scaffold:scheme
8794}
8895
96+ // oadpCLISetupRunnable implements manager.Runnable to set up OADP CLI downloads after cache starts
97+ type oadpCLISetupRunnable struct {
98+ client client.Client
99+ namespace string
100+ log logr.Logger
101+ }
102+
103+ func (r * oadpCLISetupRunnable ) Start (ctx context.Context ) error {
104+ // Run setup in a goroutine and keep the runnable alive
105+ go func () {
106+ r .log .Info ("setting up OADP CLI download resources" )
107+ if err := setupOADPCLIDownload (ctx , r .client , r .namespace ); err != nil {
108+ r .log .Error (err , "unable to setup OADP CLI download, continuing anyway" )
109+ }
110+ }()
111+
112+ // Block until context is cancelled (manager shutdown)
113+ <- ctx .Done ()
114+ return nil
115+ }
116+
117+ // NeedLeaderElection returns false so this runs even if not the leader
118+ func (r * oadpCLISetupRunnable ) NeedLeaderElection () bool {
119+ return true
120+ }
121+
89122func main () {
90123 var metricsAddr string
91124 var enableLeaderElection bool
@@ -200,6 +233,11 @@ func main() {
200233 os .Exit (1 )
201234 }
202235
236+ if err := consolev1 .AddToScheme (mgr .GetScheme ()); err != nil {
237+ setupLog .Error (err , "unable to add OpenShift console API to scheme" )
238+ os .Exit (1 )
239+ }
240+
203241 if err := velerov1 .AddToScheme (mgr .GetScheme ()); err != nil {
204242 setupLog .Error (err , "unable to add Velero APIs to scheme" )
205243 os .Exit (1 )
@@ -275,6 +313,16 @@ func main() {
275313 os .Exit (1 )
276314 }
277315
316+ // Add OADP CLI download setup as a manager runnable
317+ if err := mgr .Add (& oadpCLISetupRunnable {
318+ client : mgr .GetClient (),
319+ namespace : watchNamespace ,
320+ log : setupLog ,
321+ }); err != nil {
322+ setupLog .Error (err , "unable to add OADP CLI setup runnable" )
323+ os .Exit (1 )
324+ }
325+
278326 setupLog .Info ("starting manager" )
279327 if err := mgr .Start (ctrl .SetupSignalHandler ()); err != nil {
280328 setupLog .Error (err , "problem running manager" )
@@ -348,3 +396,95 @@ func DoesCRDExist(CRDGroupVersion, CRDName string, kubeconf *rest.Config) (bool,
348396 }
349397 return discoveryResult , nil
350398}
399+
400+ func setupOADPCLIDownload (ctx context.Context , c client.Client , namespace string ) error {
401+ // Create Route, wait for hostname, create ConsoleCLIDownload
402+ // Implementation goes here
403+ route := & routev1.Route {
404+ ObjectMeta : metav1.ObjectMeta {
405+ Name : "openshift-adp-cli-server-route" ,
406+ Namespace : namespace ,
407+ },
408+ Spec : routev1.RouteSpec {
409+ To : routev1.RouteTargetReference {
410+ Kind : "Service" ,
411+ Name : "openshift-adp-cli-server" ,
412+ },
413+ Port : & routev1.RoutePort {
414+ TargetPort : intstr .FromString ("http" ),
415+ },
416+ TLS : & routev1.TLSConfig {
417+ Termination : routev1 .TLSTerminationEdge ,
418+ InsecureEdgeTerminationPolicy : routev1 .InsecureEdgeTerminationPolicyRedirect ,
419+ },
420+ },
421+ }
422+ err := c .Create (ctx , route )
423+ if err != nil && ! errors .IsAlreadyExists (err ) {
424+ return err
425+ }
426+ if errors .IsAlreadyExists (err ) {
427+ // Route already exists, just get it
428+ err = c .Get (ctx , client.ObjectKey {
429+ Name : "openshift-adp-cli-server-route" ,
430+ Namespace : namespace ,
431+ }, route )
432+ if err != nil {
433+ return fmt .Errorf ("failed to get existing route: %w" , err )
434+ }
435+ }
436+
437+ hostname := ""
438+ // 2. Get hostname from Status
439+ for i := 0 ; i < 3 ; i ++ {
440+ err = c .Get (ctx , client.ObjectKey {
441+ Name : "openshift-adp-cli-server-route" ,
442+ Namespace : namespace ,
443+ }, route )
444+
445+ if err != nil {
446+ return fmt .Errorf ("failed to get route: %w" , err )
447+ }
448+
449+ // Check if hostname is assigned
450+ if len (route .Status .Ingress ) > 0 && route .Status .Ingress [0 ].Host != "" {
451+ hostname = route .Status .Ingress [0 ].Host
452+ break
453+ }
454+
455+ // Backoff: wait 2^i seconds (1s, 2s, 4s)
456+ if i < 2 {
457+ setupLog .Info ("route hostname not ready, retrying..." , "attempt" , i + 1 )
458+ time .Sleep (time .Duration (1 << uint (i )) * time .Second )
459+ }
460+ }
461+
462+ if hostname == "" {
463+ return fmt .Errorf ("failed to get route hostname, oadp-cli-server is not ready" )
464+ }
465+
466+ // 3. Create ConsoleCLIDownload with the hostname
467+ downloadURL := fmt .Sprintf ("https://%s/" , hostname )
468+
469+ // Create the ConsoleCLIDownload (cluster-scoped resource, no namespace)
470+ consoleCLIDownload := & consolev1.ConsoleCLIDownload {
471+ ObjectMeta : metav1.ObjectMeta {
472+ Name : "openshift-adp-oadp-cli" ,
473+ },
474+ Spec : consolev1.ConsoleCLIDownloadSpec {
475+ Description : "OADP operator Command Line Interface (CLI)" ,
476+ DisplayName : "oadp - OADP operator Command Line Interface (CLI)" ,
477+ Links : []consolev1.CLIDownloadLink {
478+ {
479+ Href : downloadURL ,
480+ Text : "Download OADP CLI for Linux x86_64" ,
481+ },
482+ },
483+ },
484+ }
485+ err = c .Create (ctx , consoleCLIDownload )
486+ if err != nil && ! errors .IsAlreadyExists (err ) {
487+ return fmt .Errorf ("failed to create ConsoleCLIDownload: %w" , err )
488+ }
489+ return nil
490+ }
0 commit comments