Repository: turbot/steampipe-plugin-aws (language: go)

Issue:
Create a table for AWS Cost Optimization Hub recommendations
**Is your feature request related to a problem? Please describe.**
AWS has the Cost Optimization Hub (COH) under Billing and Cost Management to provide recommendations of where to save money.  Example recommendations are around moving to a different EBS volume type, decreasing memory for a Lambda, or purchasing a compute saving plan.  This request is to ask for COH recommendations to be included in a Steampipe table.  This could be from the payor account of an AWS Organization or from all member accounts.

An example CLI call is with "aws cost-optimization-hub list-recommendations"

**Describe the solution you'd like**
One additional table, with a row for each account and a JSON structure of each finding and its cost savings potentials.

**Describe alternatives you've considered**
Our alternative to Pipes is to use a Lambda with boto3 calls to the payor account, or to all accounts, to collect the recommendations and display in a Quicksight report.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion / hints:
Hello @2020python, welcome to Steampipe 👍 !!

Thanks for raising the table issue with good details on its usage.

Have you tried out https://hub.powerpipe.io/mods/turbot/aws_thrifty? It provides a good set of controls and benchmarks for cost and resource management.
> Hello [@2020python](https://github.com/2020python), welcome to Steampipe 👍 !!
> 
> Thanks for raising the table issue with good details on its usage.
> 
> Have you tried out https://hub.powerpipe.io/mods/turbot/aws_thrifty? It provides a good set of controls and benchmarks for cost and resource management.

Hi @misraved, yes we have AWS Thrifty installed and it has a lot of great cost related content.  However, there are items in the AWS Cost Optimization Hub that I can't find directly in Thrifty, such as for one account they use dozens of EBS volumes for EKS (Kubernetes), and one set of recommendations is to move from the more expensive io1 to the io2 EOT type, saving about $1000 per volume.  This detail is not available in Thrifty.  This is just one example.   So this request is asking for COH output specifically so we can get the complete list of recommendations, preferring Pipes over writing our own Lambda boto3 script outside of Pipes.

Fix patch:
diff --git a/aws/plugin.go b/aws/plugin.go
index c9ea62e0f..46d1732a0 100644
--- a/aws/plugin.go
+++ b/aws/plugin.go
@@ -179,6 +179,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
 			"aws_cost_by_tag":                                              tableAwsCostByTag(ctx),
 			"aws_cost_forecast_daily":                                      tableAwsCostForecastDaily(ctx),
 			"aws_cost_forecast_monthly":                                    tableAwsCostForecastMonthly(ctx),
+			"aws_costoptimizationhub_recommendation":                       tableAwsCostOptimizationHubRecommendation(ctx),
 			"aws_cost_usage":                                               tableAwsCostAndUsage(ctx),
 			"aws_dax_cluster":                                              tableAwsDaxCluster(ctx),
 			"aws_dax_parameter":                                            tableAwsDaxParameter(ctx),
diff --git a/aws/service.go b/aws/service.go
index 040916daa..f1b6a6dae 100644
--- a/aws/service.go
+++ b/aws/service.go
@@ -50,6 +50,7 @@ import (
 	"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
 	"github.com/aws/aws-sdk-go-v2/service/configservice"
 	"github.com/aws/aws-sdk-go-v2/service/costexplorer"
+	"github.com/aws/aws-sdk-go-v2/service/costoptimizationhub"
 	"github.com/aws/aws-sdk-go-v2/service/databasemigrationservice"
 	"github.com/aws/aws-sdk-go-v2/service/dax"
 	"github.com/aws/aws-sdk-go-v2/service/directoryservice"
@@ -570,6 +571,15 @@ func CostExplorerClient(ctx context.Context, d *plugin.QueryData) (*costexplorer
 	return costexplorer.NewFromConfig(*cfg), nil
 }
 
+func CostOptimizationHubClient(ctx context.Context, d *plugin.QueryData) (*costoptimizationhub.Client, error) {
+	cfg, err := getClientForDefaultRegion(ctx, d)
+	if err != nil {
+		return nil, err
+	}
+	return costoptimizationhub.NewFromConfig(*cfg), nil
+}
+
+
 func DatabaseMigrationClient(ctx context.Context, d *plugin.QueryData) (*databasemigrationservice.Client, error) {
 	cfg, err := getClientForQueryRegion(ctx, d)
 	if err != nil {
diff --git a/aws/table_aws_costoptimizationhub_recommendation.go b/aws/table_aws_costoptimizationhub_recommendation.go
new file mode 100644
index 000000000..a3464b407
--- /dev/null
+++ b/aws/table_aws_costoptimizationhub_recommendation.go
@@ -0,0 +1,350 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/aws/aws-sdk-go-v2/service/costoptimizationhub"
+	"github.com/aws/aws-sdk-go-v2/service/costoptimizationhub/types"
+
+	"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
+	"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
+	"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
+)
+
+func tableAwsCostOptimizationHubRecommendation(_ context.Context) *plugin.Table {
+	return &plugin.Table{
+		Name:        "aws_costoptimizationhub_recommendation",
+		Description: "AWS Cost Optimization Hub Recommendation",
+		List: &plugin.ListConfig{
+			Hydrate: listCostOptimizationHubRecommendations,
+			KeyColumns: plugin.KeyColumnSlice{
+				{
+					Name:    "recommendation_account_id",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "action_type",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "implementation_effort",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "recommendation_id",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "resource_region",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "resource_arn",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "resource_id",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "current_resource_type",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "recommended_resource_type",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "restart_needed",
+					Require: plugin.Optional,
+				},
+				{
+					Name:    "rollback_possible",
+					Require: plugin.Optional,
+				},
+			},
+			Tags: map[string]string{"service": "cost-optimization-hub", "action": "ListRecommendations"},
+		},
+		Columns: awsGlobalRegionColumns(
+			[]*plugin.Column{
+				{
+					Name:        "recommendation_id",
+					Description: "The ID for the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "resource_arn",
+					Description: "The Amazon Resource Name (ARN) for the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "resource_id",
+					Description: "The resource ID for the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+
+				// We have a common column named "account_id" for all the tables that represents current caller account ID, so renamed it to "recommendation_account_id" to avoid ambiguity.
+				{
+					Name:        "recommendation_account_id",
+					Description: "The account that the recommendation is for.",
+					Type:        proto.ColumnType_STRING,
+					Transform:   transform.FromField("AccountId"),
+				},
+				{
+					Name:        "action_type",
+					Description: "The type of tasks that can be carried out by this action.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "currency_code",
+					Description: "The currency code used for the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "current_resource_summary",
+					Description: "Describes the current resource.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "current_resource_type",
+					Description: "The current resource type.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "estimated_monthly_cost",
+					Description: "The estimated monthly cost for the recommendation.",
+					Type:        proto.ColumnType_DOUBLE,
+				},
+				{
+					Name:        "estimated_monthly_savings",
+					Description: "The estimated monthly savings amount for the recommendation.",
+					Type:        proto.ColumnType_DOUBLE,
+				},
+				{
+					Name:        "estimated_savings_percentage",
+					Description: "The estimated savings percentage relative to the total cost over the cost calculation lookback period.",
+					Type:        proto.ColumnType_DOUBLE,
+				},
+				{
+					Name:        "implementation_effort",
+					Description: "The effort required to implement the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "last_refresh_timestamp",
+					Description: "The time when the recommendation was last generated.",
+					Type:        proto.ColumnType_TIMESTAMP,
+				},
+				{
+					Name:        "recommendation_lookback_period_in_days",
+					Description: "The lookback period that's used to generate the recommendation.",
+					Type:        proto.ColumnType_INT,
+				},
+				{
+					Name:        "recommended_resource_summary",
+					Description: "Describes the recommended resource.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "recommended_resource_type",
+					Description: "Describes the recommended resource.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "resource_region",
+					Description: "The Amazon Web Services Region of the resource.",
+					Type:        proto.ColumnType_STRING,
+					Transform:   transform.FromField("Region"),
+				},
+				{
+					Name:        "restart_needed",
+					Description: "Whether or not implementing the recommendation requires a restart.",
+					Type:        proto.ColumnType_BOOL,
+				},
+				{
+					Name:        "rollback_possible",
+					Description: "Whether or not implementing the recommendation can be rolled back.",
+					Type:        proto.ColumnType_BOOL,
+				},
+				{
+					Name:        "source",
+					Description: "The source of the recommendation.",
+					Type:        proto.ColumnType_STRING,
+				},
+				{
+					Name:        "current_resource_details",
+					Description: "The details for the resource.",
+					Type:        proto.ColumnType_JSON,
+					Hydrate:     getCostOptimizationHubRecommendations,
+					Transform:   transform.FromField("CurrentResourceDetails"),
+				},
+				{
+					Name:        "recommended_resource_details",
+					Description: "The details about the recommended resource.",
+					Type:        proto.ColumnType_JSON,
+					Hydrate:     getCostOptimizationHubRecommendations,
+					Transform:   transform.FromField("RecommendedResourceDetails"),
+				},
+				{
+					Name:        "tags_src",
+					Description: "A list of tags assigned to the recommendation.",
+					Type:        proto.ColumnType_JSON,
+				},
+
+				// Steampipe standard columns
+				{
+					Name:        "tags",
+					Description: resourceInterfaceDescription("tags"),
+					Type:        proto.ColumnType_JSON,
+					Transform:   transform.From(costOptimizationRecommendationTurbotTags),
+				},
+				{
+					Name:        "title",
+					Description: resourceInterfaceDescription("title"),
+					Type:        proto.ColumnType_STRING,
+					Transform:   transform.FromField("RecommendationId"),
+				},
+			}),
+	}
+}
+
+//// LIST FUNCTION
+
+func listCostOptimizationHubRecommendations(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) {
+	params := buildCostOptimizationHubRecommendationInputFromQuals(d.Quals)
+
+	// Create Client
+	svc, err := CostOptimizationHubClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_costoptimizationhub_recommendation.listCostOptimizationHubRecommendations", "connection_error", err)
+		return nil, err
+	}
+
+	// Limiting the results
+	maxLimit := int32(1000)
+	if d.QueryContext.Limit != nil {
+		limit := int32(*d.QueryContext.Limit)
+		if limit < maxLimit {
+			maxLimit = limit
+		}
+	}
+
+	input := &costoptimizationhub.ListRecommendationsInput{
+		MaxResults: &maxLimit,
+	}
+
+	if params != nil {
+		input.Filter = params
+	}
+
+	paginator := costoptimizationhub.NewListRecommendationsPaginator(svc, input, func(o *costoptimizationhub.ListRecommendationsPaginatorOptions) {
+		o.Limit = maxLimit
+		o.StopOnDuplicateToken = true
+	})
+
+	for paginator.HasMorePages() {
+		d.WaitForListRateLimit(ctx)
+
+		output, err := paginator.NextPage(ctx)
+		if err != nil {
+			plugin.Logger(ctx).Error("aws_costoptimizationhub_recommendation.listCostOptimizationHubRecommendations", "api_error", err)
+			return nil, err
+		}
+
+		for _, item := range output.Items {
+			d.StreamListItem(ctx, item)
+
+			// Context can be cancelled due to manual cancellation or the limit has been hit
+			if d.RowsRemaining(ctx) == 0 {
+				return nil, nil
+			}
+		}
+	}
+
+	return nil, nil
+}
+
+//// HYDRATE FUNCTIONS
+
+func getCostOptimizationHubRecommendations(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
+	recommendation := h.Item.(types.Recommendation)
+
+	// Create Client
+	svc, err := CostOptimizationHubClient(ctx, d)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_costoptimizationhub_recommendation.getCostOptimizationHubRecommendations", "connection_error", err)
+		return nil, err
+	}
+
+	input := &costoptimizationhub.GetRecommendationInput{
+		RecommendationId: recommendation.RecommendationId,
+	}
+
+	result, err := svc.GetRecommendation(ctx, input)
+	if err != nil {
+		plugin.Logger(ctx).Error("aws_costoptimizationhub_recommendation.getCostOptimizationHubRecommendations", "api_error", err)
+		return nil, err
+	}
+
+	return result, nil
+}
+
+//// TRANSFORM FUNCTIONS
+
+func costOptimizationRecommendationTurbotTags(_ context.Context, d *transform.TransformData) (interface{}, error) {
+	r := d.HydrateItem.(types.Recommendation)
+	var turbotTagsMap map[string]string
+	if r.Tags != nil {
+		turbotTagsMap = map[string]string{}
+		for _, i := range r.Tags {
+			turbotTagsMap[*i.Key] = *i.Value
+		}
+	}
+	return turbotTagsMap, nil
+}
+
+////  Build input parameter fot list API call
+
+func buildCostOptimizationHubRecommendationInputFromQuals(quals plugin.KeyColumnQualMap) *types.Filter {
+	param := &types.Filter{}
+	filterQuals := []string{"recommendation_account_id", "action_type", "implementation_effort", "recommendation_id", "resource_region", "resource_arn", "resource_id", "current_resource_type", "recommended_resource_type", "restart_needed", "rollback_possible"}
+
+	for _, columnName := range filterQuals {
+		if quals[columnName] != nil {
+			switch columnName {
+			case "restart_needed", "rollback_possible":
+				value := getQualsValueByColumn(quals, columnName, "boolean")
+				val := value.(string) == "true"
+				if columnName == "restart_needed" {
+					param.RestartNeeded = &val
+				}
+				if columnName == "rollback_possible" {
+					param.RollbackPossible = &val
+				}
+			default:
+				value := getQualsValueByColumn(quals, columnName, "string")
+				switch columnName {
+				case "recommendation_account_id":
+					param.AccountIds = []string{fmt.Sprint(value)}
+				case "recommendation_id":
+					param.RecommendationIds = []string{fmt.Sprint(value)}
+				case "resource_region":
+					param.Regions = []string{fmt.Sprint(value)}
+				case "resource_arn":
+					param.ResourceArns = []string{fmt.Sprint(value)}
+				case "resource_id":
+					param.ResourceIds = []string{fmt.Sprint(value)}
+				case "implementation_effort":
+					param.ImplementationEfforts = []types.ImplementationEffort{types.ImplementationEffort(value.(string))}
+				case "action_type":
+					param.ActionTypes = []types.ActionType{types.ActionType(value.(string))}
+				case "current_resource_type", "recommended_resource_type":
+					param.ResourceTypes = []types.ResourceType{types.ResourceType(value.(string))}
+				}
+			}
+		}
+	}
+
+	return param
+}
diff --git a/docs/tables/aws_costoptimizationhub_recommendation.md b/docs/tables/aws_costoptimizationhub_recommendation.md
new file mode 100644
index 000000000..ac60785e7
--- /dev/null
+++ b/docs/tables/aws_costoptimizationhub_recommendation.md
@@ -0,0 +1,184 @@
+---
+title: "Steampipe Table: aws_costoptimizationhub_recommendation - Query AWS Cost Optimization Recommendations using SQL"
+description: "Allows users to query AWS Cost Optimization Hub Recommendations to obtain insights on cost-saving opportunities, resource configuration, and associated metadata."
+---
+
+# Table: aws_costoptimizationhub_recommendation - Query AWS Cost Optimization Recommendations using SQL
+
+The AWS Cost Optimization Hub provides recommendations for reducing costs and optimizing the usage of AWS resources. These recommendations are based on usage patterns, cost analysis, and resource configurations, helping organizations achieve better cost efficiency.
+
+## Table Usage Guide
+
+The `aws_costoptimizationhub_recommendation` table in Steampipe allows you to query detailed cost optimization recommendations. This table helps DevOps engineers, cost analysts, or financial professionals identify potential savings, understand implementation efforts, and track the effectiveness of recommendations.
+
+The schema outlines various attributes of the cost optimization recommendations, including estimated savings, resource details, recommendation types, and implementation efforts. It also provides timestamps for the last refresh and additional metadata such as tags and ARNs.
+
+**Important Notes**
+- This table supports optional quals. Queries with optional quals are optimized to use additional filtering provided by the AWS API function to narrow down the results for better query performance.. Optional quals are supported for the following columns:
+  - `recommendation_account_id`
+  - `action_type`
+  - `implementation_effort`
+  - `recommendation_id`
+  - `resource_region`
+  - `resource_arn`
+  - `resource_id`
+  - `current_resource_type`
+  - `recommended_resource_type`
+  - `restart_needed`
+  - `rollback_possible`
+
+## Examples
+
+### Basic info
+Retrieve the basic details of cost optimization recommendations, including the resource and estimated savings.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  estimated_monthly_savings,
+  estimated_savings_percentage,
+  implementation_effort
+from
+  aws_costoptimizationhub_recommendation;
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  estimated_monthly_savings,
+  estimated_savings_percentage,
+  implementation_effort
+from
+  aws_costoptimizationhub_recommendation;
+```
+
+### List recommendations with significant savings
+Identify recommendations where the estimated savings percentage is greater than 50%. This helps prioritize high-impact cost optimization opportunities.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  estimated_monthly_savings,
+  estimated_savings_percentage
+from
+  aws_costoptimizationhub_recommendation
+where
+  estimated_savings_percentage > 50;
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  estimated_monthly_savings,
+  estimated_savings_percentage
+from
+  aws_costoptimizationhub_recommendation
+where
+  estimated_savings_percentage > 50;
+```
+
+### List recommendations requiring a resource restart
+Find recommendations that require a restart to implement. This query helps in planning implementation efforts and minimizing downtime.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  implementation_effort,
+  restart_needed
+from
+  aws_costoptimizationhub_recommendation
+where
+  restart_needed = true;
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  implementation_effort,
+  restart_needed
+from
+  aws_costoptimizationhub_recommendation
+where
+  restart_needed = 1;
+```
+
+### Get recommendations by resource type
+Filter recommendations based on specific resource types, such as EC2 or RDS, to analyze opportunities for optimizing particular services.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  current_resource_type,
+  recommended_resource_type
+from
+  aws_costoptimizationhub_recommendation
+where
+  current_resource_type = 'EC2';
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  current_resource_type,
+  recommended_resource_type
+from
+  aws_costoptimizationhub_recommendation
+where
+  current_resource_type = 'EC2';
+```
+
+### List recommendations refreshed in the last 30 days
+Track recently updated recommendations to stay up-to-date with the latest cost optimization insights.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  last_refresh_timestamp
+from
+  aws_costoptimizationhub_recommendation
+where
+  last_refresh_timestamp > now() - interval '30 days';
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  last_refresh_timestamp
+from
+  aws_costoptimizationhub_recommendation
+where
+  last_refresh_timestamp > datetime('now','-30 days');
+```
+
+### Get the tags associated with a recommendation
+Retrieve tags assigned to recommendations to better organize and manage resources.
+
+```sql+postgres
+select
+  recommendation_id,
+  resource_id,
+  jsonb_each_text(tags) as tag
+from
+  aws_costoptimizationhub_recommendation;
+```
+
+```sql+sqlite
+select
+  recommendation_id,
+  resource_id,
+  json_each(tags_src).key as tag_key,
+  json_each(tags_src).value as tag_value
+from
+  aws_costoptimizationhub_recommendation;
+```
+
diff --git a/go.mod b/go.mod
index 4f7ef1e83..afa6da6b2 100644
--- a/go.mod
+++ b/go.mod
@@ -43,6 +43,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.36.3
 	github.com/aws/aws-sdk-go-v2/service/configservice v1.46.4
 	github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1
+	github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.7
 	github.com/aws/aws-sdk-go-v2/service/databasemigrationservice v1.38.4
 	github.com/aws/aws-sdk-go-v2/service/dax v1.19.4
 	github.com/aws/aws-sdk-go-v2/service/directoryservice v1.24.4
@@ -118,6 +119,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/servicequotas v1.21.4
 	github.com/aws/aws-sdk-go-v2/service/ses v1.22.4
 	github.com/aws/aws-sdk-go-v2/service/sfn v1.26.4
+	github.com/aws/aws-sdk-go-v2/service/shield v1.25.7
 	github.com/aws/aws-sdk-go-v2/service/simspaceweaver v1.10.4
 	github.com/aws/aws-sdk-go-v2/service/sns v1.29.4
 	github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4
@@ -132,7 +134,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/wafv2 v1.48.2
 	github.com/aws/aws-sdk-go-v2/service/wellarchitected v1.29.4
 	github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.4
-	github.com/aws/smithy-go v1.20.4
+	github.com/aws/smithy-go v1.22.1
 	github.com/gocarina/gocsv v0.0.0-20201208093247-67c824bc04d4
 	github.com/goccy/go-yaml v1.11.3
 	github.com/golang/protobuf v1.5.4
@@ -147,7 +149,6 @@ require (
 require golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 
 require (
-	github.com/aws/aws-sdk-go-v2/service/shield v1.25.7 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
diff --git a/go.sum b/go.sum
index db85cbafe..23f0d205e 100644
--- a/go.sum
+++ b/go.sum
@@ -291,6 +291,8 @@ github.com/aws/aws-sdk-go-v2/service/configservice v1.46.4 h1:bmVA/LmysEu6gOplmz
 github.com/aws/aws-sdk-go-v2/service/configservice v1.46.4/go.mod h1:WCD4Psga99kZmdqPGJ88SURa6UMa4WgqpqzY5vP2ZS0=
 github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1 h1:xjhk+io+kPtDOG5RizvHlkGKET3dxRBzorLdPPkpZQc=
 github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1/go.mod h1:uLOg0o57AyQQhZGtUKIlcBJOKE53mO9bXKyrM9dFhy4=
+github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.7 h1:MGeK6VW2qK2jY5mG0a5VyJ9AFwxjQUumkZcUK/C1UDA=
+github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.7/go.mod h1:dD0mbm64tfE2DRlIVEKg0dXb9qyf+qZtNitsR8CvMVM=
 github.com/aws/aws-sdk-go-v2/service/databasemigrationservice v1.38.4 h1:ot9PKavvbeEg3eofQdkpJWrf8DR90S9wx1OirBUComU=
 github.com/aws/aws-sdk-go-v2/service/databasemigrationservice v1.38.4/go.mod h1:hTZS15Gghi40UxU03Cv09Qr2tXgoQrZOSGY6oaNUNAg=
 github.com/aws/aws-sdk-go-v2/service/dax v1.19.4 h1:S3mvtYjRVVsg1R4EuV1LWZUiD72t+pfnBbK8TL7zEmo=
@@ -487,8 +489,8 @@ github.com/aws/aws-sdk-go-v2/service/wellarchitected v1.29.4 h1:OuFs453KXWTLBkem
 github.com/aws/aws-sdk-go-v2/service/wellarchitected v1.29.4/go.mod h1:MRT/P9Cwn+7xCCVpD1sTvUESiWMAc9hA+FooRsW5fe8=
 github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.4 h1:SvHYikdxmnyptMebU3zFfXbfU96SHzdUX+KXqa6pjYE=
 github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.4/go.mod h1:1XK49PATLHBd7mpKqO91GqRuV7bEsmyQ8Lslvn3fFj4=
-github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
-github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
+github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
+github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=


Test patch: