Skip to content

Commit 186efd6

Browse files
authored
feat: sync the rds cluster resource (#62)
1 parent c8802fc commit 186efd6

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

  • cmd/ctrlc/root/sync/aws/rds

cmd/ctrlc/root/sync/aws/rds/rds.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
249433
func getStringPtrValue(ptr *string) string {
250434
if ptr == nil {

0 commit comments

Comments
 (0)