Last active
August 9, 2023 08:33
-
-
Save niski84/d83b5f24f5199ee9ad6f3515d1706eaa to your computer and use it in GitHub Desktop.
RDS Cloning
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"github.com/aws/aws-sdk-go/aws" | |
"github.com/aws/aws-sdk-go/aws/session" | |
"github.com/aws/aws-sdk-go/service/rds" | |
"log" | |
"time" | |
) | |
type CloneRDSInput struct { | |
DBHostName string // Example: "mydbinstance.c6c8vnl3bgkz.us-west-2.rds.amazonaws.com" | |
NewRdsDBIdentifier string // Optional; if blank, it will use the DB identifier found for DBHostName with date appended, e.g., "existing-db-identifier-20230606-123456" | |
} | |
func CloneRDS(input CloneRDSInput) error { | |
// 1. Find the RDS instance identifier using a hostname | |
dbIdentifier, err := FindRDSInstanceIdentifierByHostname(input.DBHostName) | |
if err != nil { | |
return fmt.Errorf("Error finding DB identifier: %v", err) | |
} | |
// If NewRdsDBIdentifier is blank, use the dbIdentifier and append a date | |
if input.NewRdsDBIdentifier == "" { | |
dateSuffix := time.Now().Format("20060102-150405") | |
input.NewRdsDBIdentifier = dbIdentifier + "-" + dateSuffix | |
} | |
// 2. Create a snapshot of the RDS instance (using blank snapshot identifier) | |
createSnapshotInput := CreateSnapshotInput{ | |
DBIdentifier: dbIdentifier, | |
} | |
err = CreateRDSSnapshot(createSnapshotInput) | |
if err != nil { | |
return fmt.Errorf("Error creating snapshot: %v", err) | |
} | |
// 3. Find the latest snapshot for the given hostname | |
latestSnapshotIdentifier, err := FindLatestSnapshotByHostname(input.DBHostName) | |
if err != nil { | |
return fmt.Errorf("Error finding latest snapshot: %v", err) | |
} | |
// 4. Restore an RDS instance from that snapshot | |
err = RestoreRDSFromSnapshot(latestSnapshotIdentifier, input.NewRdsDBIdentifier) | |
if err != nil { | |
return fmt.Errorf("Error restoring from snapshot: %v", err) | |
} | |
log.Println("RDS instance successfully restored!") | |
return nil | |
} | |
func FindRDSInstanceIdentifierByHostname(hostname string) (string, error) { | |
sess := session.Must(session.NewSession()) | |
svc := rds.New(sess) | |
input := &rds.DescribeDBInstancesInput{} | |
result, err := svc.DescribeDBInstances(input) | |
if err != nil { | |
return "", err | |
} | |
for _, db := range result.DBInstances { | |
if *db.Endpoint.Address == hostname { | |
return *db.DBInstanceIdentifier, nil | |
} | |
} | |
return "", fmt.Errorf("RDS instance not found for hostname %s", hostname) | |
} | |
func main() { | |
input := CloneRDSInput{ | |
DBHostName: "mydbinstance.c6c8vnl3bgkz.us-west-2.rds.amazonaws.com", // Replace with your RDS hostname | |
NewRdsDBIdentifier: "new-db-instance-identifier", // Replace with your desired new DB identifier | |
} | |
if err := CloneRDS(input); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func RestoreRDSFromSnapshot(snapshotIdentifier string, newDBIdentifier string) error { | |
sess, err := session.NewSession(&aws.Config{ | |
Region: aws.String("us-west-2"), // Change to your region | |
}) | |
if err != nil { | |
return err | |
} | |
svc := rds.New(sess) | |
params := &rds.RestoreDBInstanceFromDBSnapshotInput{ | |
DBInstanceIdentifier: aws.String(newDBIdentifier), | |
DBSnapshotIdentifier: aws.String(snapshotIdentifier), | |
} | |
_, err = svc.RestoreDBInstanceFromDBSnapshot(params) | |
if err != nil { | |
return err | |
} | |
log.Printf("RDS instance %s restored successfully from snapshot %s.", newDBIdentifier, snapshotIdentifier) | |
return nil | |
} | |
type CreateSnapshotInput struct { | |
DBIdentifier string // Example: "mydbinstance" | |
SnapshotIdentifier string // Optional; if blank, it will use the DB identifier with date appended, e.g., "mydbinstance-20230606-123456" | |
} | |
func CreateRDSSnapshot(input CreateSnapshotInput) error { | |
sess := session.Must(session.NewSession()) | |
svc := rds.New(sess) | |
// Determine the snapshot identifier | |
snapshotIdentifier := input.SnapshotIdentifier | |
if snapshotIdentifier == "" { | |
snapshotIdentifier = input.DBIdentifier + "-" + time.Now().Format("20060102-150405") | |
} | |
// Creating the snapshot | |
createOutput, err := svc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ | |
DBInstanceIdentifier: aws.String(input.DBIdentifier), | |
DBSnapshotIdentifier: aws.String(snapshotIdentifier), | |
}) | |
if err != nil { | |
return fmt.Errorf("Error creating snapshot: %v", err) | |
} | |
// Polling for snapshot status | |
for { | |
snapshotOutput, err := svc.DescribeDBSnapshots(&rds.DescribeDBSnapshotsInput{ | |
DBSnapshotIdentifier: aws.String(snapshotIdentifier), | |
}) | |
if err != nil { | |
return fmt.Errorf("Error describing snapshot: %v", err) | |
} | |
status := aws.StringValue(snapshotOutput.DBSnapshots[0].Status) | |
if status == "available" { | |
break | |
} | |
if status != "creating" { | |
return fmt.Errorf("Snapshot status unexpected: %v", status) | |
} | |
fmt.Println("Waiting for snapshot to be created, this may take a while...") | |
time.Sleep(1 * time.Minute) | |
} | |
// Adding the "managed_by_cleanup" tag | |
_, err = svc.AddTagsToResource(&rds.AddTagsToResourceInput{ | |
ResourceName: createOutput.DBSnapshot.DBSnapshotArn, | |
Tags: []*rds.Tag{ | |
{ | |
Key: aws.String("managed_by_cleanup"), | |
Value: aws.String("true"), | |
}, | |
}, | |
}) | |
if err != nil { | |
return fmt.Errorf("Error adding managed_by_cleanup tag to snapshot: %v", err) | |
} | |
fmt.Println("Snapshot created successfully") | |
return nil | |
} | |
func FindLatestSnapshotByHostname(hostname string) (string, error) { | |
sess := session.Must(session.NewSession()) | |
svc := rds.New(sess) | |
input := &rds.DescribeDBSnapshotsInput{} | |
result, err := svc.DescribeDBSnapshots(input) | |
if err != nil { | |
return "", err | |
} | |
var latestSnapshot *rds.DBSnapshot | |
var latestTime time.Time | |
for _, snapshot := range result.DBSnapshots { | |
if aws.StringValue(snapshot.Status) != "available" { | |
continue | |
} | |
// Check if the snapshot identifier starts with the hostname | |
if !strings.HasPrefix(*snapshot.DBSnapshotIdentifier, hostname) { | |
continue | |
} | |
// Extract the date part from the snapshot identifier | |
datePart := strings.TrimPrefix(*snapshot.DBSnapshotIdentifier, hostname + "-") | |
snapshotTime, err := time.Parse("200601021504", datePart) | |
if err != nil { | |
continue | |
} | |
// Check if this snapshot is newer than the latest found so far | |
if snapshotTime.After(latestTime) { | |
latestTime = snapshotTime | |
latestSnapshot = snapshot | |
} | |
} | |
if latestSnapshot == nil { | |
return "", fmt.Errorf("No matching snapshots found for hostname %s", hostname) | |
} | |
return *latestSnapshot.DBSnapshotIdentifier, nil | |
} | |
func validateDBInstanceIdentifier(identifier string) error { | |
if len(identifier) < 1 || len(identifier) > 63 { | |
return fmt.Errorf("DB instance identifier must be between 1 and 63 characters") | |
} | |
if unicode.IsNumber(rune(identifier[0])) { | |
return fmt.Errorf("DB instance identifier must start with a letter") | |
} | |
if identifier[len(identifier)-1] == '-' || strings.Contains(identifier, "--") { | |
return fmt.Errorf("DB instance identifier cannot end with a hyphen or contain two consecutive hyphens") | |
} | |
return nil | |
} | |
func CleanOldSnapshots(dbIdentifier string) error { | |
sess := session.Must(session.NewSession()) | |
svc := rds.New(sess) | |
input := &rds.DescribeDBSnapshotsInput{ | |
DBInstanceIdentifier: aws.String(dbIdentifier), | |
} | |
result, err := svc.DescribeDBSnapshots(input) | |
if err != nil { | |
return err | |
} | |
newestSnapshots := make(map[string]*rds.DBSnapshot) | |
// First loop to identify the newest snapshot for each DB instance | |
for _, snapshot := range result.DBSnapshots { | |
if aws.StringValue(snapshot.Status) != "available" { | |
continue | |
} | |
if snapshotIsProtected(snapshot) { | |
continue | |
} | |
if !snapshotIsManagedByCleanup(snapshot) { | |
continue | |
} | |
currentNewestSnapshot := newestSnapshots[*snapshot.DBInstanceIdentifier] | |
if currentNewestSnapshot == nil || snapshot.SnapshotCreateTime.After(*currentNewestSnapshot.SnapshotCreateTime) { | |
newestSnapshots[*snapshot.DBInstanceIdentifier] = snapshot | |
} | |
} | |
// Second loop to delete snapshots that are not the newest | |
for _, snapshot := range result.DBSnapshots { | |
if aws.StringValue(snapshot.Status) != "available" { | |
continue | |
} | |
if snapshotIsProtected(snapshot) { | |
continue | |
} | |
if !snapshotIsManagedByCleanup(snapshot) { | |
continue | |
} | |
if snapshot.SnapshotCreateTime.After(time.Now().Add(-30*24*time.Hour)) { | |
continue | |
} | |
if newestSnapshots[*snapshot.DBInstanceIdentifier] != snapshot { | |
_, err := svc.DeleteDBSnapshot(&rds.DeleteDBSnapshotInput{ | |
DBSnapshotIdentifier: snapshot.DBSnapshotIdentifier, | |
}) | |
if err != nil { | |
fmt.Printf("Error deleting snapshot: %v\n", err) | |
} else { | |
fmt.Printf("Deleted snapshot: %s\n", *snapshot.DBSnapshotIdentifier) | |
} | |
} | |
} | |
return nil | |
} | |
func snapshotIsProtected(snapshot *rds.DBSnapshot) bool { | |
for _, tag := range snapshot.TagList { | |
if *tag.Key == "delete_protection" && *tag.Value == "true" { | |
return true | |
} | |
} | |
return false | |
} | |
func snapshotIsManagedByCleanup(snapshot *rds.DBSnapshot) bool { | |
for _, tag := range snapshot.TagList { | |
if *tag.Key == "managed_by_cleanup" && *tag.Value == "true" { | |
return true | |
} | |
} | |
return false | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment