From 72e70042785a0068ca6cae836ec127a7e588133d Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:37:23 +0530 Subject: [PATCH] Add table aws_rds_db_recommendation Closes #2050 (#2238) Co-authored-by: Ved misra <47312748+misraved@users.noreply.github.com> --- aws/plugin.go | 1 + aws/service.go | 31 +++ aws/table_aws_rds_db_recomendation.go | 260 +++++++++++++++++++++++ docs/tables/aws_rds_db_recommendation.md | 213 +++++++++++++++++++ 4 files changed, 505 insertions(+) create mode 100644 aws/table_aws_rds_db_recomendation.go create mode 100644 docs/tables/aws_rds_db_recommendation.md diff --git a/aws/plugin.go b/aws/plugin.go index 04731681a..6c69f1260 100644 --- a/aws/plugin.go +++ b/aws/plugin.go @@ -416,6 +416,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "aws_rds_db_option_group": tableAwsRDSDBOptionGroup(ctx), "aws_rds_db_parameter_group": tableAwsRDSDBParameterGroup(ctx), "aws_rds_db_proxy": tableAwsRDSDBProxy(ctx), + "aws_rds_db_recommendation": tableAwsRDSDBRecommendation(ctx), "aws_rds_db_snapshot": tableAwsRDSDBSnapshot(ctx), "aws_rds_db_subnet_group": tableAwsRDSDBSubnetGroup(ctx), "aws_rds_reserved_db_instance": tableAwsRDSReservedDBInstance(ctx), diff --git a/aws/service.go b/aws/service.go index 14626889f..3d47ef204 100644 --- a/aws/service.go +++ b/aws/service.go @@ -1149,6 +1149,37 @@ func RDSClient(ctx context.Context, d *plugin.QueryData) (*rds.Client, error) { return rds.NewFromConfig(*cfg), nil } +func RDSDBRecommendationClient(ctx context.Context, d *plugin.QueryData) (*rds.Client, error) { + // RDS DB Recommendation has the same endpoint information in the SDK as RDS, but + // is actually available in less regions. We have to manually remove them + // here. + // Source - https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UserRecommendationsView.html + excludeRegions := []string{ + "af-south-1", // Africa (Cape Town) + "ap-east-1", // Asia Pacific (Hong Kong) + "ap-northeast-3", // Asia Pacific (Osaka) + "ap-southeast-3", // Asia Pacific (Jakarta) + "eu-north-1", // Europe (Stockholm) + "eu-south-1", // Europe (Milan) + "me-central-1", // Middle East (UAE) + "me-south-1", // Middle East (Bahrain) + "us-gov-west-1", // AWS GovCloud (US-West) + "us-gov-east-1", // AWS GovCloud (US-East) + "cn-north-1", // China (Beijing) + "cn-northwest-1", // China (Ningxia) + } + excludeRegions = append(excludeRegions, awsChinaRegions()...) + excludeRegions = append(excludeRegions, awsUsGovRegions()...) + cfg, err := getClientForQuerySupportedRegionWithExclusions(ctx, d, rdsEndpoint.EndpointsID, excludeRegions) + if err != nil { + return nil, err + } + if cfg == nil { + return nil, nil + } + return rds.NewFromConfig(*cfg), nil +} + func RDSDBProxyClient(ctx context.Context, d *plugin.QueryData) (*rds.Client, error) { // RDS DB Proxy has the same endpoint information in the SDK as RDS, but // is actually available in less regions. We have to manually remove them diff --git a/aws/table_aws_rds_db_recomendation.go b/aws/table_aws_rds_db_recomendation.go new file mode 100644 index 000000000..81cc188ab --- /dev/null +++ b/aws/table_aws_rds_db_recomendation.go @@ -0,0 +1,260 @@ +package aws + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" + + rdsv1 "github.com/aws/aws-sdk-go/service/rds" + + "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" +) + +//// TABLE DEFINITION + +func tableAwsRDSDBRecommendation(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "aws_rds_db_recommendation", + Description: "AWS RDS DB Recommendation", + List: &plugin.ListConfig{ + Hydrate: listRDSDBRecommendations, + KeyColumns: plugin.KeyColumnSlice{ + {Name: "recommendation_id", Require: plugin.Optional}, + {Name: "status", Require: plugin.Optional}, + {Name: "severity", Require: plugin.Optional}, + {Name: "type_id", Require: plugin.Optional}, + {Name: "updated_time", Require: plugin.Optional, Operators: []string{">=", "<="}}, + }, + Tags: map[string]string{"service": "rds", "action": "DescribeDBRecommendations"}, + }, + GetMatrixItemFunc: SupportedRegionMatrix(rdsv1.EndpointsID), + Columns: awsRegionalColumns([]*plugin.Column{ + { + Name: "recommendation_id", + Description: "The unique identifier for the recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "resource_arn", + Description: "The Amazon Resource Name (ARN) of the RDS resource associated with the recommendation.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ResourceArn"), + }, + { + Name: "category", + Description: "The category of the recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "status", + Description: "The current status of the recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "created_time", + Description: "The time when the recommendation was created.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "updated_time", + Description: "The time when the recommendation was last updated.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("UpdatedTime").Transform(transform.NullIfZeroValue), + }, + { + Name: "description", + Description: "A detailed description of the recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "detection", + Description: "A short description of the issue identified for this recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "impact", + Description: "A short description that explains the possible impact of an issue.", + Type: proto.ColumnType_STRING, + }, + { + Name: "reason", + Description: "The reason why this recommendation was created.", + Type: proto.ColumnType_STRING, + }, + { + Name: "recommendation", + Description: "A short description of the recommendation to resolve an issue.", + Type: proto.ColumnType_STRING, + }, + { + Name: "severity", + Description: "The severity level of the recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "source", + Description: "The AWS service that generated the recommendations.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type_detection", + Description: "A short description of the recommendation type.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type_id", + Description: "A value that indicates the type of recommendation.", + Type: proto.ColumnType_STRING, + }, + { + Name: "type_recommendation", + Description: "A short description that summarizes the recommendation to fix all the issues of the recommendation type.", + Type: proto.ColumnType_STRING, + }, + { + Name: "additional_info", + Description: "Additional information about the recommendation.", + Type: proto.ColumnType_STRING, + }, + + // JSON fields + { + Name: "issue_details", + Description: "Details of the issue that caused the recommendation.", + Type: proto.ColumnType_JSON, + }, + { + Name: "links", + Description: "A link to documentation that provides additional information about the recommendation.", + Type: proto.ColumnType_JSON, + }, + { + Name: "recommended_actions", + Description: "A list of recommended actions.", + Type: proto.ColumnType_JSON, + }, + + // Steampipe standard columns + { + Name: "title", + Description: resourceInterfaceDescription("title"), + Type: proto.ColumnType_STRING, + Transform: transform.FromField("RecommendationId"), + }, + }), + } +} + +//// LIST FUNCTION + +func listRDSDBRecommendations(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + + // Create Session + svc, err := RDSDBRecommendationClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_rds_db_recommendation.listRDSDBRecommendations", "connection_error", err) + return nil, err + } + // Unsupported region check + if svc == nil { + return nil, nil + } + + // Limiting the results + maxLimit := int32(50) + if d.QueryContext.Limit != nil { + limit := int32(*d.QueryContext.Limit) + if limit < maxLimit { + maxLimit = limit + } + } + + input := &rds.DescribeDBRecommendationsInput{ + MaxRecords: aws.Int32(maxLimit), + } + + if d.Quals["updated_time"] != nil { + for _, q := range d.Quals["updated_time"].Quals { + value := q.Value.GetTimestampValue().AsTime() + if q.Operator == ">=" { + input.LastUpdatedAfter = &value + } + if q.Operator == "<=" { + input.LastUpdatedBefore = &value + } + } + } + + // Build input filter parameter + filters := buildRdsDBRecommendationFilter(d.Quals) + + if len(filters) > 0 { + input.Filters = filters + } + + paginator := rds.NewDescribeDBRecommendationsPaginator(svc, input, func(o *rds.DescribeDBRecommendationsPaginatorOptions) { + o.Limit = maxLimit + o.StopOnDuplicateToken = true + }) + + // List call + for paginator.HasMorePages() { + // apply rate limiting + d.WaitForListRateLimit(ctx) + + output, err := paginator.NextPage(ctx) + if err != nil { + plugin.Logger(ctx).Error("aws_rds_db_recommendation.listRDSDBRecommendations", "api_error", err) + return nil, err + } + + for _, recommendation := range output.DBRecommendations { + d.StreamListItem(ctx, recommendation) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + + return nil, err +} + +//// UTILITY FUNCTIONS + +// Build RDS DB Recommendation list call input filter +func buildRdsDBRecommendationFilter(quals plugin.KeyColumnQualMap) []types.Filter { + filters := make([]types.Filter, 0) + // We have intentionally avoided using the other filter parameters: + // - dbi-resource-id + // - cluster-resource-id + // - pg-arn + // - cluster-pg-arn + // because they are not part of the API response. + filterQuals := map[string]string{ + "recommendation_id": "recommendation-id", + "status": "status", + "severity": "severity", + "type_id": "type-id", + } + + for columnName, filterName := range filterQuals { + if quals[columnName] != nil { + filter := types.Filter{ + Name: aws.String(filterName), + } + value := getQualsValueByColumn(quals, columnName, "string") + val, ok := value.(string) + if ok { + filter.Values = []string{val} + } + filters = append(filters, filter) + } + } + return filters +} diff --git a/docs/tables/aws_rds_db_recommendation.md b/docs/tables/aws_rds_db_recommendation.md new file mode 100644 index 000000000..1a99b992e --- /dev/null +++ b/docs/tables/aws_rds_db_recommendation.md @@ -0,0 +1,213 @@ +--- +title: "Steampipe Table: aws_rds_db_recommendation - Query AWS RDS DB Recommendations using SQL" +description: "Allows users to query AWS RDS DB Recommendations and retrieve valuable information about each recommendation's status, impact, and suggested actions." +--- + +# Table: aws_rds_db_recommendation - Query AWS RDS DB Recommendations using SQL + +The AWS RDS DB Recommendation table provides insights and suggestions for improving the configuration and performance of your RDS DB instances and clusters. This table allows you, as a DevOps engineer, to query specific recommendations, understand their impact, and take appropriate actions to optimize your database setup. + +## Table Usage Guide + +The `aws_rds_db_recommendation` table in Steampipe offers detailed information about recommendations generated by AWS for your RDS DB instances and clusters. You can query this table to gather insights on issues, suggested improvements, and the status of each recommendation. The schema includes various attributes such as recommendation ID, description, severity, creation and update times, and links to further documentation. + +## Examples + +### List all recommendations with high severity +Identify all recommendations with a high severity level to prioritize actions that address the most critical issues in your RDS setup. + +```sql+postgres +select + recommendation_id, + title, + severity, + description, + created_time +from + aws_rds_db_recommendation +where + severity = 'high'; +``` + +```sql+sqlite +select + recommendation_id, + title, + severity, + description, + created_time +from + aws_rds_db_recommendation +where + severity = 'high'; +``` + +### List all recommendations created in the last 30 days +Find recommendations that were created recently to stay updated on new issues and suggested improvements. + +```sql+postgres +select + recommendation_id, + title, + created_time, + description +from + aws_rds_db_recommendation +where + created_time >= now() - interval '30 days'; +``` + +```sql+sqlite +select + recommendation_id, + title, + created_time, + description +from + aws_rds_db_recommendation +where + created_time >= datetime('now', '-30 days'); +``` + +### List recommendations with their recommended actions +Get details of recommendations along with the actions suggested by AWS to resolve the identified issues. + +```sql+postgres +select + recommendation_id, + title, + recommendation, + recommended_actions +from + aws_rds_db_recommendation; +``` + +```sql+sqlite +select + recommendation_id, + title, + recommendation, + recommended_actions +from + aws_rds_db_recommendation; +``` + +### List recommendations by the order of impact +Understand the potential impact of each recommendation to prioritize actions based on the severity of the issues. + +```sql+postgres +select + recommendation_id, + title, + impact, + description +from + aws_rds_db_recommendation +order by + impact desc; +``` + +```sql+sqlite +select + recommendation_id, + title, + impact, + description +from + aws_rds_db_recommendation +order by + impact desc; +``` + +### List recommendations with unresolved status +Identify recommendations that are still unresolved to track pending issues that need attention. + +```sql+postgres +select + recommendation_id, + title, + status, + updated_time +from + aws_rds_db_recommendation +where + status != 'resolved'; +``` + +```sql+sqlite +select + recommendation_id, + title, + status, + updated_time +from + aws_rds_db_recommendation +where + status != 'resolved'; +``` + +### Get the issue details that triggered the recommendation +Analyze performance issues for the recommendation. + +```sql+postgres +select + recommendation_id, + issue_details -> 'PerformanceIssueDetails' ->> 'Analysis' as analysis, + issue_details -> 'PerformanceIssueDetails' ->> 'EndTime' as end_time, + issue_details -> 'PerformanceIssueDetails' ->> 'StartTime' as start_time, + issue_details -> 'PerformanceIssueDetails' -> 'Metrics' as metrics +from + aws_rds_db_recommendation +where + issue_details is not null; +``` + +```sql+sqlite +select + recommendation_id, + json_extract(issue_details, '$.PerformanceIssueDetails.Analysis') as analysis, + json_extract(issue_details, '$.PerformanceIssueDetails.EndTime') as end_time, + json_extract(issue_details, '$.PerformanceIssueDetails.StartTime') as start_time, + json_extract(issue_details, '$.PerformanceIssueDetails.Metrics') as metrics +from + aws_rds_db_recommendation +where + issue_details is not null; +``` + +### List the recommended action details for a particular recommendation +View all relevant information clearly and organized, facilitating better planning and decision-making for database maintenance and optimization. + +```sql+postgres +select + recommendation_id, + action ->> 'Title' as title, + action ->> 'Status' as status, + action ->> 'ActionId' as action_id, + action ->> 'Operation' as operation, + action -> 'ApplyModes' as apply_modes, + action -> 'Parameters' as parameters, + action ->> 'Description' as description, + action ->> 'IssueDetails' as issue_details, + action -> 'ContextAttributes' as context_attributes +from + aws_rds_db_recommendation, + jsonb_array_elements(recommended_actions) as action; +``` + +```sql+sqlite +select + recommendation_id, + json_extract(action.value, '$.Title') as title, + json_extract(action.value, '$.Status') as status, + json_extract(action.value, '$.ActionId') as action_id, + json_extract(action.value, '$.Operation') as operation, + json_extract(action.value, '$.ApplyModes') as apply_modes, + json_extract(action.value, '$.Parameters') as parameters, + json_extract(action.value, '$.Description') as description, + json_extract(action.value, '$.IssueDetails') as issue_details, + json_extract(action.value, '$.ContextAttributes') as context_attributes +from + aws_rds_db_recommendation, + json_each(recommended_actions) as action; +```