Skip to content

Instantly share code, notes, and snippets.

@niski84
Last active August 9, 2023 08:33
Show Gist options
  • Save niski84/d83b5f24f5199ee9ad6f3515d1706eaa to your computer and use it in GitHub Desktop.
Save niski84/d83b5f24f5199ee9ad6f3515d1706eaa to your computer and use it in GitHub Desktop.
RDS Cloning
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