diff --git a/cmd/cluster/resync.go b/cmd/cluster/resync.go index 95e8f2a5c..cc45a2f22 100644 --- a/cmd/cluster/resync.go +++ b/cmd/cluster/resync.go @@ -24,8 +24,9 @@ const ( ) type Resync struct { - hive client.Client - clusterId string + hive client.Client + clusterId string + hiveOcmUrl string } func newCmdResync() *cobra.Command { @@ -44,6 +45,9 @@ func newCmdResync() *cobra.Command { Example: ` # Force a cluster resync by deleting its clustersync CustomResource osdctl cluster resync --cluster-id ${CLUSTER_ID} + + # While connected to staging OCM, force a resync using the production Hive environment + OCM_URL=staging osdctl cluster resync --cluster-id ${CLUSTER_ID} --hive-ocm-url production `, RunE: func(cmd *cobra.Command, args []string) error { return r.Run(context.Background()) @@ -51,6 +55,7 @@ func newCmdResync() *cobra.Command { } resyncCmd.Flags().StringVarP(&r.clusterId, "cluster-id", "C", "", "OCM internal/external cluster id or cluster name to delete the clustersync for.") + resyncCmd.Flags().StringVar(&r.hiveOcmUrl, "hive-ocm-url", "", "(optional) OCM environment URL for Hive operations. Aliases: 'production', 'staging', 'integration'. This only changes how the Hive cluster is resolved; the target cluster still comes from the current/default OCM environment.") return resyncCmd } @@ -67,6 +72,22 @@ func (r *Resync) New() error { return err } + // Validate and resolve --hive-ocm-url if provided + if r.hiveOcmUrl != "" { + resolvedHiveOcmURL, err := utils.ValidateAndResolveOcmUrl(r.hiveOcmUrl) + if err != nil { + return fmt.Errorf("invalid --hive-ocm-url: %w", err) + } + r.hiveOcmUrl = resolvedHiveOcmURL + } + + // Check if multi-environment support is needed + if r.hiveOcmUrl != "" { + // Use new multi-environment path + return r.initWithMultiEnv(scheme) + } + + // === ORIGINAL PATH (PRESERVED FOR BACKWARD COMPATIBILITY) === ocmClient, err := utils.CreateConnection() if err != nil { return err @@ -93,6 +114,48 @@ func (r *Resync) New() error { return nil } +// initWithMultiEnv initializes the Resync struct using separate OCM connections for target cluster and hive +func (r *Resync) initWithMultiEnv(scheme *runtime.Scheme) error { + // Create OCM connection for target cluster (uses system env vars) + targetOCM, err := utils.CreateConnection() + if err != nil { + return fmt.Errorf("failed to create target cluster OCM connection: %w", err) + } + defer targetOCM.Close() + + // Create separate OCM connection for hive + hiveOCM, err := utils.CreateConnectionWithUrl(r.hiveOcmUrl) + if err != nil { + return fmt.Errorf("failed to create hive OCM connection with URL '%s': %w", r.hiveOcmUrl, err) + } + defer hiveOCM.Close() + + // Get cluster info from target OCM environment + cluster, err := utils.GetClusterAnyStatus(targetOCM, r.clusterId) + if err != nil { + return fmt.Errorf("failed to get OCM cluster info for %s: %w", r.clusterId, err) + } + r.clusterId = cluster.ID() + + // Get hive cluster using both OCM connections + hive, err := utils.GetHiveClusterWithConn(cluster.ID(), targetOCM, hiveOCM) + if err != nil { + return fmt.Errorf("failed to get hive cluster (OCM URL:'%s'): %w", r.hiveOcmUrl, err) + } + + // Create k8s client for hive using the hive OCM connection + hc, err := k8s.NewWithConn(hive.ID(), client.Options{Scheme: scheme}, hiveOCM) + if err != nil { + return fmt.Errorf("failed to create hive k8s client(OCM URL:'%s'): %w", r.hiveOcmUrl, err) + } + + r.hive = hc + log.Printf("ready to delete clustersync for cluster: %s/%s on hive: %s (using hive OCM URL: %s)", + cluster.ID(), cluster.Name(), hive.Name(), r.hiveOcmUrl) + + return nil +} + func (r *Resync) Run(ctx context.Context) error { if err := r.New(); err != nil { return fmt.Errorf("failed to initialize command: %v", err) diff --git a/cmd/cluster/resync_test.go b/cmd/cluster/resync_test.go new file mode 100644 index 000000000..ff7da8f2f --- /dev/null +++ b/cmd/cluster/resync_test.go @@ -0,0 +1,72 @@ +package cluster + +import ( + "strings" + "testing" + + "github.com/openshift/osdctl/pkg/utils" +) + +// TestHiveOcmUrlValidation tests the early validation of --hive-ocm-url flag in the resync command +func TestHiveOcmUrlValidation(t *testing.T) { + tests := []struct { + name string + hiveOcmUrl string + expectErr bool + errContains string + }{ + { + name: "Valid hive-ocm-url (production)", + hiveOcmUrl: "production", + expectErr: false, + }, + { + name: "Valid hive-ocm-url (staging)", + hiveOcmUrl: "staging", + expectErr: false, + }, + { + name: "Valid hive-ocm-url (integration)", + hiveOcmUrl: "integration", + expectErr: false, + }, + { + name: "Valid hive-ocm-url (full URL)", + hiveOcmUrl: "https://api.openshift.com", + expectErr: false, + }, + { + name: "Invalid hive-ocm-url", + hiveOcmUrl: "invalid-environment", + expectErr: true, + errContains: "invalid OCM_URL", + }, + { + name: "Empty hive-ocm-url (flag omitted)", + hiveOcmUrl: "", + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Mirrors Resync.New(): validate only when flag is provided + var err error + if tt.hiveOcmUrl != "" { + _, err = utils.ValidateAndResolveOcmUrl(tt.hiveOcmUrl) + } + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error containing '%s', but got nil", tt.errContains) + } else if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("Expected error containing '%s', but got: %v", tt.errContains, err) + } + } else { + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + } + }) + } +} diff --git a/docs/README.md b/docs/README.md index 458cac5e2..c3957046d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2009,6 +2009,7 @@ osdctl cluster resync [flags] -C, --cluster-id string OCM internal/external cluster id or cluster name to delete the clustersync for. --context string The name of the kubeconfig context to use -h, --help help for resync + --hive-ocm-url string (optional) OCM environment URL for Hive operations. Aliases: 'production', 'staging', 'integration'. This only changes how the Hive cluster is resolved; the target cluster still comes from the current/default OCM environment. --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --kubeconfig string Path to the kubeconfig file to use for CLI requests. -o, --output string Valid formats are ['', 'json', 'yaml', 'env'] diff --git a/docs/osdctl_cluster_resync.md b/docs/osdctl_cluster_resync.md index ac6f69e23..321466577 100644 --- a/docs/osdctl_cluster_resync.md +++ b/docs/osdctl_cluster_resync.md @@ -23,13 +23,17 @@ osdctl cluster resync [flags] # Force a cluster resync by deleting its clustersync CustomResource osdctl cluster resync --cluster-id ${CLUSTER_ID} + # While connected to staging OCM, force a resync using the production Hive environment + OCM_URL=staging osdctl cluster resync --cluster-id ${CLUSTER_ID} --hive-ocm-url production + ``` ### Options ``` - -C, --cluster-id string OCM internal/external cluster id or cluster name to delete the clustersync for. - -h, --help help for resync + -C, --cluster-id string OCM internal/external cluster id or cluster name to delete the clustersync for. + -h, --help help for resync + --hive-ocm-url string (optional) OCM environment URL for Hive operations. Aliases: 'production', 'staging', 'integration'. This only changes how the Hive cluster is resolved; the target cluster still comes from the current/default OCM environment. ``` ### Options inherited from parent commands