Skip to content

Commit

Permalink
make use of environment config
Browse files Browse the repository at this point in the history
  • Loading branch information
kringkaste committed Mar 13, 2024
1 parent 59e3eff commit 5d0a901
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 37 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.direnv
/.direnv
/dist
/package.zip
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[MAIN]
max-line-length=120
max-line-length=120
max-args=7
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,13 @@ format:
test:
black -l 120 --check -t py311 .
pylint main.py


clean:
rm -rf dist
rm -f package.zip

build: clean test
mkdir dist
pip install --target ./dist -r requirements.txt
cd dist; zip -r ../package.zip .
zip package.zip main.py
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
# rds-backup-lambda
Small lambda function to copy automated RDS snapshots into another region

Small AWS Lambda function to copy automated RDS snapshots into another region.

## Installation

Create a AWS lambda function in your prefered region. Add the following environment configuration:


```
# AWS account ID
ACCOUNT = "1234567890"
# Region where the automated snapshots are located
SOURCE_REGION = "eu-west-1"
# Region where the automated snapshots should be copied to
TARGET_REGION = "eu-central-1"
# Comma separated list of RDS instances which snapshots should be copied
SOURCE_DB = "my-rds-instance"
# Comma-separated list of RDS clusters which snapshots should be copied
SOURCE_CLUSTER = "my-aurora-cluster"
# KMS Key in target region to use for snapshot encryption
DEST_KMS = "arn:aws:kms:eu-central-1:1234567890:key/abc123"
# Number of snapshots to keep
KEEP_SNAPSHOTS = 10
```

Create a `package.zip` for uploading as code:

```
make build
```
65 changes: 32 additions & 33 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,7 @@

import boto3
import botocore

# Region where the automated snapshots are located
SOURCE_REGION = "eu-west-1"
# List of RDS instances which snapshots should be copied
SOURCE_DB = ["my-rds-instance"]
# List of RDS clusters which snapshots should be copied
SOURCE_CLUSTER = ["my-aurora-cluster", "my-second-cluster"]
# KMS Key in target region to use for Aurora Cluster Snapshots
DEST_KMS = "arn:aws:kms:eu-central-1:ABC123:key/abc123"
# Number of snapshots to keep
KEEP = 10
from environs import Env


class BackupException(Exception):
Expand Down Expand Up @@ -51,7 +41,7 @@ def by_timestamp(snap):
return datetime.datetime.isoformat(datetime.datetime.now())


def copy_rds_snapshots(source_client, target_client, account, name):
def copy_rds_snapshots(source_client, target_client, source_region, account, name, kms, keep):
"""
Copies snapshots from an RDS instance
"""
Expand All @@ -64,32 +54,32 @@ def copy_rds_snapshots(source_client, target_client, account, name):
return

source_snap = sorted(source_snaps, key=by_snapshot_id, reverse=True)[0]["DBSnapshotIdentifier"]
source_snap_arn = f"arn:aws:rds:{SOURCE_REGION}:{account}:snapshot:{source_snap}"
source_snap_arn = f"arn:aws:rds:{source_region}:{account}:snapshot:{source_snap}"
target_snap_id = f"copy-of-{re.sub('rds:', '', source_snap)}"
print(f"Will copy {source_snap_arn} to {target_snap_id}")

try:
target_client.copy_db_snapshot(
SourceDBSnapshotIdentifier=source_snap_arn,
TargetDBSnapshotIdentifier=target_snap_id,
KmsKeyId=DEST_KMS,
KmsKeyId=kms,
CopyTags=True,
SourceRegion=SOURCE_REGION,
SourceRegion=source_region,
)
except botocore.exceptions.ClientError as ex:
raise BackupException(f"Could not issue copy command: {ex}") from ex

copied_snaps = target_client.describe_db_snapshots(SnapshotType="manual", DBInstanceIdentifier=name)["DBSnapshots"]
if len(copied_snaps) > KEEP:
for snap in sorted(copied_snaps, key=by_timestamp, reverse=True)[KEEP:]:
if len(copied_snaps) > keep:
for snap in sorted(copied_snaps, key=by_timestamp, reverse=True)[keep:]:
print(f"Will remove {snap['DBSnapshotIdentifier']}")
try:
target_client.delete_db_snapshot(DBSnapshotIdentifier=snap["DBSnapshotIdentifier"])
except botocore.exceptions.ClientError as ex:
raise BackupException(f"Could not delete snapshot {snap['DBSnapshotIdentifier']}: {ex}") from ex


