diff --git a/cmd/prometheus/testdata/features.json b/cmd/prometheus/testdata/features.json index 2ca8768cc3..60e6b65b40 100644 --- a/cmd/prometheus/testdata/features.json +++ b/cmd/prometheus/testdata/features.json @@ -140,13 +140,13 @@ "-": true, "/": true, "<": true, + "": true, - ">=": true, ">/": true, - "=": true, "@": true, "^": true, "and": true, @@ -206,6 +206,7 @@ "openstack": true, "ovhcloud": true, "puppetdb": true, + "rds": true, "scaleway": true, "serverset": true, "stackit": true, diff --git a/discovery/aws/aws.go b/discovery/aws/aws.go index f0f9c3d4df..81afe5ddda 100644 --- a/discovery/aws/aws.go +++ b/discovery/aws/aws.go @@ -48,6 +48,7 @@ const ( RoleElasticache Role = "elasticache" RoleLightsail Role = "lightsail" RoleMSK Role = "msk" + RoleRDS Role = "rds" ) // UnmarshalYAML implements the yaml.Unmarshaler interface. @@ -56,7 +57,7 @@ func (c *Role) UnmarshalYAML(unmarshal func(any) error) error { return err } switch *c { - case RoleEC2, RoleECS, RoleElasticache, RoleLightsail, RoleMSK: + case RoleEC2, RoleECS, RoleElasticache, RoleLightsail, RoleMSK, RoleRDS: return nil default: return fmt.Errorf("unknown AWS SD role %q", *c) @@ -92,6 +93,7 @@ type SDConfig struct { *ElasticacheSDConfig `yaml:"-"` *LightsailSDConfig `yaml:"-"` *MSKSDConfig `yaml:"-"` + *RDSSDConfig `yaml:"-"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for SDConfig. @@ -264,6 +266,37 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(any) error) error { if c.Clusters != nil { c.MSKSDConfig.Clusters = c.Clusters } + case RoleRDS: + if c.RDSSDConfig == nil { + rdsConfig := DefaultRDSSDConfig + c.RDSSDConfig = &rdsConfig + } + c.RDSSDConfig.HTTPClientConfig = c.HTTPClientConfig + c.RDSSDConfig.Region = c.Region + if c.Endpoint != "" { + c.RDSSDConfig.Endpoint = c.Endpoint + } + if c.AccessKey != "" { + c.RDSSDConfig.AccessKey = c.AccessKey + } + if c.SecretKey != "" { + c.RDSSDConfig.SecretKey = c.SecretKey + } + if c.Profile != "" { + c.RDSSDConfig.Profile = c.Profile + } + if c.RoleARN != "" { + c.RDSSDConfig.RoleARN = c.RoleARN + } + if c.Port != 0 { + c.RDSSDConfig.Port = c.Port + } + if c.RefreshInterval != 0 { + c.RDSSDConfig.RefreshInterval = c.RefreshInterval + } + if c.Clusters != nil { + c.RDSSDConfig.Clusters = c.Clusters + } default: return fmt.Errorf("unknown AWS SD role %q", c.Role) } @@ -301,6 +334,9 @@ func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Di case RoleMSK: opts.Metrics = &mskMetrics{refreshMetrics: awsMetrics.refreshMetrics} return NewMSKDiscovery(c.MSKSDConfig, opts) + case RoleRDS: + opts.Metrics = &rdsMetrics{refreshMetrics: awsMetrics.refreshMetrics} + return NewRDSDiscovery(c.RDSSDConfig, opts) default: return nil, fmt.Errorf("unknown AWS SD role %q", c.Role) } diff --git a/discovery/aws/metrics_rds.go b/discovery/aws/metrics_rds.go new file mode 100644 index 0000000000..b8e29cfcec --- /dev/null +++ b/discovery/aws/metrics_rds.go @@ -0,0 +1,32 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "github.com/prometheus/prometheus/discovery" +) + +type rdsMetrics struct { + refreshMetrics discovery.RefreshMetricsInstantiator +} + +var _ discovery.DiscovererMetrics = (*rdsMetrics)(nil) + +// Register implements discovery.DiscovererMetrics. +func (*rdsMetrics) Register() error { + return nil +} + +// Unregister implements discovery.DiscovererMetrics. +func (*rdsMetrics) Unregister() {} diff --git a/discovery/aws/rds.go b/discovery/aws/rds.go new file mode 100644 index 0000000000..ba700f8763 --- /dev/null +++ b/discovery/aws/rds.go @@ -0,0 +1,999 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net" + "strconv" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + awsConfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/aws/aws-sdk-go-v2/service/rds/types" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/config" + "github.com/prometheus/common/model" + "github.com/prometheus/common/promslog" + "golang.org/x/sync/errgroup" + + "github.com/prometheus/prometheus/discovery" + "github.com/prometheus/prometheus/discovery/refresh" + "github.com/prometheus/prometheus/discovery/targetgroup" + "github.com/prometheus/prometheus/util/strutil" +) + +const ( + rdsLabel = model.MetaLabelPrefix + "rds_" + + // DB cluster labels. + rdsLabelCluster = rdsLabel + "cluster_" + rdsLabelClusterActivityStreamKinesisStreamName = rdsLabelCluster + "activity_stream_kinesis_stream_name" + rdsLabelClusterActivityStreamKMSKeyID = rdsLabelCluster + "activity_stream_kms_key_id" + rdsLabelClusterActivityStreamMode = rdsLabelCluster + "activity_stream_mode" + rdsLabelClusterActivityStreamStatus = rdsLabelCluster + "activity_stream_status" + rdsLabelClusterAllocatedStorage = rdsLabelCluster + "allocated_storage" + rdsLabelClusterAutoMinorVersionUpgrade = rdsLabelCluster + "auto_minor_version_upgrade" + rdsLabelClusterAutomaticRestartTime = rdsLabelCluster + "automatic_restart_time" + rdsLabelClusterAwsBackupRecoveryPointArn = rdsLabelCluster + "aws_backup_recovery_point_arn" + rdsLabelClusterBacktrackConsumedChangeRecords = rdsLabelCluster + "backtrack_consumed_change_records" + rdsLabelClusterBacktrackWindow = rdsLabelCluster + "backtrack_window" + rdsLabelClusterBackupRetentionPeriod = rdsLabelCluster + "backup_retention_period" + rdsLabelClusterCapacity = rdsLabelCluster + "capacity" + rdsLabelClusterCharacterSetName = rdsLabelCluster + "character_set_name" + rdsLabelClusterCloneGroupID = rdsLabelCluster + "clone_group_id" + rdsLabelClusterClusterCreateTime = rdsLabelCluster + "cluster_create_time" + rdsLabelClusterClusterScalabilityType = rdsLabelCluster + "cluster_scalability_type" + rdsLabelClusterCopyTagsToSnapshot = rdsLabelCluster + "copy_tags_to_snapshot" + rdsLabelClusterCrossAccountClone = rdsLabelCluster + "cross_account_clone" + rdsLabelClusterDBClusterArn = rdsLabelCluster + "arn" + rdsLabelClusterDBClusterIdentifier = rdsLabelCluster + "identifier" + rdsLabelClusterDBClusterInstanceClass = rdsLabelCluster + "instance_class" + rdsLabelClusterDBClusterParameterGroup = rdsLabelCluster + "parameter_group" + rdsLabelClusterDBSubnetGroup = rdsLabelCluster + "subnet_group" + rdsLabelClusterDBSystemID = rdsLabelCluster + "db_system_id" + rdsLabelClusterDatabaseInsightsMode = rdsLabelCluster + "database_insights_mode" + rdsLabelClusterDatabaseName = rdsLabelCluster + "database_name" + rdsLabelClusterDBClusterResourceID = rdsLabelCluster + "resource_id" + rdsLabelClusterDeletionProtection = rdsLabelCluster + "deletion_protection" + rdsLabelClusterEarliestBacktrackTime = rdsLabelCluster + "earliest_backtrack_time" + rdsLabelClusterEarliestRestorableTime = rdsLabelCluster + "earliest_restorable_time" + rdsLabelClusterEndpoint = rdsLabelCluster + "endpoint" + rdsLabelClusterEngine = rdsLabelCluster + "engine" + rdsLabelClusterEngineLifecycleSupport = rdsLabelCluster + "engine_lifecycle_support" + rdsLabelClusterEngineMode = rdsLabelCluster + "engine_mode" + rdsLabelClusterEngineVersion = rdsLabelCluster + "engine_version" + rdsLabelClusterGlobalClusterIdentifier = rdsLabelCluster + "global_cluster_identifier" + rdsLabelClusterGlobalWriteForwardingRequested = rdsLabelCluster + "global_write_forwarding_requested" + rdsLabelClusterGlobalWriteForwardingStatus = rdsLabelCluster + "global_write_forwarding_status" + rdsLabelClusterHostedZoneID = rdsLabelCluster + "hosted_zone_id" + rdsLabelClusterHTTPEndpointEnabled = rdsLabelCluster + "http_endpoint_enabled" + rdsLabelClusterIAMDatabaseAuthenticationEnabled = rdsLabelCluster + "iam_database_authentication_enabled" + rdsLabelClusterIOOptimizedNextAllowedModificationTime = rdsLabelCluster + "io_optimized_next_allowed_modification_time" + rdsLabelClusterIops = rdsLabelCluster + "iops" + rdsLabelClusterKMSKeyID = rdsLabelCluster + "kms_key_id" + rdsLabelClusterLatestRestorableTime = rdsLabelCluster + "latest_restorable_time" + rdsLabelClusterLocalWriteForwardingStatus = rdsLabelCluster + "local_write_forwarding_status" + rdsLabelClusterMasterUsername = rdsLabelCluster + "master_username" + rdsLabelClusterMonitoringInterval = rdsLabelCluster + "monitoring_interval" + rdsLabelClusterMonitoringRoleArn = rdsLabelCluster + "monitoring_role_arn" + rdsLabelClusterMultiAZ = rdsLabelCluster + "multi_az" + rdsLabelClusterNetworkType = rdsLabelCluster + "network_type" + rdsLabelClusterPercentProgress = rdsLabelCluster + "percent_progress" + rdsLabelClusterPerformanceInsightsEnabled = rdsLabelCluster + "performance_insights_enabled" + rdsLabelClusterPerformanceInsightsKMSKeyID = rdsLabelCluster + "performance_insights_kms_key_id" + rdsLabelClusterPerformanceInsightsRetentionPeriod = rdsLabelCluster + "performance_insights_retention_period" + rdsLabelClusterPort = rdsLabelCluster + "port" + rdsLabelClusterPreferredBackupWindow = rdsLabelCluster + "preferred_backup_window" + rdsLabelClusterPreferredMaintenanceWindow = rdsLabelCluster + "preferred_maintenance_window" + rdsLabelClusterPubliclyAccessible = rdsLabelCluster + "publicly_accessible" + rdsLabelClusterReaderEndpoint = rdsLabelCluster + "reader_endpoint" + rdsLabelClusterReplicationSourceIdentifier = rdsLabelCluster + "replication_source_identifier" + rdsLabelClusterServerlessV2PlatformVersion = rdsLabelCluster + "serverless_v2_platform_version" + rdsLabelClusterStatus = rdsLabelCluster + "status" + rdsLabelClusterStorageEncrypted = rdsLabelCluster + "storage_encrypted" + rdsLabelClusterStorageEncryptionType = rdsLabelCluster + "storage_encryption_type" + rdsLabelClusterStorageThroughput = rdsLabelCluster + "storage_throughput" + rdsLabelClusterStorageType = rdsLabelCluster + "storage_type" + rdsLabelClusterUpgradeRolloutOrder = rdsLabelCluster + "upgrade_rollout_order" + + // DB cluster tags - create one label per tag key, with the format: rds_cluster_tag_. + rdsLabelClusterTag = rdsLabelCluster + "tag_" + + // DB instance labels. + rdsLabelInstance = rdsLabel + "instance_" + rdsLabelInstanceIsClusterWriter = rdsLabelInstance + "is_cluster_writer" + rdsLabelInstanceActivityStreamEngineNativeAuditFieldsIncluded = rdsLabelInstance + "activity_stream_engine_native_audit_fields_included" + rdsLabelInstanceActivityStreamKinesisStreamName = rdsLabelInstance + "activity_stream_kinesis_stream_name" + rdsLabelInstanceActivityStreamKmsKeyID = rdsLabelInstance + "activity_stream_kms_key_id" + rdsLabelInstanceActivityStreamMode = rdsLabelInstance + "activity_stream_mode" + rdsLabelInstanceActivityStreamPolicyStatus = rdsLabelInstance + "activity_stream_policy_status" + rdsLabelInstanceActivityStreamStatus = rdsLabelInstance + "activity_stream_status" + rdsLabelInstanceAllocatedStorage = rdsLabelInstance + "allocated_storage" + rdsLabelInstanceAutoMinorVersionUpgrade = rdsLabelInstance + "auto_minor_version_upgrade" + rdsLabelInstanceAutomaticRestartTime = rdsLabelInstance + "automatic_restart_time" + rdsLabelInstanceAutomationMode = rdsLabelInstance + "automation_mode" + rdsLabelInstanceAvailabilityZone = rdsLabelInstance + "availability_zone" + rdsLabelInstanceAwsBackupRecoveryPointArn = rdsLabelInstance + "aws_backup_recovery_point_arn" + rdsLabelInstanceBackupRetentionPeriod = rdsLabelInstance + "backup_retention_period" + rdsLabelInstanceBackupTarget = rdsLabelInstance + "backup_target" + rdsLabelInstanceCACertificateIdentifier = rdsLabelInstance + "ca_certificate_identifier" + rdsLabelInstanceCharacterSetName = rdsLabelInstance + "character_set_name" + rdsLabelInstanceCopyTagsToSnapshot = rdsLabelInstance + "copy_tags_to_snapshot" + rdsLabelInstanceCustomIamInstanceProfile = rdsLabelInstance + "custom_iam_instance_profile" + rdsLabelInstanceCustomerOwnedIPEnabled = rdsLabelInstance + "customer_owned_ip_enabled" + rdsLabelInstanceDBClusterIdentifier = rdsLabelInstance + "db_cluster_identifier" + rdsLabelInstanceDBInstanceArn = rdsLabelInstance + "arn" + rdsLabelInstanceDBInstanceClass = rdsLabelInstance + "class" + rdsLabelInstanceDBInstanceIdentifier = rdsLabelInstance + "identifier" + rdsLabelInstanceDBInstanceStatus = rdsLabelInstance + "status" + rdsLabelInstanceDBName = rdsLabelInstance + "db_name" + rdsLabelInstanceDBSubnetGroup = rdsLabelInstance + "subnet_group" + rdsLabelInstanceDBSystemID = rdsLabelInstance + "db_system_id" + rdsLabelInstanceDatabaseInsightsMode = rdsLabelInstance + "database_insights_mode" + rdsLabelInstanceDBInstancePort = rdsLabelInstance + "port" + rdsLabelInstanceDBResourceID = rdsLabelInstance + "resource_id" + rdsLabelInstanceDedicatedLogVolume = rdsLabelInstance + "dedicated_log_volume" + rdsLabelInstanceDeletionProtection = rdsLabelInstance + "deletion_protection" + rdsLabelInstanceEndpointAddress = rdsLabelInstance + "endpoint_address" + rdsLabelInstanceEndpointHostedZoneID = rdsLabelInstance + "endpoint_hosted_zone_id" + rdsLabelInstanceEndpointPort = rdsLabelInstance + "endpoint_port" + rdsLabelInstanceEngine = rdsLabelInstance + "engine" + rdsLabelInstanceEngineLifecycleSupport = rdsLabelInstance + "engine_lifecycle_support" + rdsLabelInstanceEngineVersion = rdsLabelInstance + "engine_version" + rdsLabelInstanceEnhancedMonitoringResourceArn = rdsLabelInstance + "enhanced_monitoring_resource_arn" + rdsLabelInstanceIAMDatabaseAuthenticationEnabled = rdsLabelInstance + "iam_database_authentication_enabled" + rdsLabelInstanceInstanceCreateTime = rdsLabelInstance + "instance_create_time" + rdsLabelInstanceIops = rdsLabelInstance + "iops" + rdsLabelInstanceIsStorageConfigUpgradeAvailable = rdsLabelInstance + "is_storage_config_upgrade_available" + rdsLabelInstanceKMSKeyID = rdsLabelInstance + "kms_key_id" + rdsLabelInstanceLatestRestorableTime = rdsLabelInstance + "latest_restorable_time" + rdsLabelInstanceLicenseModel = rdsLabelInstance + "license_model" + rdsLabelInstanceListenerEndpointAddress = rdsLabelInstance + "listener_endpoint_address" + rdsLabelInstanceListenerEndpointHostedZoneID = rdsLabelInstance + "listener_endpoint_hosted_zone_id" + rdsLabelInstanceListenerEndpointPort = rdsLabelInstance + "listener_endpoint_port" + rdsLabelInstanceMasterUsername = rdsLabelInstance + "master_username" + rdsLabelInstanceMaxAllocatedStorage = rdsLabelInstance + "max_allocated_storage" + rdsLabelInstanceMonitoringInterval = rdsLabelInstance + "monitoring_interval" + rdsLabelInstanceMonitoringRoleArn = rdsLabelInstance + "monitoring_role_arn" + rdsLabelInstanceMultiAZ = rdsLabelInstance + "multi_az" + rdsLabelInstanceMultiTenant = rdsLabelInstance + "multi_tenant" + rdsLabelInstanceNcharCharacterSetName = rdsLabelInstance + "nchar_character_set_name" + rdsLabelInstanceNetworkType = rdsLabelInstance + "network_type" + rdsLabelInstancePercentProgress = rdsLabelInstance + "percent_progress" + rdsLabelInstancePerformanceInsightsEnabled = rdsLabelInstance + "performance_insights_enabled" + rdsLabelInstancePerformanceInsightsKMSKeyID = rdsLabelInstance + "performance_insights_kms_key_id" + rdsLabelInstancePerformanceInsightsRetentionPeriod = rdsLabelInstance + "performance_insights_retention_period" + rdsLabelInstancePreferredBackupWindow = rdsLabelInstance + "preferred_backup_window" + rdsLabelInstancePreferredMaintenanceWindow = rdsLabelInstance + "preferred_maintenance_window" + rdsLabelInstancePromotionTier = rdsLabelInstance + "promotion_tier" + rdsLabelInstancePubliclyAccessible = rdsLabelInstance + "publicly_accessible" + rdsLabelInstanceReadReplicaSourceDBClusterIdentifier = rdsLabelInstance + "read_replica_source_db_cluster_identifier" + rdsLabelInstanceReadReplicaSourceDBInstanceIdentifier = rdsLabelInstance + "read_replica_source_db_instance_identifier" + rdsLabelInstanceReplicaMode = rdsLabelInstance + "replica_mode" + rdsLabelInstanceResumeFullAutomationModeTime = rdsLabelInstance + "resume_full_automation_mode_time" + rdsLabelInstanceSecondaryAvailabilityZone = rdsLabelInstance + "secondary_availability_zone" + rdsLabelInstanceStorageEncrypted = rdsLabelInstance + "storage_encrypted" + rdsLabelInstanceStorageEncryptionType = rdsLabelInstance + "storage_encryption_type" + rdsLabelInstanceStorageThroughput = rdsLabelInstance + "storage_throughput" + rdsLabelInstanceStorageType = rdsLabelInstance + "storage_type" + rdsLabelInstanceStorageVolumeStatus = rdsLabelInstance + "storage_volume_status" + rdsLabelInstanceTdeCredentialArn = rdsLabelInstance + "tde_credential_arn" + rdsLabelInstanceTimezone = rdsLabelInstance + "timezone" + rdsLabelInstanceUpgradeRolloutOrder = rdsLabelInstance + "upgrade_rollout_order" + + // DB instance tags - create one label per tag key, with the format: rds_instance_tag_. + rdsLabelInstanceTag = rdsLabelInstance + "tag_" +) + +// DefaultRDSSDConfig is the default RDS SD configuration. +var DefaultRDSSDConfig = RDSSDConfig{ + Port: 80, + RefreshInterval: model.Duration(60 * time.Second), + RequestConcurrency: 10, + HTTPClientConfig: config.DefaultHTTPClientConfig, +} + +func init() { + discovery.RegisterConfig(&RDSSDConfig{}) +} + +// RDSSDConfig is the configuration for RDS based service discovery. +type RDSSDConfig struct { + Region string `yaml:"region"` + Endpoint string `yaml:"endpoint"` + AccessKey string `yaml:"access_key,omitempty"` + SecretKey config.Secret `yaml:"secret_key,omitempty"` + Profile string `yaml:"profile,omitempty"` + RoleARN string `yaml:"role_arn,omitempty"` + Clusters []string `yaml:"clusters,omitempty"` + Port int `yaml:"port"` + RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` + + RequestConcurrency int `yaml:"request_concurrency,omitempty"` + HTTPClientConfig config.HTTPClientConfig `yaml:",inline"` +} + +// NewDiscovererMetrics implements discovery.Config. +func (*RDSSDConfig) NewDiscovererMetrics(_ prometheus.Registerer, rmi discovery.RefreshMetricsInstantiator) discovery.DiscovererMetrics { + return &rdsMetrics{ + refreshMetrics: rmi, + } +} + +// Name returns the name of the RDS Config. +func (*RDSSDConfig) Name() string { return "rds" } + +// NewDiscoverer returns a Discoverer for the RDS Config. +func (c *RDSSDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) { + return NewRDSDiscovery(c, opts) +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for the RDS Config. +func (c *RDSSDConfig) UnmarshalYAML(unmarshal func(any) error) error { + *c = DefaultRDSSDConfig + type plain RDSSDConfig + err := unmarshal((*plain)(c)) + if err != nil { + return err + } + + c.Region, err = loadRegion(context.Background(), c.Region) + if err != nil { + return fmt.Errorf("could not determine AWS region: %w", err) + } + + return c.HTTPClientConfig.Validate() +} + +type rdsClient interface { + DescribeDBClusters(context.Context, *rds.DescribeDBClustersInput, ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error) + DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) +} + +// RDSDiscovery periodically performs RDS-SD requests. It implements +// the Discoverer interface. +type RDSDiscovery struct { + *refresh.Discovery + logger *slog.Logger + cfg *RDSSDConfig + rds rdsClient +} + +// NewRDSDiscovery returns a new RDSDiscovery which periodically refreshes its targets. +func NewRDSDiscovery(conf *RDSSDConfig, opts discovery.DiscovererOptions) (*RDSDiscovery, error) { + m, ok := opts.Metrics.(*rdsMetrics) + if !ok { + return nil, errors.New("invalid discovery metrics type") + } + + if opts.Logger == nil { + opts.Logger = promslog.NewNopLogger() + } + d := &RDSDiscovery{ + logger: opts.Logger, + cfg: conf, + } + d.Discovery = refresh.NewDiscovery( + refresh.Options{ + Logger: opts.Logger, + Mech: "rds", + Interval: time.Duration(d.cfg.RefreshInterval), + RefreshF: d.refresh, + MetricsInstantiator: m.refreshMetrics, + }, + ) + return d, nil +} + +func (d *RDSDiscovery) initRdsClient(ctx context.Context) error { + if d.rds != nil { + return nil + } + + if d.cfg.Region == "" { + return errors.New("region must be set for RDS service discovery") + } + + // Build the HTTP client from the provided HTTPClientConfig. + client, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "rds_sd") + if err != nil { + return err + } + + // Build the AWS config with the provided region. + var configOptions []func(*awsConfig.LoadOptions) error + configOptions = append(configOptions, awsConfig.WithRegion(d.cfg.Region)) + configOptions = append(configOptions, awsConfig.WithHTTPClient(client)) + + // Only set static credentials if both access key and secret key are provided + // Otherwise, let AWS SDK use its default credential chain + if d.cfg.AccessKey != "" && d.cfg.SecretKey != "" { + credProvider := credentials.NewStaticCredentialsProvider(d.cfg.AccessKey, string(d.cfg.SecretKey), "") + configOptions = append(configOptions, awsConfig.WithCredentialsProvider(credProvider)) + } + + if d.cfg.Profile != "" { + configOptions = append(configOptions, awsConfig.WithSharedConfigProfile(d.cfg.Profile)) + } + + cfg, err := awsConfig.LoadDefaultConfig(ctx, configOptions...) + if err != nil { + d.logger.Error("Failed to create AWS config", "error", err) + return fmt.Errorf("could not create aws config: %w", err) + } + + // If the role ARN is set, assume the role to get credentials and set the credentials provider in the config. + if d.cfg.RoleARN != "" { + assumeProvider := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), d.cfg.RoleARN) + cfg.Credentials = aws.NewCredentialsCache(assumeProvider) + } + + d.rds = rds.NewFromConfig(cfg, func(options *rds.Options) { + if d.cfg.Endpoint != "" { + options.BaseEndpoint = &d.cfg.Endpoint + } + options.HTTPClient = client + }) + + // Test credentials by making a simple API call + testCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + _, err = d.rds.DescribeDBClusters(testCtx, &rds.DescribeDBClustersInput{}) + if err != nil { + d.logger.Error("Failed to test RDS credentials", "error", err) + return fmt.Errorf("RDS credential test failed: %w", err) + } + + return nil +} + +func (d *RDSDiscovery) describeAllDBClusters(ctx context.Context) (map[string]types.DBCluster, error) { + dbClustersByARN := make(map[string]types.DBCluster) + var nextToken *string + + for { + output, err := d.rds.DescribeDBClusters(ctx, &rds.DescribeDBClustersInput{ + Marker: nextToken, + MaxRecords: aws.Int32(100), + }) + if err != nil { + return nil, fmt.Errorf("failed to describe DB clusters: %w", err) + } + for _, dbCluster := range output.DBClusters { + if dbCluster.DBClusterArn != nil { + dbClustersByARN[*dbCluster.DBClusterArn] = dbCluster + } + } + if output.Marker == nil { + break + } + nextToken = output.Marker + } + + return dbClustersByARN, nil +} + +func (d *RDSDiscovery) describeDBClusters(ctx context.Context, dbClusterARNS []string) (map[string]types.DBCluster, error) { + mu := &sync.Mutex{} + errg, ectx := errgroup.WithContext(ctx) + errg.SetLimit(d.cfg.RequestConcurrency) + dbClustersByARN := make(map[string]types.DBCluster) + var nextToken *string + + for _, arn := range dbClusterARNS { + errg.Go(func() error { + for { + output, err := d.rds.DescribeDBClusters(ectx, &rds.DescribeDBClustersInput{ + DBClusterIdentifier: aws.String(arn), + Marker: nextToken, + MaxRecords: aws.Int32(100), + }) + if err != nil { + return fmt.Errorf("failed to describe DB cluster %s: %w", arn, err) + } + if len(output.DBClusters) == 0 { + return fmt.Errorf("no DB cluster found for ARN %s", arn) + } + + for _, dbCluster := range output.DBClusters { + mu.Lock() + dbClustersByARN[arn] = dbCluster + mu.Unlock() + } + if output.Marker == nil { + break + } + nextToken = output.Marker + } + return nil + }) + } + return dbClustersByARN, errg.Wait() +} + +func (d *RDSDiscovery) describeDBInstances(ctx context.Context, dbClusterARN string) ([]types.DBInstance, error) { + mu := &sync.Mutex{} + errg, ectx := errgroup.WithContext(ctx) + errg.SetLimit(d.cfg.RequestConcurrency) + dbInstances := []types.DBInstance{} + var nextToken *string + for { + output, err := d.rds.DescribeDBInstances(ectx, &rds.DescribeDBInstancesInput{ + Filters: []types.Filter{ + { + Name: aws.String("db-cluster-id"), + Values: []string{dbClusterARN}, + }, + }, + Marker: nextToken, + MaxRecords: aws.Int32(100), + }) + if err != nil { + return nil, fmt.Errorf("failed to describe DB instances for cluster ARN %s: %w", dbClusterARN, err) + } + if len(output.DBInstances) == 0 { + return nil, fmt.Errorf("no DB instances found for cluster ARN %s", dbClusterARN) + } + + for _, dbInstance := range output.DBInstances { + mu.Lock() + dbInstances = append(dbInstances, dbInstance) + mu.Unlock() + } + if output.Marker == nil { + break + } + nextToken = output.Marker + } + return dbInstances, errg.Wait() +} + +func (d *RDSDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) { + err := d.initRdsClient(ctx) + if err != nil { + return nil, err + } + + tg := &targetgroup.Group{ + Source: d.cfg.Region, + } + + var clusters map[string]types.DBCluster + if len(d.cfg.Clusters) == 0 { + clusters, err = d.describeAllDBClusters(ctx) + if err != nil { + return nil, fmt.Errorf("error describing all DB clusters: %w", err) + } + } else { + clusters, err = d.describeDBClusters(ctx, d.cfg.Clusters) + if err != nil { + return nil, fmt.Errorf("error describing DB clusters: %w", err) + } + } + + var ( + mu sync.Mutex + wg sync.WaitGroup + ) + for _, cluster := range clusters { + wg.Add(1) + + instances, err := d.describeDBInstances(ctx, *cluster.DBClusterArn) + if err != nil { + return nil, fmt.Errorf("error describing DB instances: %w", err) + } + + go func(cluster types.DBCluster, instances []types.DBInstance) { + defer wg.Done() + + // Build a map of instance identifiers to their IsClusterWriter status + writerMap := make(map[string]bool) + for _, member := range cluster.DBClusterMembers { + if member.DBInstanceIdentifier != nil && member.IsClusterWriter != nil { + writerMap[*member.DBInstanceIdentifier] = *member.IsClusterWriter + } + } + + for _, instance := range instances { + labels := model.LabelSet{} + + // Cluster labels + if cluster.DBClusterArn != nil { + labels[rdsLabelClusterDBClusterArn] = model.LabelValue(*cluster.DBClusterArn) + } + if cluster.DBClusterIdentifier != nil { + labels[rdsLabelClusterDBClusterIdentifier] = model.LabelValue(*cluster.DBClusterIdentifier) + } + if cluster.ActivityStreamKinesisStreamName != nil { + labels[rdsLabelClusterActivityStreamKinesisStreamName] = model.LabelValue(*cluster.ActivityStreamKinesisStreamName) + } + if cluster.ActivityStreamKmsKeyId != nil { + labels[rdsLabelClusterActivityStreamKMSKeyID] = model.LabelValue(*cluster.ActivityStreamKmsKeyId) + } + if cluster.ActivityStreamMode != "" { + labels[rdsLabelClusterActivityStreamMode] = model.LabelValue(cluster.ActivityStreamMode) + } + if cluster.ActivityStreamStatus != "" { + labels[rdsLabelClusterActivityStreamStatus] = model.LabelValue(cluster.ActivityStreamStatus) + } + if cluster.AllocatedStorage != nil { + labels[rdsLabelClusterAllocatedStorage] = model.LabelValue(strconv.Itoa(int(*cluster.AllocatedStorage))) + } + if cluster.AutoMinorVersionUpgrade != nil { + labels[rdsLabelClusterAutoMinorVersionUpgrade] = model.LabelValue(strconv.FormatBool(*cluster.AutoMinorVersionUpgrade)) + } + if cluster.AutomaticRestartTime != nil { + labels[rdsLabelClusterAutomaticRestartTime] = model.LabelValue(cluster.AutomaticRestartTime.Format(time.RFC3339)) + } + if cluster.AwsBackupRecoveryPointArn != nil { + labels[rdsLabelClusterAwsBackupRecoveryPointArn] = model.LabelValue(*cluster.AwsBackupRecoveryPointArn) + } + if cluster.BacktrackConsumedChangeRecords != nil { + labels[rdsLabelClusterBacktrackConsumedChangeRecords] = model.LabelValue(strconv.FormatInt(*cluster.BacktrackConsumedChangeRecords, 10)) + } + if cluster.BacktrackWindow != nil { + labels[rdsLabelClusterBacktrackWindow] = model.LabelValue(strconv.FormatInt(*cluster.BacktrackWindow, 10)) + } + if cluster.BackupRetentionPeriod != nil { + labels[rdsLabelClusterBackupRetentionPeriod] = model.LabelValue(strconv.Itoa(int(*cluster.BackupRetentionPeriod))) + } + if cluster.Capacity != nil { + labels[rdsLabelClusterCapacity] = model.LabelValue(strconv.Itoa(int(*cluster.Capacity))) + } + if cluster.CharacterSetName != nil { + labels[rdsLabelClusterCharacterSetName] = model.LabelValue(*cluster.CharacterSetName) + } + if cluster.CloneGroupId != nil { + labels[rdsLabelClusterCloneGroupID] = model.LabelValue(*cluster.CloneGroupId) + } + if cluster.ClusterCreateTime != nil { + labels[rdsLabelClusterClusterCreateTime] = model.LabelValue(cluster.ClusterCreateTime.Format(time.RFC3339)) + } + if cluster.ClusterScalabilityType != "" { + labels[rdsLabelClusterClusterScalabilityType] = model.LabelValue(cluster.ClusterScalabilityType) + } + if cluster.CopyTagsToSnapshot != nil { + labels[rdsLabelClusterCopyTagsToSnapshot] = model.LabelValue(strconv.FormatBool(*cluster.CopyTagsToSnapshot)) + } + if cluster.CrossAccountClone != nil { + labels[rdsLabelClusterCrossAccountClone] = model.LabelValue(strconv.FormatBool(*cluster.CrossAccountClone)) + } + if cluster.DBClusterInstanceClass != nil { + labels[rdsLabelClusterDBClusterInstanceClass] = model.LabelValue(*cluster.DBClusterInstanceClass) + } + if cluster.DBClusterParameterGroup != nil { + labels[rdsLabelClusterDBClusterParameterGroup] = model.LabelValue(*cluster.DBClusterParameterGroup) + } + if cluster.DBSubnetGroup != nil { + labels[rdsLabelClusterDBSubnetGroup] = model.LabelValue(*cluster.DBSubnetGroup) + } + if cluster.DBSystemId != nil { + labels[rdsLabelClusterDBSystemID] = model.LabelValue(*cluster.DBSystemId) + } + if cluster.DatabaseInsightsMode != "" { + labels[rdsLabelClusterDatabaseInsightsMode] = model.LabelValue(cluster.DatabaseInsightsMode) + } + if cluster.DatabaseName != nil { + labels[rdsLabelClusterDatabaseName] = model.LabelValue(*cluster.DatabaseName) + } + if cluster.DbClusterResourceId != nil { + labels[rdsLabelClusterDBClusterResourceID] = model.LabelValue(*cluster.DbClusterResourceId) + } + if cluster.DeletionProtection != nil { + labels[rdsLabelClusterDeletionProtection] = model.LabelValue(strconv.FormatBool(*cluster.DeletionProtection)) + } + if cluster.EarliestBacktrackTime != nil { + labels[rdsLabelClusterEarliestBacktrackTime] = model.LabelValue(cluster.EarliestBacktrackTime.Format(time.RFC3339)) + } + if cluster.EarliestRestorableTime != nil { + labels[rdsLabelClusterEarliestRestorableTime] = model.LabelValue(cluster.EarliestRestorableTime.Format(time.RFC3339)) + } + if cluster.Endpoint != nil { + labels[rdsLabelClusterEndpoint] = model.LabelValue(*cluster.Endpoint) + } + if cluster.Engine != nil { + labels[rdsLabelClusterEngine] = model.LabelValue(*cluster.Engine) + } + if cluster.EngineLifecycleSupport != nil { + labels[rdsLabelClusterEngineLifecycleSupport] = model.LabelValue(*cluster.EngineLifecycleSupport) + } + if cluster.EngineMode != nil { + labels[rdsLabelClusterEngineMode] = model.LabelValue(*cluster.EngineMode) + } + if cluster.EngineVersion != nil { + labels[rdsLabelClusterEngineVersion] = model.LabelValue(*cluster.EngineVersion) + } + if cluster.GlobalClusterIdentifier != nil { + labels[rdsLabelClusterGlobalClusterIdentifier] = model.LabelValue(*cluster.GlobalClusterIdentifier) + } + if cluster.GlobalWriteForwardingRequested != nil { + labels[rdsLabelClusterGlobalWriteForwardingRequested] = model.LabelValue(strconv.FormatBool(*cluster.GlobalWriteForwardingRequested)) + } + if cluster.GlobalWriteForwardingStatus != "" { + labels[rdsLabelClusterGlobalWriteForwardingStatus] = model.LabelValue(cluster.GlobalWriteForwardingStatus) + } + if cluster.HostedZoneId != nil { + labels[rdsLabelClusterHostedZoneID] = model.LabelValue(*cluster.HostedZoneId) + } + if cluster.HttpEndpointEnabled != nil { + labels[rdsLabelClusterHTTPEndpointEnabled] = model.LabelValue(strconv.FormatBool(*cluster.HttpEndpointEnabled)) + } + if cluster.IAMDatabaseAuthenticationEnabled != nil { + labels[rdsLabelClusterIAMDatabaseAuthenticationEnabled] = model.LabelValue(strconv.FormatBool(*cluster.IAMDatabaseAuthenticationEnabled)) + } + if cluster.IOOptimizedNextAllowedModificationTime != nil { + labels[rdsLabelClusterIOOptimizedNextAllowedModificationTime] = model.LabelValue(cluster.IOOptimizedNextAllowedModificationTime.Format(time.RFC3339)) + } + if cluster.Iops != nil { + labels[rdsLabelClusterIops] = model.LabelValue(strconv.Itoa(int(*cluster.Iops))) + } + if cluster.KmsKeyId != nil { + labels[rdsLabelClusterKMSKeyID] = model.LabelValue(*cluster.KmsKeyId) + } + if cluster.LatestRestorableTime != nil { + labels[rdsLabelClusterLatestRestorableTime] = model.LabelValue(cluster.LatestRestorableTime.Format(time.RFC3339)) + } + if cluster.LocalWriteForwardingStatus != "" { + labels[rdsLabelClusterLocalWriteForwardingStatus] = model.LabelValue(cluster.LocalWriteForwardingStatus) + } + if cluster.MasterUsername != nil { + labels[rdsLabelClusterMasterUsername] = model.LabelValue(*cluster.MasterUsername) + } + if cluster.MonitoringInterval != nil { + labels[rdsLabelClusterMonitoringInterval] = model.LabelValue(strconv.Itoa(int(*cluster.MonitoringInterval))) + } + if cluster.MonitoringRoleArn != nil { + labels[rdsLabelClusterMonitoringRoleArn] = model.LabelValue(*cluster.MonitoringRoleArn) + } + if cluster.MultiAZ != nil { + labels[rdsLabelClusterMultiAZ] = model.LabelValue(strconv.FormatBool(*cluster.MultiAZ)) + } + if cluster.NetworkType != nil { + labels[rdsLabelClusterNetworkType] = model.LabelValue(*cluster.NetworkType) + } + if cluster.PercentProgress != nil { + labels[rdsLabelClusterPercentProgress] = model.LabelValue(*cluster.PercentProgress) + } + if cluster.PerformanceInsightsEnabled != nil { + labels[rdsLabelClusterPerformanceInsightsEnabled] = model.LabelValue(strconv.FormatBool(*cluster.PerformanceInsightsEnabled)) + } + if cluster.PerformanceInsightsKMSKeyId != nil { + labels[rdsLabelClusterPerformanceInsightsKMSKeyID] = model.LabelValue(*cluster.PerformanceInsightsKMSKeyId) + } + if cluster.PerformanceInsightsRetentionPeriod != nil { + labels[rdsLabelClusterPerformanceInsightsRetentionPeriod] = model.LabelValue(strconv.Itoa(int(*cluster.PerformanceInsightsRetentionPeriod))) + } + if cluster.Port != nil { + labels[rdsLabelClusterPort] = model.LabelValue(strconv.Itoa(int(*cluster.Port))) + } + if cluster.PreferredBackupWindow != nil { + labels[rdsLabelClusterPreferredBackupWindow] = model.LabelValue(*cluster.PreferredBackupWindow) + } + if cluster.PreferredMaintenanceWindow != nil { + labels[rdsLabelClusterPreferredMaintenanceWindow] = model.LabelValue(*cluster.PreferredMaintenanceWindow) + } + if cluster.PubliclyAccessible != nil { + labels[rdsLabelClusterPubliclyAccessible] = model.LabelValue(strconv.FormatBool(*cluster.PubliclyAccessible)) + } + if cluster.ReaderEndpoint != nil { + labels[rdsLabelClusterReaderEndpoint] = model.LabelValue(*cluster.ReaderEndpoint) + } + if cluster.ReplicationSourceIdentifier != nil { + labels[rdsLabelClusterReplicationSourceIdentifier] = model.LabelValue(*cluster.ReplicationSourceIdentifier) + } + if cluster.ServerlessV2PlatformVersion != nil { + labels[rdsLabelClusterServerlessV2PlatformVersion] = model.LabelValue(*cluster.ServerlessV2PlatformVersion) + } + if cluster.Status != nil { + labels[rdsLabelClusterStatus] = model.LabelValue(*cluster.Status) + } + if cluster.StorageEncrypted != nil { + labels[rdsLabelClusterStorageEncrypted] = model.LabelValue(strconv.FormatBool(*cluster.StorageEncrypted)) + } + if cluster.StorageEncryptionType != "" { + labels[rdsLabelClusterStorageEncryptionType] = model.LabelValue(cluster.StorageEncryptionType) + } + if cluster.StorageThroughput != nil { + labels[rdsLabelClusterStorageThroughput] = model.LabelValue(strconv.Itoa(int(*cluster.StorageThroughput))) + } + if cluster.StorageType != nil { + labels[rdsLabelClusterStorageType] = model.LabelValue(*cluster.StorageType) + } + if cluster.UpgradeRolloutOrder != "" { + labels[rdsLabelClusterUpgradeRolloutOrder] = model.LabelValue(cluster.UpgradeRolloutOrder) + } + + // Cluster tags + for _, tag := range cluster.TagList { + if tag.Key != nil && tag.Value != nil { + labels[model.LabelName(rdsLabelClusterTag+strutil.SanitizeLabelName(*tag.Key))] = model.LabelValue(*tag.Value) + } + } + + // Instance labels + if instance.DBInstanceArn != nil { + labels[rdsLabelInstanceDBInstanceArn] = model.LabelValue(*instance.DBInstanceArn) + } + if instance.DBInstanceIdentifier != nil { + labels[rdsLabelInstanceDBInstanceIdentifier] = model.LabelValue(*instance.DBInstanceIdentifier) + // Set IsClusterWriter based on cluster membership information + if isWriter, found := writerMap[*instance.DBInstanceIdentifier]; found { + labels[rdsLabelInstanceIsClusterWriter] = model.LabelValue(strconv.FormatBool(isWriter)) + } + } + if instance.ActivityStreamEngineNativeAuditFieldsIncluded != nil { + labels[rdsLabelInstanceActivityStreamEngineNativeAuditFieldsIncluded] = model.LabelValue(strconv.FormatBool(*instance.ActivityStreamEngineNativeAuditFieldsIncluded)) + } + if instance.ActivityStreamKinesisStreamName != nil { + labels[rdsLabelInstanceActivityStreamKinesisStreamName] = model.LabelValue(*instance.ActivityStreamKinesisStreamName) + } + if instance.ActivityStreamKmsKeyId != nil { + labels[rdsLabelInstanceActivityStreamKmsKeyID] = model.LabelValue(*instance.ActivityStreamKmsKeyId) + } + if instance.ActivityStreamMode != "" { + labels[rdsLabelInstanceActivityStreamMode] = model.LabelValue(instance.ActivityStreamMode) + } + if instance.ActivityStreamPolicyStatus != "" { + labels[rdsLabelInstanceActivityStreamPolicyStatus] = model.LabelValue(instance.ActivityStreamPolicyStatus) + } + if instance.ActivityStreamStatus != "" { + labels[rdsLabelInstanceActivityStreamStatus] = model.LabelValue(instance.ActivityStreamStatus) + } + if instance.AllocatedStorage != nil { + labels[rdsLabelInstanceAllocatedStorage] = model.LabelValue(strconv.Itoa(int(*instance.AllocatedStorage))) + } + if instance.AutoMinorVersionUpgrade != nil { + labels[rdsLabelInstanceAutoMinorVersionUpgrade] = model.LabelValue(strconv.FormatBool(*instance.AutoMinorVersionUpgrade)) + } + if instance.AutomaticRestartTime != nil { + labels[rdsLabelInstanceAutomaticRestartTime] = model.LabelValue(instance.AutomaticRestartTime.Format(time.RFC3339)) + } + if instance.AutomationMode != "" { + labels[rdsLabelInstanceAutomationMode] = model.LabelValue(instance.AutomationMode) + } + if instance.AvailabilityZone != nil { + labels[rdsLabelInstanceAvailabilityZone] = model.LabelValue(*instance.AvailabilityZone) + } + if instance.AwsBackupRecoveryPointArn != nil { + labels[rdsLabelInstanceAwsBackupRecoveryPointArn] = model.LabelValue(*instance.AwsBackupRecoveryPointArn) + } + if instance.BackupRetentionPeriod != nil { + labels[rdsLabelInstanceBackupRetentionPeriod] = model.LabelValue(strconv.Itoa(int(*instance.BackupRetentionPeriod))) + } + if instance.BackupTarget != nil { + labels[rdsLabelInstanceBackupTarget] = model.LabelValue(*instance.BackupTarget) + } + if instance.CACertificateIdentifier != nil { + labels[rdsLabelInstanceCACertificateIdentifier] = model.LabelValue(*instance.CACertificateIdentifier) + } + if instance.CharacterSetName != nil { + labels[rdsLabelInstanceCharacterSetName] = model.LabelValue(*instance.CharacterSetName) + } + if instance.CopyTagsToSnapshot != nil { + labels[rdsLabelInstanceCopyTagsToSnapshot] = model.LabelValue(strconv.FormatBool(*instance.CopyTagsToSnapshot)) + } + if instance.CustomIamInstanceProfile != nil { + labels[rdsLabelInstanceCustomIamInstanceProfile] = model.LabelValue(*instance.CustomIamInstanceProfile) + } + if instance.CustomerOwnedIpEnabled != nil { + labels[rdsLabelInstanceCustomerOwnedIPEnabled] = model.LabelValue(strconv.FormatBool(*instance.CustomerOwnedIpEnabled)) + } + if instance.DBClusterIdentifier != nil { + labels[rdsLabelInstanceDBClusterIdentifier] = model.LabelValue(*instance.DBClusterIdentifier) + } + if instance.DBInstanceClass != nil { + labels[rdsLabelInstanceDBInstanceClass] = model.LabelValue(*instance.DBInstanceClass) + } + if instance.DBInstanceStatus != nil { + labels[rdsLabelInstanceDBInstanceStatus] = model.LabelValue(*instance.DBInstanceStatus) + } + if instance.DBName != nil { + labels[rdsLabelInstanceDBName] = model.LabelValue(*instance.DBName) + } + if instance.DbInstancePort != nil { + labels[rdsLabelInstanceDBInstancePort] = model.LabelValue(strconv.Itoa(int(*instance.DbInstancePort))) + } + if instance.DbiResourceId != nil { + labels[rdsLabelInstanceDBResourceID] = model.LabelValue(*instance.DbiResourceId) + } + if instance.DedicatedLogVolume != nil { + labels[rdsLabelInstanceDedicatedLogVolume] = model.LabelValue(strconv.FormatBool(*instance.DedicatedLogVolume)) + } + if instance.DeletionProtection != nil { + labels[rdsLabelInstanceDeletionProtection] = model.LabelValue(strconv.FormatBool(*instance.DeletionProtection)) + } + if instance.Endpoint != nil { + if instance.Endpoint.Address != nil { + labels[rdsLabelInstanceEndpointAddress] = model.LabelValue(*instance.Endpoint.Address) + } + if instance.Endpoint.HostedZoneId != nil { + labels[rdsLabelInstanceEndpointHostedZoneID] = model.LabelValue(*instance.Endpoint.HostedZoneId) + } + if instance.Endpoint.Port != nil { + labels[rdsLabelInstanceEndpointPort] = model.LabelValue(strconv.Itoa(int(*instance.Endpoint.Port))) + } + } + if instance.Engine != nil { + labels[rdsLabelInstanceEngine] = model.LabelValue(*instance.Engine) + } + if instance.EngineLifecycleSupport != nil { + labels[rdsLabelInstanceEngineLifecycleSupport] = model.LabelValue(*instance.EngineLifecycleSupport) + } + if instance.EngineVersion != nil { + labels[rdsLabelInstanceEngineVersion] = model.LabelValue(*instance.EngineVersion) + } + if instance.EnhancedMonitoringResourceArn != nil { + labels[rdsLabelInstanceEnhancedMonitoringResourceArn] = model.LabelValue(*instance.EnhancedMonitoringResourceArn) + } + if instance.IAMDatabaseAuthenticationEnabled != nil { + labels[rdsLabelInstanceIAMDatabaseAuthenticationEnabled] = model.LabelValue(strconv.FormatBool(*instance.IAMDatabaseAuthenticationEnabled)) + } + if instance.InstanceCreateTime != nil { + labels[rdsLabelInstanceInstanceCreateTime] = model.LabelValue(instance.InstanceCreateTime.Format(time.RFC3339)) + } + if instance.Iops != nil { + labels[rdsLabelInstanceIops] = model.LabelValue(strconv.Itoa(int(*instance.Iops))) + } + if instance.IsStorageConfigUpgradeAvailable != nil { + labels[rdsLabelInstanceIsStorageConfigUpgradeAvailable] = model.LabelValue(strconv.FormatBool(*instance.IsStorageConfigUpgradeAvailable)) + } + if instance.KmsKeyId != nil { + labels[rdsLabelInstanceKMSKeyID] = model.LabelValue(*instance.KmsKeyId) + } + if instance.LatestRestorableTime != nil { + labels[rdsLabelInstanceLatestRestorableTime] = model.LabelValue(instance.LatestRestorableTime.Format(time.RFC3339)) + } + if instance.LicenseModel != nil { + labels[rdsLabelInstanceLicenseModel] = model.LabelValue(*instance.LicenseModel) + } + if instance.ListenerEndpoint != nil { + if instance.ListenerEndpoint.Address != nil { + labels[rdsLabelInstanceListenerEndpointAddress] = model.LabelValue(*instance.ListenerEndpoint.Address) + } + if instance.ListenerEndpoint.HostedZoneId != nil { + labels[rdsLabelInstanceListenerEndpointHostedZoneID] = model.LabelValue(*instance.ListenerEndpoint.HostedZoneId) + } + if instance.ListenerEndpoint.Port != nil { + labels[rdsLabelInstanceListenerEndpointPort] = model.LabelValue(strconv.Itoa(int(*instance.ListenerEndpoint.Port))) + } + } + if instance.MasterUsername != nil { + labels[rdsLabelInstanceMasterUsername] = model.LabelValue(*instance.MasterUsername) + } + if instance.MaxAllocatedStorage != nil { + labels[rdsLabelInstanceMaxAllocatedStorage] = model.LabelValue(strconv.Itoa(int(*instance.MaxAllocatedStorage))) + } + if instance.MonitoringInterval != nil { + labels[rdsLabelInstanceMonitoringInterval] = model.LabelValue(strconv.Itoa(int(*instance.MonitoringInterval))) + } + if instance.MonitoringRoleArn != nil { + labels[rdsLabelInstanceMonitoringRoleArn] = model.LabelValue(*instance.MonitoringRoleArn) + } + if instance.MultiAZ != nil { + labels[rdsLabelInstanceMultiAZ] = model.LabelValue(strconv.FormatBool(*instance.MultiAZ)) + } + if instance.MultiTenant != nil { + labels[rdsLabelInstanceMultiTenant] = model.LabelValue(strconv.FormatBool(*instance.MultiTenant)) + } + if instance.NcharCharacterSetName != nil { + labels[rdsLabelInstanceNcharCharacterSetName] = model.LabelValue(*instance.NcharCharacterSetName) + } + if instance.NetworkType != nil { + labels[rdsLabelInstanceNetworkType] = model.LabelValue(*instance.NetworkType) + } + if instance.PercentProgress != nil { + labels[rdsLabelInstancePercentProgress] = model.LabelValue(*instance.PercentProgress) + } + if instance.PerformanceInsightsEnabled != nil { + labels[rdsLabelInstancePerformanceInsightsEnabled] = model.LabelValue(strconv.FormatBool(*instance.PerformanceInsightsEnabled)) + } + if instance.PerformanceInsightsKMSKeyId != nil { + labels[rdsLabelInstancePerformanceInsightsKMSKeyID] = model.LabelValue(*instance.PerformanceInsightsKMSKeyId) + } + if instance.PerformanceInsightsRetentionPeriod != nil { + labels[rdsLabelInstancePerformanceInsightsRetentionPeriod] = model.LabelValue(strconv.Itoa(int(*instance.PerformanceInsightsRetentionPeriod))) + } + if instance.PreferredBackupWindow != nil { + labels[rdsLabelInstancePreferredBackupWindow] = model.LabelValue(*instance.PreferredBackupWindow) + } + if instance.PreferredMaintenanceWindow != nil { + labels[rdsLabelInstancePreferredMaintenanceWindow] = model.LabelValue(*instance.PreferredMaintenanceWindow) + } + if instance.PromotionTier != nil { + labels[rdsLabelInstancePromotionTier] = model.LabelValue(strconv.Itoa(int(*instance.PromotionTier))) + } + if instance.PubliclyAccessible != nil { + labels[rdsLabelInstancePubliclyAccessible] = model.LabelValue(strconv.FormatBool(*instance.PubliclyAccessible)) + } + if instance.ReadReplicaSourceDBClusterIdentifier != nil { + labels[rdsLabelInstanceReadReplicaSourceDBClusterIdentifier] = model.LabelValue(*instance.ReadReplicaSourceDBClusterIdentifier) + } + if instance.ReadReplicaSourceDBInstanceIdentifier != nil { + labels[rdsLabelInstanceReadReplicaSourceDBInstanceIdentifier] = model.LabelValue(*instance.ReadReplicaSourceDBInstanceIdentifier) + } + if instance.ReplicaMode != "" { + labels[rdsLabelInstanceReplicaMode] = model.LabelValue(instance.ReplicaMode) + } + if instance.ResumeFullAutomationModeTime != nil { + labels[rdsLabelInstanceResumeFullAutomationModeTime] = model.LabelValue(instance.ResumeFullAutomationModeTime.Format(time.RFC3339)) + } + if instance.SecondaryAvailabilityZone != nil { + labels[rdsLabelInstanceSecondaryAvailabilityZone] = model.LabelValue(*instance.SecondaryAvailabilityZone) + } + if instance.StorageEncrypted != nil { + labels[rdsLabelInstanceStorageEncrypted] = model.LabelValue(strconv.FormatBool(*instance.StorageEncrypted)) + } + if instance.StorageEncryptionType != "" { + labels[rdsLabelInstanceStorageEncryptionType] = model.LabelValue(instance.StorageEncryptionType) + } + if instance.StorageThroughput != nil { + labels[rdsLabelInstanceStorageThroughput] = model.LabelValue(strconv.Itoa(int(*instance.StorageThroughput))) + } + if instance.StorageType != nil { + labels[rdsLabelInstanceStorageType] = model.LabelValue(*instance.StorageType) + } + if instance.StorageVolumeStatus != nil { + labels[rdsLabelInstanceStorageVolumeStatus] = model.LabelValue(*instance.StorageVolumeStatus) + } + if instance.TdeCredentialArn != nil { + labels[rdsLabelInstanceTdeCredentialArn] = model.LabelValue(*instance.TdeCredentialArn) + } + if instance.Timezone != nil { + labels[rdsLabelInstanceTimezone] = model.LabelValue(*instance.Timezone) + } + if instance.UpgradeRolloutOrder != "" { + labels[rdsLabelInstanceUpgradeRolloutOrder] = model.LabelValue(instance.UpgradeRolloutOrder) + } + if instance.DBSubnetGroup != nil && instance.DBSubnetGroup.DBSubnetGroupName != nil { + labels[rdsLabelInstanceDBSubnetGroup] = model.LabelValue(*instance.DBSubnetGroup.DBSubnetGroupName) + } + if instance.DBSystemId != nil { + labels[rdsLabelInstanceDBSystemID] = model.LabelValue(*instance.DBSystemId) + } + if instance.DatabaseInsightsMode != "" { + labels[rdsLabelInstanceDatabaseInsightsMode] = model.LabelValue(instance.DatabaseInsightsMode) + } + + // Instance tags + for _, tag := range instance.TagList { + if tag.Key != nil && tag.Value != nil { + labels[model.LabelName(rdsLabelInstanceTag+strutil.SanitizeLabelName(*tag.Key))] = model.LabelValue(*tag.Value) + } + } + + // Set the address label + if instance.Endpoint != nil && instance.Endpoint.Address != nil && instance.Endpoint.Port != nil { + labels[model.AddressLabel] = model.LabelValue(net.JoinHostPort(*instance.Endpoint.Address, strconv.Itoa(d.cfg.Port))) + } + + mu.Lock() + tg.Targets = append(tg.Targets, labels) + mu.Unlock() + } + }(cluster, instances) + } + + wg.Wait() + return []*targetgroup.Group{tg}, nil +} diff --git a/discovery/aws/rds_test.go b/discovery/aws/rds_test.go new file mode 100644 index 0000000000..c6ab9d06fe --- /dev/null +++ b/discovery/aws/rds_test.go @@ -0,0 +1,450 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "context" + "net" + "strconv" + "testing" + "time" + + "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" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + + "github.com/prometheus/prometheus/discovery/targetgroup" +) + +// Mock RDS client for testing. +type mockRDSClient struct { + clusters map[string]types.DBCluster + instances map[string][]types.DBInstance +} + +func (m *mockRDSClient) DescribeDBClusters(_ context.Context, input *rds.DescribeDBClustersInput, _ ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error) { + var clusters []types.DBCluster + + if input.DBClusterIdentifier != nil { + // Specific cluster requested + if cluster, ok := m.clusters[*input.DBClusterIdentifier]; ok { + clusters = append(clusters, cluster) + } + } else { + // All clusters + for _, cluster := range m.clusters { + clusters = append(clusters, cluster) + } + } + + return &rds.DescribeDBClustersOutput{ + DBClusters: clusters, + }, nil +} + +func (m *mockRDSClient) DescribeDBInstances(_ context.Context, input *rds.DescribeDBInstancesInput, _ ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) { + var instances []types.DBInstance + + // Check if filtering by cluster + if input.Filters != nil { + for _, filter := range input.Filters { + if filter.Name != nil && *filter.Name == "db-cluster-id" { + for _, clusterID := range filter.Values { + if clusterInstances, ok := m.instances[clusterID]; ok { + instances = append(instances, clusterInstances...) + } + } + } + } + } else { + // All instances + for _, clusterInstances := range m.instances { + instances = append(instances, clusterInstances...) + } + } + + return &rds.DescribeDBInstancesOutput{ + DBInstances: instances, + }, nil +} + +func TestRDSDiscoveryRefresh(t *testing.T) { + testTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + + tests := []struct { + name string + clusters map[string]types.DBCluster + instances map[string][]types.DBInstance + expectedLabels []model.LabelSet + }{ + { + name: "SingleClusterWithInstance", + clusters: map[string]types.DBCluster{ + "arn:aws:rds:us-east-1:123456789012:cluster:test-cluster": { + DBClusterArn: aws.String("arn:aws:rds:us-east-1:123456789012:cluster:test-cluster"), + DBClusterIdentifier: aws.String("test-cluster"), + Engine: aws.String("aurora-postgresql"), + EngineVersion: aws.String("15.4"), + Status: aws.String("available"), + Endpoint: aws.String("test-cluster.cluster-xyz.us-east-1.rds.amazonaws.com"), + Port: aws.Int32(5432), + MasterUsername: aws.String("admin"), + MultiAZ: aws.Bool(true), + ClusterCreateTime: aws.Time(testTime), + DBClusterMembers: []types.DBClusterMember{ + { + DBInstanceIdentifier: aws.String("test-instance-1"), + IsClusterWriter: aws.Bool(true), + }, + }, + TagList: []types.Tag{ + {Key: aws.String("Environment"), Value: aws.String("test")}, + }, + }, + }, + instances: map[string][]types.DBInstance{ + "arn:aws:rds:us-east-1:123456789012:cluster:test-cluster": { + { + DBInstanceArn: aws.String("arn:aws:rds:us-east-1:123456789012:db:test-instance-1"), + DBInstanceIdentifier: aws.String("test-instance-1"), + DBInstanceClass: aws.String("db.r5.large"), + DBInstanceStatus: aws.String("available"), + Engine: aws.String("aurora-postgresql"), + EngineVersion: aws.String("15.4"), + AvailabilityZone: aws.String("us-east-1a"), + DBClusterIdentifier: aws.String("test-cluster"), + PubliclyAccessible: aws.Bool(false), + InstanceCreateTime: aws.Time(testTime), + Endpoint: &types.Endpoint{ + Address: aws.String("test-instance-1.xyz.us-east-1.rds.amazonaws.com"), + Port: aws.Int32(5432), + HostedZoneId: aws.String("Z2R2ITUGPM61AM"), + }, + TagList: []types.Tag{ + {Key: aws.String("Name"), Value: aws.String("test-instance")}, + }, + }, + }, + }, + expectedLabels: []model.LabelSet{ + { + model.AddressLabel: model.LabelValue("test-instance-1.xyz.us-east-1.rds.amazonaws.com:5432"), + rdsLabelClusterDBClusterArn: model.LabelValue("arn:aws:rds:us-east-1:123456789012:cluster:test-cluster"), + rdsLabelClusterDBClusterIdentifier: model.LabelValue("test-cluster"), + rdsLabelClusterEngine: model.LabelValue("aurora-postgresql"), + rdsLabelClusterEngineVersion: model.LabelValue("15.4"), + rdsLabelClusterStatus: model.LabelValue("available"), + rdsLabelClusterEndpoint: model.LabelValue("test-cluster.cluster-xyz.us-east-1.rds.amazonaws.com"), + rdsLabelClusterPort: model.LabelValue("5432"), + rdsLabelClusterMasterUsername: model.LabelValue("admin"), + rdsLabelClusterMultiAZ: model.LabelValue("true"), + rdsLabelClusterClusterCreateTime: model.LabelValue(testTime.Format(time.RFC3339)), + model.LabelName(rdsLabelClusterTag + "Environment"): model.LabelValue("test"), + rdsLabelInstanceDBInstanceArn: model.LabelValue("arn:aws:rds:us-east-1:123456789012:db:test-instance-1"), + rdsLabelInstanceDBInstanceIdentifier: model.LabelValue("test-instance-1"), + rdsLabelInstanceIsClusterWriter: model.LabelValue("true"), + rdsLabelInstanceDBInstanceClass: model.LabelValue("db.r5.large"), + rdsLabelInstanceDBInstanceStatus: model.LabelValue("available"), + rdsLabelInstanceEngine: model.LabelValue("aurora-postgresql"), + rdsLabelInstanceEngineVersion: model.LabelValue("15.4"), + rdsLabelInstanceAvailabilityZone: model.LabelValue("us-east-1a"), + rdsLabelInstanceDBClusterIdentifier: model.LabelValue("test-cluster"), + rdsLabelInstancePubliclyAccessible: model.LabelValue("false"), + rdsLabelInstanceInstanceCreateTime: model.LabelValue(testTime.Format(time.RFC3339)), + rdsLabelInstanceEndpointAddress: model.LabelValue("test-instance-1.xyz.us-east-1.rds.amazonaws.com"), + rdsLabelInstanceEndpointPort: model.LabelValue("5432"), + rdsLabelInstanceEndpointHostedZoneID: model.LabelValue("Z2R2ITUGPM61AM"), + model.LabelName(rdsLabelInstanceTag + "Name"): model.LabelValue("test-instance"), + }, + }, + }, + { + name: "MultipleInstancesInCluster", + clusters: map[string]types.DBCluster{ + "arn:aws:rds:us-west-2:123456789012:cluster:prod-cluster": { + DBClusterArn: aws.String("arn:aws:rds:us-west-2:123456789012:cluster:prod-cluster"), + DBClusterIdentifier: aws.String("prod-cluster"), + Engine: aws.String("aurora-mysql"), + EngineVersion: aws.String("8.0.mysql_aurora.3.04.0"), + Status: aws.String("available"), + DBClusterMembers: []types.DBClusterMember{ + { + DBInstanceIdentifier: aws.String("prod-instance-1"), + IsClusterWriter: aws.Bool(true), + }, + { + DBInstanceIdentifier: aws.String("prod-instance-2"), + IsClusterWriter: aws.Bool(false), + }, + }, + }, + }, + instances: map[string][]types.DBInstance{ + "arn:aws:rds:us-west-2:123456789012:cluster:prod-cluster": { + { + DBInstanceArn: aws.String("arn:aws:rds:us-west-2:123456789012:db:prod-instance-1"), + DBInstanceIdentifier: aws.String("prod-instance-1"), + DBInstanceClass: aws.String("db.r6g.xlarge"), + DBInstanceStatus: aws.String("available"), + Endpoint: &types.Endpoint{ + Address: aws.String("prod-instance-1.xyz.us-west-2.rds.amazonaws.com"), + Port: aws.Int32(3306), + }, + }, + { + DBInstanceArn: aws.String("arn:aws:rds:us-west-2:123456789012:db:prod-instance-2"), + DBInstanceIdentifier: aws.String("prod-instance-2"), + DBInstanceClass: aws.String("db.r6g.xlarge"), + DBInstanceStatus: aws.String("available"), + Endpoint: &types.Endpoint{ + Address: aws.String("prod-instance-2.xyz.us-west-2.rds.amazonaws.com"), + Port: aws.Int32(3306), + }, + }, + }, + }, + expectedLabels: []model.LabelSet{ + { + model.AddressLabel: model.LabelValue("prod-instance-1.xyz.us-west-2.rds.amazonaws.com:3306"), + rdsLabelClusterDBClusterArn: model.LabelValue("arn:aws:rds:us-west-2:123456789012:cluster:prod-cluster"), + rdsLabelClusterDBClusterIdentifier: model.LabelValue("prod-cluster"), + rdsLabelClusterEngine: model.LabelValue("aurora-mysql"), + rdsLabelClusterEngineVersion: model.LabelValue("8.0.mysql_aurora.3.04.0"), + rdsLabelClusterStatus: model.LabelValue("available"), + rdsLabelInstanceDBInstanceArn: model.LabelValue("arn:aws:rds:us-west-2:123456789012:db:prod-instance-1"), + rdsLabelInstanceDBInstanceIdentifier: model.LabelValue("prod-instance-1"), + rdsLabelInstanceIsClusterWriter: model.LabelValue("true"), + rdsLabelInstanceDBInstanceClass: model.LabelValue("db.r6g.xlarge"), + rdsLabelInstanceDBInstanceStatus: model.LabelValue("available"), + rdsLabelInstanceEndpointAddress: model.LabelValue("prod-instance-1.xyz.us-west-2.rds.amazonaws.com"), + rdsLabelInstanceEndpointPort: model.LabelValue("3306"), + }, + { + model.AddressLabel: model.LabelValue("prod-instance-2.xyz.us-west-2.rds.amazonaws.com:3306"), + rdsLabelClusterDBClusterArn: model.LabelValue("arn:aws:rds:us-west-2:123456789012:cluster:prod-cluster"), + rdsLabelClusterDBClusterIdentifier: model.LabelValue("prod-cluster"), + rdsLabelClusterEngine: model.LabelValue("aurora-mysql"), + rdsLabelClusterEngineVersion: model.LabelValue("8.0.mysql_aurora.3.04.0"), + rdsLabelClusterStatus: model.LabelValue("available"), + rdsLabelInstanceDBInstanceArn: model.LabelValue("arn:aws:rds:us-west-2:123456789012:db:prod-instance-2"), + rdsLabelInstanceDBInstanceIdentifier: model.LabelValue("prod-instance-2"), + rdsLabelInstanceIsClusterWriter: model.LabelValue("false"), + rdsLabelInstanceDBInstanceClass: model.LabelValue("db.r6g.xlarge"), + rdsLabelInstanceDBInstanceStatus: model.LabelValue("available"), + rdsLabelInstanceEndpointAddress: model.LabelValue("prod-instance-2.xyz.us-west-2.rds.amazonaws.com"), + rdsLabelInstanceEndpointPort: model.LabelValue("3306"), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := &mockRDSClient{ + clusters: tt.clusters, + instances: tt.instances, + } + + d := &RDSDiscovery{ + rds: mockClient, + cfg: &RDSSDConfig{ + Region: "us-east-1", + RequestConcurrency: 10, + }, + } + + tg := &targetgroup.Group{} + + // Get all cluster ARNs + var clusterARNs []string + for arn := range tt.clusters { + clusterARNs = append(clusterARNs, arn) + } + + clusters, err := d.describeAllDBClusters(context.Background()) + require.NoError(t, err) + require.Len(t, clusters, len(tt.clusters)) + + instances := make(map[string][]types.DBInstance) + for _, arn := range clusterARNs { + clusterInstances, err := d.describeDBInstances(context.Background(), arn) + require.NoError(t, err) + instances[arn] = clusterInstances + } + + // Build targets like the refresh function does + for _, cluster := range clusters { + writerMap := make(map[string]bool) + for _, member := range cluster.DBClusterMembers { + if member.DBInstanceIdentifier != nil && member.IsClusterWriter != nil { + writerMap[*member.DBInstanceIdentifier] = *member.IsClusterWriter + } + } + + clusterInstances := instances[*cluster.DBClusterArn] + for _, instance := range clusterInstances { + labels := model.LabelSet{} + + // Add basic cluster labels + if cluster.DBClusterArn != nil { + labels[rdsLabelClusterDBClusterArn] = model.LabelValue(*cluster.DBClusterArn) + } + if cluster.DBClusterIdentifier != nil { + labels[rdsLabelClusterDBClusterIdentifier] = model.LabelValue(*cluster.DBClusterIdentifier) + } + if cluster.Engine != nil { + labels[rdsLabelClusterEngine] = model.LabelValue(*cluster.Engine) + } + if cluster.EngineVersion != nil { + labels[rdsLabelClusterEngineVersion] = model.LabelValue(*cluster.EngineVersion) + } + if cluster.Status != nil { + labels[rdsLabelClusterStatus] = model.LabelValue(*cluster.Status) + } + if cluster.Endpoint != nil { + labels[rdsLabelClusterEndpoint] = model.LabelValue(*cluster.Endpoint) + } + if cluster.Port != nil { + labels[rdsLabelClusterPort] = model.LabelValue(strconv.Itoa(int(*cluster.Port))) + } + if cluster.MasterUsername != nil { + labels[rdsLabelClusterMasterUsername] = model.LabelValue(*cluster.MasterUsername) + } + if cluster.MultiAZ != nil { + labels[rdsLabelClusterMultiAZ] = model.LabelValue(strconv.FormatBool(*cluster.MultiAZ)) + } + if cluster.ClusterCreateTime != nil { + labels[rdsLabelClusterClusterCreateTime] = model.LabelValue(cluster.ClusterCreateTime.Format(time.RFC3339)) + } + + // Cluster tags + for _, tag := range cluster.TagList { + if tag.Key != nil && tag.Value != nil { + labels[model.LabelName(rdsLabelClusterTag+*tag.Key)] = model.LabelValue(*tag.Value) + } + } + + // Add basic instance labels + if instance.DBInstanceArn != nil { + labels[rdsLabelInstanceDBInstanceArn] = model.LabelValue(*instance.DBInstanceArn) + } + if instance.DBInstanceIdentifier != nil { + labels[rdsLabelInstanceDBInstanceIdentifier] = model.LabelValue(*instance.DBInstanceIdentifier) + if isWriter, found := writerMap[*instance.DBInstanceIdentifier]; found { + labels[rdsLabelInstanceIsClusterWriter] = model.LabelValue(strconv.FormatBool(isWriter)) + } + } + if instance.DBInstanceClass != nil { + labels[rdsLabelInstanceDBInstanceClass] = model.LabelValue(*instance.DBInstanceClass) + } + if instance.DBInstanceStatus != nil { + labels[rdsLabelInstanceDBInstanceStatus] = model.LabelValue(*instance.DBInstanceStatus) + } + if instance.Engine != nil { + labels[rdsLabelInstanceEngine] = model.LabelValue(*instance.Engine) + } + if instance.EngineVersion != nil { + labels[rdsLabelInstanceEngineVersion] = model.LabelValue(*instance.EngineVersion) + } + if instance.AvailabilityZone != nil { + labels[rdsLabelInstanceAvailabilityZone] = model.LabelValue(*instance.AvailabilityZone) + } + if instance.DBClusterIdentifier != nil { + labels[rdsLabelInstanceDBClusterIdentifier] = model.LabelValue(*instance.DBClusterIdentifier) + } + if instance.PubliclyAccessible != nil { + labels[rdsLabelInstancePubliclyAccessible] = model.LabelValue(strconv.FormatBool(*instance.PubliclyAccessible)) + } + if instance.InstanceCreateTime != nil { + labels[rdsLabelInstanceInstanceCreateTime] = model.LabelValue(instance.InstanceCreateTime.Format(time.RFC3339)) + } + if instance.Endpoint != nil { + if instance.Endpoint.Address != nil { + labels[rdsLabelInstanceEndpointAddress] = model.LabelValue(*instance.Endpoint.Address) + } + if instance.Endpoint.Port != nil { + labels[rdsLabelInstanceEndpointPort] = model.LabelValue(strconv.Itoa(int(*instance.Endpoint.Port))) + } + if instance.Endpoint.HostedZoneId != nil { + labels[rdsLabelInstanceEndpointHostedZoneID] = model.LabelValue(*instance.Endpoint.HostedZoneId) + } + } + + // Instance tags + for _, tag := range instance.TagList { + if tag.Key != nil && tag.Value != nil { + labels[model.LabelName(rdsLabelInstanceTag+*tag.Key)] = model.LabelValue(*tag.Value) + } + } + + // Set address + if instance.Endpoint != nil && instance.Endpoint.Address != nil && instance.Endpoint.Port != nil { + labels[model.AddressLabel] = model.LabelValue(net.JoinHostPort(*instance.Endpoint.Address, strconv.Itoa(int(*instance.Endpoint.Port)))) + } + + tg.Targets = append(tg.Targets, labels) + } + } + + require.Len(t, tg.Targets, len(tt.expectedLabels)) + + // Verify each expected label set is present + for _, expectedLabels := range tt.expectedLabels { + found := false + for _, target := range tg.Targets { + if target[model.AddressLabel] == expectedLabels[model.AddressLabel] { + found = true + // Check all expected labels are present with correct values + for key, expectedValue := range expectedLabels { + require.Equal(t, expectedValue, target[key], "Label %s mismatch", key) + } + break + } + } + require.True(t, found, "Expected target with address %s not found", expectedLabels[model.AddressLabel]) + } + }) + } +} + +func TestDescribeAllDBClusters(t *testing.T) { + mockClient := &mockRDSClient{ + clusters: map[string]types.DBCluster{ + "arn:aws:rds:us-east-1:123456789012:cluster:cluster-1": { + DBClusterArn: aws.String("arn:aws:rds:us-east-1:123456789012:cluster:cluster-1"), + DBClusterIdentifier: aws.String("cluster-1"), + }, + "arn:aws:rds:us-east-1:123456789012:cluster:cluster-2": { + DBClusterArn: aws.String("arn:aws:rds:us-east-1:123456789012:cluster:cluster-2"), + DBClusterIdentifier: aws.String("cluster-2"), + }, + }, + instances: map[string][]types.DBInstance{}, + } + + d := &RDSDiscovery{ + rds: mockClient, + cfg: &RDSSDConfig{ + RequestConcurrency: 10, + }, + } + + clusters, err := d.describeAllDBClusters(context.Background()) + require.NoError(t, err) + require.Len(t, clusters, 2) + require.Contains(t, clusters, "arn:aws:rds:us-east-1:123456789012:cluster:cluster-1") + require.Contains(t, clusters, "arn:aws:rds:us-east-1:123456789012:cluster:cluster-2") +} diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 6d9da2dd45..9f03c000ef 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -1135,11 +1135,177 @@ The following meta labels are available on targets during [relabeling](#relabel_ * `__meta_elasticache_cache_cluster_node_endpoint_port`: cache node endpoint port * `__meta_elasticache_cache_cluster_tag_`: each cache cluster tag value, keyed by tag name +#### `rds` + +The `rds` role discovers targets from [AWS RDS](https://aws.amazon.com/rds/) +database instances within clusters. One target is created for each DB instance +within the specified clusters. The endpoint address and port of each instance is used by default. + +The IAM credentials used must have the `rds:DescribeDBClusters` and `rds:DescribeDBInstances` +permissions to discover scrape targets. + +The following meta labels are available on targets during [relabeling](#relabel_config): + +**Cluster labels:** + +* `__meta_rds_cluster_activity_stream_kinesis_stream_name`: the name of the Amazon Kinesis data stream used for database activity stream +* `__meta_rds_cluster_activity_stream_kms_key_id`: the AWS KMS key identifier used for encrypting the database activity stream +* `__meta_rds_cluster_activity_stream_mode`: the mode of the database activity stream (sync or async) +* `__meta_rds_cluster_activity_stream_status`: the status of the database activity stream +* `__meta_rds_cluster_allocated_storage`: the allocated storage size in gibibytes (GiB) +* `__meta_rds_cluster_arn`: the Amazon Resource Name (ARN) of the DB cluster +* `__meta_rds_cluster_auto_minor_version_upgrade`: whether automatic minor version upgrades are enabled +* `__meta_rds_cluster_automatic_restart_time`: the time when a stopped cluster will be automatically restarted +* `__meta_rds_cluster_aws_backup_recovery_point_arn`: the ARN of the recovery point in AWS Backup +* `__meta_rds_cluster_backtrack_consumed_change_records`: the number of change records stored for backtrack +* `__meta_rds_cluster_backtrack_window`: the target backtrack window in hours +* `__meta_rds_cluster_backup_retention_period`: the number of days for which automated backups are retained +* `__meta_rds_cluster_capacity`: the current capacity of an Aurora Serverless DB cluster +* `__meta_rds_cluster_character_set_name`: the name of the character set +* `__meta_rds_cluster_clone_group_id`: the ID of the clone group +* `__meta_rds_cluster_cluster_create_time`: the time when the DB cluster was created +* `__meta_rds_cluster_cluster_scalability_type`: the scalability type of the cluster +* `__meta_rds_cluster_copy_tags_to_snapshot`: whether tags are copied from the cluster to snapshots +* `__meta_rds_cluster_cross_account_clone`: whether the DB cluster is a cross-account clone +* `__meta_rds_cluster_database_insights_mode`: the mode of Database Insights +* `__meta_rds_cluster_database_name`: the database name +* `__meta_rds_cluster_db_system_id`: the Oracle system ID (Oracle SID) +* `__meta_rds_cluster_deletion_protection`: whether deletion protection is enabled +* `__meta_rds_cluster_earliest_backtrack_time`: the earliest time to which a database can be restored with backtrack +* `__meta_rds_cluster_earliest_restorable_time`: the earliest time to which a database can be restored +* `__meta_rds_cluster_endpoint`: the endpoint of the DB cluster +* `__meta_rds_cluster_engine_lifecycle_support`: the engine lifecycle support value +* `__meta_rds_cluster_engine_mode`: the engine mode of the cluster (provisioned, serverless, etc.) +* `__meta_rds_cluster_engine_version`: the version of the database engine +* `__meta_rds_cluster_engine`: the database engine of the DB cluster +* `__meta_rds_cluster_global_cluster_identifier`: the identifier of the global cluster +* `__meta_rds_cluster_global_write_forwarding_requested`: whether global write forwarding is requested +* `__meta_rds_cluster_global_write_forwarding_status`: the status of global write forwarding +* `__meta_rds_cluster_hosted_zone_id`: the Route 53 hosted zone ID +* `__meta_rds_cluster_http_endpoint_enabled`: whether the HTTP endpoint is enabled +* `__meta_rds_cluster_iam_database_authentication_enabled`: whether the DB cluster has IAM database authentication enabled +* `__meta_rds_cluster_identifier`: the identifier of the DB cluster +* `__meta_rds_cluster_instance_class`: the compute and memory capacity class of the DB cluster +* `__meta_rds_cluster_io_optimized_next_allowed_modification_time`: the time when the next IO optimization configuration change is allowed +* `__meta_rds_cluster_iops`: the provisioned IOPS (I/O operations per second) value +* `__meta_rds_cluster_kms_key_id`: the AWS KMS key identifier for the encrypted cluster +* `__meta_rds_cluster_latest_restorable_time`: the latest time to which a database can be restored +* `__meta_rds_cluster_local_write_forwarding_status`: the status of local write forwarding +* `__meta_rds_cluster_master_username`: the master username +* `__meta_rds_cluster_monitoring_interval`: the interval in seconds between enhanced monitoring metrics collection +* `__meta_rds_cluster_monitoring_role_arn`: the ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch +* `__meta_rds_cluster_multi_az`: whether the DB cluster is multi-AZ +* `__meta_rds_cluster_network_type`: the network type (IPV4 or DUAL) +* `__meta_rds_cluster_parameter_group`: the name of the DB cluster parameter group +* `__meta_rds_cluster_percent_progress`: the progress percentage of the DB cluster operation +* `__meta_rds_cluster_performance_insights_enabled`: whether Performance Insights is enabled +* `__meta_rds_cluster_performance_insights_kms_key_id`: the AWS KMS key identifier for encrypting Performance Insights data +* `__meta_rds_cluster_performance_insights_retention_period`: the retention period for Performance Insights data +* `__meta_rds_cluster_port`: the port the DB cluster is listening on +* `__meta_rds_cluster_preferred_backup_window`: the daily time range during which automated backups are created +* `__meta_rds_cluster_preferred_maintenance_window`: the weekly time range during which system maintenance can occur +* `__meta_rds_cluster_publicly_accessible`: whether the DB cluster is publicly accessible +* `__meta_rds_cluster_reader_endpoint`: the reader endpoint of the DB cluster +* `__meta_rds_cluster_replication_source_identifier`: the identifier of the source DB cluster if this is a read replica +* `__meta_rds_cluster_resource_id`: the AWS Region-unique immutable identifier for the DB cluster +* `__meta_rds_cluster_serverless_v2_platform_version`: the platform version of the Aurora Serverless v2 DB cluster +* `__meta_rds_cluster_status`: the status of the DB cluster +* `__meta_rds_cluster_storage_encrypted`: whether the DB cluster is storage encrypted +* `__meta_rds_cluster_storage_encryption_type`: the storage encryption type +* `__meta_rds_cluster_storage_throughput`: the storage throughput in MiBps +* `__meta_rds_cluster_storage_type`: the storage type +* `__meta_rds_cluster_subnet_group`: the name of the subnet group associated with the DB cluster +* `__meta_rds_cluster_tag_`: each tag value of the DB cluster +* `__meta_rds_cluster_upgrade_rollout_order`: the upgrade rollout order + +**Instance labels:** + +* `__meta_rds_instance_activity_stream_engine_native_audit_fields_included`: whether engine-native audit fields are included in the database activity stream +* `__meta_rds_instance_activity_stream_kinesis_stream_name`: the name of the Amazon Kinesis data stream used for the database activity stream +* `__meta_rds_instance_activity_stream_kms_key_id`: the AWS KMS key identifier used for encrypting the database activity stream +* `__meta_rds_instance_activity_stream_mode`: the mode of the database activity stream (sync or async) +* `__meta_rds_instance_activity_stream_policy_status`: the policy status of the database activity stream +* `__meta_rds_instance_activity_stream_status`: the status of the database activity stream +* `__meta_rds_instance_allocated_storage`: the allocated storage size in gibibytes (GiB) +* `__meta_rds_instance_arn`: the Amazon Resource Name (ARN) of the DB instance +* `__meta_rds_instance_auto_minor_version_upgrade`: whether automatic minor version upgrades are enabled +* `__meta_rds_instance_automatic_restart_time`: the time when a stopped instance will be automatically restarted +* `__meta_rds_instance_automation_mode`: the automation mode of the instance +* `__meta_rds_instance_availability_zone`: the availability zone of the DB instance +* `__meta_rds_instance_aws_backup_recovery_point_arn`: the ARN of the recovery point in AWS Backup +* `__meta_rds_instance_backup_retention_period`: the number of days for which automated backups are retained +* `__meta_rds_instance_backup_target`: the backup target (region or outposts) +* `__meta_rds_instance_ca_certificate_identifier`: the identifier of the CA certificate for the DB instance +* `__meta_rds_instance_character_set_name`: the name of the character set +* `__meta_rds_instance_class`: the compute and memory capacity class of the DB instance +* `__meta_rds_instance_copy_tags_to_snapshot`: whether tags are copied from the instance to snapshots +* `__meta_rds_instance_custom_iam_instance_profile`: the instance profile associated with the underlying Amazon EC2 instance +* `__meta_rds_instance_customer_owned_ip_enabled`: whether a customer-owned IP address (CoIP) is enabled +* `__meta_rds_instance_database_insights_mode`: the mode of Database Insights +* `__meta_rds_instance_db_cluster_identifier`: the identifier of the DB cluster this instance is a member of +* `__meta_rds_instance_db_name`: the database name +* `__meta_rds_instance_db_system_id`: the Oracle system ID (Oracle SID) +* `__meta_rds_instance_dedicated_log_volume`: whether the DB instance has a dedicated log volume +* `__meta_rds_instance_deletion_protection`: whether deletion protection is enabled +* `__meta_rds_instance_endpoint_address`: the DNS address of the DB instance +* `__meta_rds_instance_endpoint_hosted_zone_id`: the Route 53 hosted zone ID of the endpoint +* `__meta_rds_instance_endpoint_port`: the port that the DB instance listens on +* `__meta_rds_instance_engine_lifecycle_support`: the engine lifecycle support value +* `__meta_rds_instance_engine_version`: the version of the database engine +* `__meta_rds_instance_engine`: the database engine that the DB instance uses +* `__meta_rds_instance_enhanced_monitoring_resource_arn`: the ARN of the Amazon CloudWatch Logs log stream for enhanced monitoring +* `__meta_rds_instance_iam_database_authentication_enabled`: whether IAM database authentication is enabled +* `__meta_rds_instance_identifier`: the identifier of the DB instance +* `__meta_rds_instance_instance_create_time`: the time when the DB instance was created +* `__meta_rds_instance_iops`: the provisioned IOPS (I/O operations per second) value +* `__meta_rds_instance_is_cluster_writer`: whether the instance is the cluster writer (true/false) +* `__meta_rds_instance_is_storage_config_upgrade_available`: whether a storage configuration upgrade is available +* `__meta_rds_instance_kms_key_id`: the AWS KMS key identifier for the encrypted instance +* `__meta_rds_instance_latest_restorable_time`: the latest time to which a database can be restored +* `__meta_rds_instance_license_model`: the license model information +* `__meta_rds_instance_listener_endpoint_address`: the DNS address of the listener endpoint +* `__meta_rds_instance_listener_endpoint_hosted_zone_id`: the Route 53 hosted zone ID of the listener endpoint +* `__meta_rds_instance_listener_endpoint_port`: the port that the listener endpoint listens on +* `__meta_rds_instance_master_username`: the master username +* `__meta_rds_instance_max_allocated_storage`: the upper limit in gibibytes to which storage can be scaled automatically +* `__meta_rds_instance_monitoring_interval`: the interval in seconds between enhanced monitoring metrics collection +* `__meta_rds_instance_monitoring_role_arn`: the ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch +* `__meta_rds_instance_multi_az`: whether the DB instance is a Multi-AZ deployment +* `__meta_rds_instance_multi_tenant`: whether the instance is in a multi-tenant configuration +* `__meta_rds_instance_nchar_character_set_name`: the national character set name +* `__meta_rds_instance_network_type`: the network type (IPV4 or DUAL) +* `__meta_rds_instance_percent_progress`: the progress percentage of the DB instance operation +* `__meta_rds_instance_performance_insights_enabled`: whether Performance Insights is enabled +* `__meta_rds_instance_performance_insights_kms_key_id`: the AWS KMS key identifier for encrypting Performance Insights data +* `__meta_rds_instance_performance_insights_retention_period`: the retention period for Performance Insights data +* `__meta_rds_instance_port`: the port that the DB instance listens on +* `__meta_rds_instance_preferred_backup_window`: the daily time range during which automated backups are created +* `__meta_rds_instance_preferred_maintenance_window`: the weekly time range during which system maintenance can occur +* `__meta_rds_instance_promotion_tier`: the order in which an Aurora replica is promoted to primary instance after a failure +* `__meta_rds_instance_publicly_accessible`: whether the DB instance is publicly accessible +* `__meta_rds_instance_read_replica_source_db_cluster_identifier`: the identifier of the source DB cluster if this instance is a read replica +* `__meta_rds_instance_read_replica_source_db_instance_identifier`: the identifier of the source DB instance if this instance is a read replica +* `__meta_rds_instance_replica_mode`: the replica mode (open-read-only or mounted) +* `__meta_rds_instance_resource_id`: the AWS Region-unique immutable identifier for the DB instance +* `__meta_rds_instance_resume_full_automation_mode_time`: the time when the DB instance will resume full automation +* `__meta_rds_instance_secondary_availability_zone`: the secondary availability zone for Multi-AZ instances +* `__meta_rds_instance_status`: the status of the DB instance +* `__meta_rds_instance_storage_encrypted`: whether the DB instance is storage encrypted +* `__meta_rds_instance_storage_encryption_type`: the storage encryption type +* `__meta_rds_instance_storage_throughput`: the storage throughput in MiBps +* `__meta_rds_instance_storage_type`: the storage type +* `__meta_rds_instance_storage_volume_status`: the status of the storage volume +* `__meta_rds_instance_subnet_group`: the name of the subnet group associated with the DB instance +* `__meta_rds_instance_tag_`: each tag value of the DB instance +* `__meta_rds_instance_tde_credential_arn`: the ARN for the TDE encryption key +* `__meta_rds_instance_timezone`: the time zone of the DB instance +* `__meta_rds_instance_upgrade_rollout_order`: the upgrade rollout order + See below for the configuration options for AWS discovery: ```yaml # The AWS role to use for service discovery. -# Must be one of: ec2, lightsail, ecs, msk, or elasticache. +# Must be one of: ec2, lightsail, ecs, msk, elasticache, or rds. role: # The AWS region. If blank, the region from the instance metadata is used. @@ -1175,7 +1341,7 @@ filters: [ - name: values: , [...] ] -# List of ECS, ElastiCache, or MSK cluster identifiers (ecs, elasticache, and msk roles only) to discover. +# List of ECS, ElastiCache, MSK, or RDS cluster identifiers (ecs, elasticache, msk, and rds roles only) to discover. # A List of ARNs of clusters to discover. If empty, all clusters in the region are discovered. # This can significantly improve performance when you only need to monitor specific clusters/caches. [ clusters: [, ...] ] diff --git a/go.mod b/go.mod index 860ac1bea4..04b0682e7b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/KimMachineGun/automemlimit v0.7.5 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b - github.com/aws/aws-sdk-go-v2 v1.41.1 + github.com/aws/aws-sdk-go-v2 v1.41.2 github.com/aws/aws-sdk-go-v2/config v1.32.9 github.com/aws/aws-sdk-go-v2/credentials v1.19.9 github.com/aws/aws-sdk-go-v2/service/ec2 v1.290.0 @@ -19,6 +19,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.9 github.com/aws/aws-sdk-go-v2/service/kafka v1.48.0 github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 + github.com/aws/aws-sdk-go-v2/service/rds v1.116.1 github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 github.com/aws/smithy-go v1.24.1 github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 @@ -143,11 +144,11 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 74b1217d90..7140d35b42 100644 --- a/go.sum +++ b/go.sum @@ -49,18 +49,18 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= -github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls= +github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4= github.com/aws/aws-sdk-go-v2/config v1.32.9 h1:ktda/mtAydeObvJXlHzyGpK1xcsLaP16zfUPDGoW90A= github.com/aws/aws-sdk-go-v2/config v1.32.9/go.mod h1:U+fCQ+9QKsLW786BCfEjYRj34VVTbPdsLP3CHSYXMOI= github.com/aws/aws-sdk-go-v2/credentials v1.19.9 h1:sWvTKsyrMlJGEuj/WgrwilpoJ6Xa1+KhIpGdzw7mMU8= github.com/aws/aws-sdk-go-v2/credentials v1.19.9/go.mod h1:+J44MBhmfVY/lETFiKI+klz0Vym2aCmIjqgClMmW82w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/service/ec2 v1.290.0 h1:Ub4CvLWf8wEQ7/pEiqXM9tTsHXf2BokPLwbqEvrmAq0= @@ -69,14 +69,16 @@ github.com/aws/aws-sdk-go-v2/service/ecs v1.72.0 h1:hggRKpv26DpYMOik3wWo1Ty5MkAN github.com/aws/aws-sdk-go-v2/service/ecs v1.72.0/go.mod h1:pMlGFDpHoLTJOIZHGdJOAWmi+xeIlQXuFTuQxs1epYE= github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.9 h1:hTgZLyNoDWphZUtTtcvQh0LP6TZO0mtdSfZK/GObDLk= github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.9/go.mod h1:91RkIYy9ubykxB50XGYDsbljLZnrZ6rp/Urt4rZrbwQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk= github.com/aws/aws-sdk-go-v2/service/kafka v1.48.0 h1:CKRWqysU9INeoi0nTI9gDzDAJk+GatzFduVYxT/wkrw= github.com/aws/aws-sdk-go-v2/service/kafka v1.48.0/go.mod h1:tWnHS64fg5ydLHivFlCAtEh/1iMNzr56QsH3F+UTwD4= github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 h1:VM5e5M39zRSs+aT0O9SoxHjUXqXxhbw3Yi0FdMQWPIc= github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11/go.mod h1:0jvzYPIQGCpnY/dmdaotTk2JH4QuBlnW0oeyrcGLWJ4= +github.com/aws/aws-sdk-go-v2/service/rds v1.116.1 h1:a5PMhM3lOcu2DKgvYGjhCDToKQnz9VEUo9iSc5+DsyA= +github.com/aws/aws-sdk-go-v2/service/rds v1.116.1/go.mod h1:bMaMwbVQ96bx42kDw/Ko+YiDyT/UCotPO+1RDp6lq7E= github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= github.com/aws/aws-sdk-go-v2/service/sso v1.30.10 h1:+VTRawC4iVY58pS/lzpo0lnoa/SYNGF4/B/3/U5ro8Y=