From 05e4a99652289f3aabe7732b3c2170081858358f Mon Sep 17 00:00:00 2001 From: Alex Vulaj Date: Fri, 28 Jul 2023 14:52:01 -0400 Subject: [PATCH] Return all active clusters when using 'org clusters' command --- cmd/org/aws-accounts.go | 2 +- cmd/org/clusters.go | 186 ++++++++++++++++++++++------------------ 2 files changed, 102 insertions(+), 86 deletions(-) diff --git a/cmd/org/aws-accounts.go b/cmd/org/aws-accounts.go index c4bb8f95..a05789ab 100644 --- a/cmd/org/aws-accounts.go +++ b/cmd/org/aws-accounts.go @@ -45,7 +45,7 @@ func init() { "ou-id", "", "", - "specify orgnaization unit id", + "specify organization unit id", ) AddOutputFlag(flags) diff --git a/cmd/org/clusters.go b/cmd/org/clusters.go index d5a406c2..34094d8e 100644 --- a/cmd/org/clusters.go +++ b/cmd/org/clusters.go @@ -1,13 +1,11 @@ package org import ( - "encoding/json" "fmt" - "log" + accountsv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" "os" "github.com/aws/aws-sdk-go-v2/service/organizations" - "github.com/openshift-online/ocm-cli/pkg/arguments" sdk "github.com/openshift-online/ocm-sdk-go" "github.com/openshift/osdctl/pkg/printer" "github.com/openshift/osdctl/pkg/utils" @@ -20,39 +18,55 @@ const ( ) var ( - clustersCmd = &cobra.Command{ - Use: "clusters", - Short: "get organization clusters", - Args: cobra.ArbitraryArgs, + allClustersFlag = false + awsAccountID = "" + clustersCmd = &cobra.Command{ + Use: "clusters", + Short: "get all active organization clusters", + Long: `By default, returns all active clusters for a given organization. The organization can either be specified with an argument +passed in, or by providing both the --aws-profile and --aws-account-id flags. You can request all clusters regardless of status by providing the --all flag.`, + Example: `Retrieving all active clusters for a given organizational unit: +osdctl org clusters 123456789AbcDEfGHiJklMnopQR + +Retrieving all active clusters for a given organizational unit in JSON format: +osdctl org clusters 123456789AbcDEfGHiJklMnopQR -o json + +Retrieving all clusters for a given organizational unit regardless of status: +osdctl org clusters 123456789AbcDEfGHiJklMnopQR --all + +Retrieving all active clusters for a given AWS profile: +osdctl org clusters --aws-profile my-aws-profile --aws-account-id 123456789 +`, + Args: cobra.MaximumNArgs(1), SilenceErrors: true, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(SearchClusters(cmd, args)) + orgId := "" + if len(args) > 0 { + orgId = args[0] + } + + status := "" + if !allClustersFlag { + status = statusActive + } + + clusters, err := SearchClusters(orgId, status) + cmdutil.CheckErr(err) + printClusters(clusters) }, } - onlyActive bool = false - awsAccountID string = "" ) -type SubscriptionItems struct { - Subscriptions []Subscription `json:"items"` -} - -type Subscription struct { - ClusterID string `json:"cluster_id"` - DisplayName string `json:"display_name"` - Status string `json:"status"` -} - func init() { // define flags flags := clustersCmd.Flags() flags.BoolVarP( - &onlyActive, - "active", - "", + &allClustersFlag, + "all", + "A", false, - "get organization active clusters", + "get all clusters regardless of status", ) flags.StringVarP( @@ -74,35 +88,41 @@ func init() { AddOutputFlag(flags) } -func SearchClusters(cmd *cobra.Command, args []string) error { - - var err error - if !hasOrgId(args) && !isAWSProfileSearch() { - err = fmt.Errorf("specify either org-id or --aws-profile,--aws-account-id arguments") +func SearchClusters(orgId string, status string) ([]*accountsv1.Subscription, error) { + if orgId == "" && !isAWSProfileSearch() { + return nil, fmt.Errorf("specify either org-id or --aws-profile,--aws-account-id arguments") } - if hasOrgId(args) { - err = searchclustersByOrg(cmd, args[0]) + + if orgId != "" && isAWSProfileSearch() { + return nil, fmt.Errorf("specify either an org id argument or --aws-profile, --aws-account-id arguments") } + if isAWSProfileSearch() { - err = searchClustersByAWSProfile(cmd) + orgIdFromAws, err := getOrganizationIdFromAWSProfile() + if err != nil { + return nil, fmt.Errorf("failed to get org ID from AWS profile: %w", err) + } + orgId = *orgIdFromAws } + clusterSubscriptions, err := searchAllClustersByOrg(orgId, status) if err != nil { - return err + return nil, err } - return nil + + return clusterSubscriptions, nil } -func searchClustersByAWSProfile(cmd *cobra.Command) error { +func getOrganizationIdFromAWSProfile() (*string, error) { awsClient, err := initAWSClient(awsProfile) if err != nil { - return fmt.Errorf("could not create AWS client: %q", err) + return nil, fmt.Errorf("could not create AWS client: %q", err) } parent, err := awsClient.ListParents(&organizations.ListParentsInput{ ChildId: &awsAccountID, }) if err != nil { - return fmt.Errorf("cannot get organization parents: %q", err) + return nil, fmt.Errorf("cannot get organization parents: %q", err) } parentId := *parent.Parents[0].Id @@ -110,34 +130,35 @@ func searchClustersByAWSProfile(cmd *cobra.Command) error { &organizations.DescribeOrganizationalUnitInput{ OrganizationalUnitId: &parentId, }) - if err != nil { - log.Fatalln("cannot get Organizational Unit:", err) + return nil, fmt.Errorf("cannot get Organizational Unit: %w", err) } - searchclustersByOrg(cmd, *result.OrganizationalUnit.Id) - - return nil + return result.OrganizationalUnit.Id, nil } -func searchclustersByOrg(cmd *cobra.Command, orgID string) error { - response, err := getClusters(orgID) - if err != nil { - return fmt.Errorf("invalid input: %q", err) - } +func searchAllClustersByOrg(orgID string, status string) ([]*accountsv1.Subscription, error) { + var clusterSubscriptions []*accountsv1.Subscription + requestPageSize := 100 + morePages := true + for page := 1; morePages; page++ { + clustersData, err := getClusters(orgID, status, page, requestPageSize) + if err != nil { + return nil, fmt.Errorf("encountered an error fetching subscriptions for page %v: %w", page, err) + } - items := SubscriptionItems{} - json.Unmarshal(response.Bytes(), &items) + clustersDataItems := clustersData.Items().Slice() + clusterSubscriptions = append(clusterSubscriptions, clustersDataItems...) - printClusters(items.Subscriptions) - if err != nil { - // If outputing the data errored, there's likely an internal error, so just return the error - return err + if clustersData.Size() < requestPageSize { + morePages = false + } } - return nil + + return clusterSubscriptions, nil } -func getClusters(orgID string) (*sdk.Response, error) { +func getClusters(orgID string, status string, page int, size int) (*accountsv1.SubscriptionsListResponse, error) { // Create OCM client to talk ocmClient, err := utils.CreateConnection() if err != nil { @@ -150,49 +171,48 @@ func getClusters(orgID string) (*sdk.Response, error) { }() // Now get the matching orgs - return sendRequest(createGetClustersRequest(ocmClient, orgID)) + response, err := createGetClustersRequest(ocmClient, orgID, status, page, size).Send() + if err != nil { + return nil, fmt.Errorf("failed to get clusters: %w", err) + } + + return response, nil } -func createGetClustersRequest(ocmClient *sdk.Connection, orgID string) *sdk.Request { +func createGetClustersRequest(ocmClient *sdk.Connection, orgID string, status string, page int, size int) *accountsv1.SubscriptionsListRequest { // Create and populate the request: - request := ocmClient.Get() - subscriptionApiPath := "/api/accounts_mgmt/v1/subscriptions" - - err := arguments.ApplyPathArg(request, subscriptionApiPath) - - if err != nil { - log.Fatalf("Can't parse API path '%s': %v\n", subscriptionApiPath, err) + request := ocmClient.AccountsMgmt().V1().Subscriptions().List().Page(page).Size(size) + searchMessage := fmt.Sprintf(`organization_id='%s'`, orgID) + if status != "" { + searchMessage += fmt.Sprintf(` and status='%s'`, statusActive) } - - formatMessage := fmt.Sprintf( - `search=organization_id='%s'`, - orgID, - ) - arguments.ApplyParameterFlag(request, []string{formatMessage}) + request = request.Search(searchMessage) return request } -func printClusters(items []Subscription) { +func printClusters(items []*accountsv1.Subscription) { if IsJsonOutput() { - subscriptionItems := SubscriptionItems{ - Subscriptions: items, + subscriptions := make([]map[string]string, 0, len(items)) + for _, item := range items { + subscription := map[string]string{ + "cluster_id": item.ClusterID(), + "display_name": item.DisplayName(), + "status": item.Status(), + } + subscriptions = append(subscriptions, subscription) } - PrintJson(subscriptionItems) + PrintJson(subscriptions) } else { table := printer.NewTablePrinter(os.Stdout, 20, 1, 3, ' ') table.AddRow([]string{"DISPLAY NAME", "CLUSTER ID", "STATUS"}) for _, subscription := range items { - if subscription.Status != statusActive && onlyActive { - // skip non active clusters when --active flag set - continue - } table.AddRow([]string{ - subscription.DisplayName, - subscription.ClusterID, - subscription.Status, + subscription.DisplayName(), + subscription.ClusterID(), + subscription.Status(), }) } @@ -202,10 +222,6 @@ func printClusters(items []Subscription) { } -func hasOrgId(args []string) bool { - return len(args) == 1 -} - func isAWSProfileSearch() bool { return awsProfile != "" && awsAccountID != "" }