def copy_cluster_snapshots(source_client, target_client, account, cluster_name):
def copy_cluster_snapshots(source_client, target_client, source_region, account, cluster_name, kms, keep):
"""
Coppies snapshots from a RDS cluster
"""
Expand All @@ -101,22 +91,22 @@ def copy_cluster_snapshots(source_client, target_client, account, cluster_name):
print("Found no automated snapshots. Nothing to do")
return
source_snap = sorted(source_snaps, key=by_cluster_snapshot_id, reverse=True)[0]["DBClusterSnapshotIdentifier"]
source_snap_arn = f"arn:aws:rds:{SOURCE_REGION}:{account}:cluster-snapshot:{source_snap}"
source_snap_arn = f"arn:aws:rds:{source_region}:{account}:cluster-snapshot:{source_snap}"
target_snap_id = f"copy-of-{re.sub('rds:', '', source_snap)}"
print(f"Will copy {source_snap_arn} to {target_snap_id}")

target_client.copy_db_cluster_snapshot(
SourceDBClusterSnapshotIdentifier=source_snap_arn,
TargetDBClusterSnapshotIdentifier=target_snap_id,
KmsKeyId=DEST_KMS,
SourceRegion=SOURCE_REGION,
KmsKeyId=kms,
SourceRegion=source_region,
)

copied_snaps = target_client.describe_db_cluster_snapshots(SnapshotType="manual", DBClusterIdentifier=cluster_name)[
"DBClusterSnapshots"
]
if len(copied_snaps) > KEEP:
for snap in sorted(copied_snaps, key=by_timestamp, reverse=True)[KEEP:]:
if len(copied_snaps) > keep:
for snap in sorted(copied_snaps, key=by_timestamp, reverse=True)[keep:]:
snap_id = snap["DBClusterSnapshotIdentifier"]
print(f"Will remove {snap_id}")
try:
Expand All @@ -125,18 +115,27 @@ def copy_cluster_snapshots(source_client, target_client, account, cluster_name):
raise BackupException(f"Could not delete snapshot {snap_id}: {ex}") from ex


def lambda_handler(event, context):
def lambda_handler(event, context): # pylint: disable=unused-argument
"""
Entrypoint for AWS Lambda
"""
if context:
print(f"Starting '{context.function_name}' version {context.function_version}")

target_region = event["region"]
account = event["account"]
source_client = boto3.client("rds", region_name=SOURCE_REGION)
# Get config from environment
env = Env()
source_region = env.str("SOURCE_REGION")
source_db = env.list("SOURCE_DB", [])
source_cluster = env.list("SOURCE_CLUSTER", [])
dest_kms = env.str("DEST_KMS", "")
keep_snapshots = env.int("KEEP_SNAPSHOTS", 1)
target_region = env.str("TARGET_REGION")
account = env.str("AWS_ACCOUNT")

# Create clients
source_client = boto3.client("rds", region_name=source_region)
target_client = boto3.client("rds", region_name=target_region)
for name in SOURCE_DB:
copy_rds_snapshots(source_client, target_client, account, name)
for name in SOURCE_CLUSTER:
copy_cluster_snapshots(source_client, target_client, account, name)

for name in source_db:
copy_rds_snapshots(source_client, target_client, source_region, account, name, dest_kms, keep_snapshots)

for name in source_cluster:
copy_cluster_snapshots(source_client, target_client, source_region, account, name, dest_kms, keep_snapshots)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
boto3
botocore
environs
3 changes: 3 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
pkgs.mkShell {
buildInputs = with pkgs; [
black
python311Packages.pip
python311Packages.pylint
python311Packages.botocore
python311Packages.boto3
python311Packages.environs
zip
];
}

0 comments on commit 5d0a901

Please sign in to comment.