@@ -91,6 +91,14 @@ func runSync(regions *[]string, name *string) func(cmd *cobra.Command, args []st
9191 return
9292 }
9393
94+ // List and process clusters for this region
95+ clusterResources , err := processClusters (ctx , rdsClient , regionName )
96+ if err != nil {
97+ log .Error ("Failed to process clusters" , "region" , regionName , "error" , err )
98+ } else {
99+ resources = append (resources , clusterResources ... )
100+ }
101+
94102 if len (resources ) > 0 {
95103 mu .Lock ()
96104 allResources = append (allResources , resources ... )
@@ -245,6 +253,182 @@ func processInstance(ctx context.Context, instance *types.DBInstance, region str
245253 }, nil
246254}
247255
256+ func processClusters (ctx context.Context , rdsClient * rds.Client , region string ) ([]api.ResourceProviderResource , error ) {
257+ var resources []api.ResourceProviderResource
258+ var marker * string
259+
260+ for {
261+ resp , err := rdsClient .DescribeDBClusters (ctx , & rds.DescribeDBClustersInput {
262+ Marker : marker ,
263+ })
264+ if err != nil {
265+ return nil , fmt .Errorf ("failed to list RDS clusters: %w" , err )
266+ }
267+
268+ for _ , cluster := range resp .DBClusters {
269+ resource , err := processCluster (& cluster , region )
270+ if err != nil {
271+ log .Error ("Failed to process RDS cluster" , "identifier" , * cluster .DBClusterIdentifier , "error" , err )
272+ continue
273+ }
274+ resources = append (resources , resource )
275+ }
276+
277+ if resp .Marker == nil {
278+ break
279+ }
280+ marker = resp .Marker
281+ }
282+
283+ log .Info ("Found RDS clusters" , "region" , region , "count" , len (resources ))
284+ return resources , nil
285+ }
286+
287+ func processCluster (cluster * types.DBCluster , region string ) (api.ResourceProviderResource , error ) {
288+ port := int32 (5432 )
289+ if cluster .Port != nil && * cluster .Port != 0 {
290+ port = * cluster .Port
291+ }
292+
293+ host := ""
294+ if cluster .Endpoint != nil {
295+ host = * cluster .Endpoint
296+ }
297+
298+ identifier := ""
299+ if cluster .DBClusterArn != nil {
300+ identifier = * cluster .DBClusterArn
301+ } else if cluster .DBClusterIdentifier != nil {
302+ identifier = fmt .Sprintf ("arn:aws:rds:%s::%s" , region , * cluster .DBClusterIdentifier )
303+ }
304+
305+ consoleUrl := fmt .Sprintf ("https://%s.console.aws.amazon.com/rds/home?region=%s#database:id=%s;is-cluster=true" ,
306+ region , region , * cluster .DBClusterIdentifier )
307+
308+ metadata := buildClusterMetadata (cluster , region , host , int (port ), consoleUrl )
309+
310+ dbType := getNormalizedDBType (* cluster .Engine )
311+
312+ awsClusterConfig := map [string ]any {
313+ "engine" : * cluster .Engine ,
314+ "engineVersion" : * cluster .EngineVersion ,
315+ "region" : region ,
316+ "status" : * cluster .Status ,
317+ "dbType" : dbType ,
318+ "multiAZ" : cluster .MultiAZ ,
319+ }
320+ if cluster .KmsKeyId != nil {
321+ awsClusterConfig ["kmsKeyId" ] = * cluster .KmsKeyId
322+ }
323+ if cluster .MasterUsername != nil {
324+ awsClusterConfig ["masterUsername" ] = * cluster .MasterUsername
325+ }
326+ if cluster .MasterUserSecret != nil {
327+ secret := map [string ]any {
328+ "secretArn" : getStringPtrValue (cluster .MasterUserSecret .SecretArn ),
329+ "secretStatus" : getStringPtrValue (cluster .MasterUserSecret .SecretStatus ),
330+ }
331+ if cluster .MasterUserSecret .KmsKeyId != nil {
332+ secret ["kmsKeyId" ] = * cluster .MasterUserSecret .KmsKeyId
333+ }
334+ awsClusterConfig ["masterUserSecret" ] = secret
335+ }
336+
337+ return api.ResourceProviderResource {
338+ Version : "ctrlplane.dev/database/v1" ,
339+ Kind : "AmazonRelationalDatabaseCluster" ,
340+ Name : * cluster .DBClusterIdentifier ,
341+ Identifier : identifier ,
342+ Config : map [string ]any {
343+ "name" : * cluster .DBClusterIdentifier ,
344+ "host" : host ,
345+ "port" : port ,
346+ "ssl" : true ,
347+ "awsRelationalDatabaseCluster" : awsClusterConfig ,
348+ },
349+ Metadata : metadata ,
350+ }, nil
351+ }
352+
353+ func buildClusterMetadata (cluster * types.DBCluster , region , host string , port int , consoleUrl string ) map [string ]string {
354+ dbType := getNormalizedDBType (* cluster .Engine )
355+ major , minor , patch , prerelease := parseEngineVersion (* cluster .EngineVersion )
356+
357+ multiAZ := false
358+ if cluster .MultiAZ != nil {
359+ multiAZ = * cluster .MultiAZ
360+ }
361+
362+ metadata := map [string ]string {
363+ kinds .DBMetadataType : dbType ,
364+ kinds .DBMetadataName : * cluster .DBClusterIdentifier ,
365+ kinds .DBMetadataRegion : region ,
366+ kinds .DBMetadataState : * cluster .Status ,
367+ kinds .DBMetadataVersion : * cluster .EngineVersion ,
368+ kinds .DBMetadataHost : host ,
369+ kinds .DBMetadataPort : strconv .Itoa (port ),
370+ kinds .DBMetadataSSL : "true" ,
371+ kinds .DBMetadataMultiAZ : strconv .FormatBool (multiAZ ),
372+
373+ kinds .DBMetadataVersionMajor : major ,
374+ kinds .DBMetadataVersionMinor : minor ,
375+ kinds .DBMetadataVersionPatch : patch ,
376+ kinds .DBMetadataVersionPrerelease : prerelease ,
377+
378+ "aws/region" : region ,
379+ "aws/resource-type" : "rds-cluster" ,
380+ "aws/status" : * cluster .Status ,
381+ "aws/console-url" : consoleUrl ,
382+ "aws/engine" : * cluster .Engine ,
383+ "aws/db-type" : dbType ,
384+ "aws/is-aurora" : strconv .FormatBool (strings .Contains (strings .ToLower (* cluster .Engine ), "aurora" )),
385+ "aws/cluster-member-count" : strconv .Itoa (len (cluster .DBClusterMembers )),
386+
387+ "compute/multi-az" : strconv .FormatBool (multiAZ ),
388+
389+ kinds .CtrlplaneMetadataLinks : fmt .Sprintf ("{ \" AWS Console\" : \" %s\" }" , consoleUrl ),
390+ }
391+
392+ if cluster .ReaderEndpoint != nil {
393+ metadata ["network/reader-endpoint" ] = * cluster .ReaderEndpoint
394+ }
395+ if cluster .DBSubnetGroup != nil {
396+ metadata ["network/subnet-group" ] = * cluster .DBSubnetGroup
397+ }
398+
399+ if cluster .AllocatedStorage != nil && * cluster .AllocatedStorage != 0 {
400+ metadata ["compute/storage-allocated-gb" ] = strconv .FormatInt (int64 (* cluster .AllocatedStorage ), 10 )
401+ }
402+ if cluster .StorageType != nil {
403+ metadata ["compute/storage-type" ] = * cluster .StorageType
404+ }
405+ if cluster .Iops != nil {
406+ metadata ["compute/storage-iops" ] = strconv .FormatInt (int64 (* cluster .Iops ), 10 )
407+ }
408+
409+ if cluster .BackupRetentionPeriod != nil && * cluster .BackupRetentionPeriod != 0 {
410+ metadata [kinds .DBMetadataBackupRetention ] = strconv .FormatInt (int64 (* cluster .BackupRetentionPeriod ), 10 )
411+ }
412+ if cluster .PreferredBackupWindow != nil {
413+ metadata [kinds .DBMetadataBackupWindow ] = * cluster .PreferredBackupWindow
414+ }
415+ if cluster .LatestRestorableTime != nil {
416+ metadata ["backup/latest-restorable" ] = cluster .LatestRestorableTime .String ()
417+ }
418+
419+ if cluster .PreferredMaintenanceWindow != nil {
420+ metadata ["maintenance/window" ] = * cluster .PreferredMaintenanceWindow
421+ }
422+
423+ for _ , tag := range cluster .TagList {
424+ if tag .Key != nil && tag .Value != nil {
425+ metadata [fmt .Sprintf ("tags/%s" , * tag .Key )] = * tag .Value
426+ }
427+ }
428+
429+ return metadata
430+ }
431+
248432// Helper function to safely get string value from pointer
249433func getStringPtrValue (ptr * string ) string {
250434 if ptr == nil {
0 commit comments