From ea0e810c4b2801b28e7a17e00ec10acac57c9519 Mon Sep 17 00:00:00 2001 From: Adam Pippin Date: Sun, 29 Dec 2019 11:10:34 -0800 Subject: [PATCH] lambda code --- clone_snapshots_locally.py | 166 +++++++++++++++++++++++++++++++++++++ share_snapshots.py | 36 ++++++++ 2 files changed, 202 insertions(+) create mode 100644 clone_snapshots_locally.py create mode 100644 share_snapshots.py diff --git a/clone_snapshots_locally.py b/clone_snapshots_locally.py new file mode 100644 index 0000000..a782593 --- /dev/null +++ b/clone_snapshots_locally.py @@ -0,0 +1,166 @@ +import json +import boto3 +import time + +# The account id sharing snapshots with you +SOURCE_ACCOUNT = "987698769876" +# The account id this is running in +TARGET_ACCOUNT = "123412341234" +# Mapping of KMS keys -- keys are the key arn in the source account, values +# are the key arn in this account. +KMS_KEYS = { + "arn:aws:kms:ca-central-1:987698769876:key/abcd-abcd-abcd": "arn:aws:kms:ca-central-1:123412341234:key/qrst-qrst-qrst" +} +# Configure retention period for deleted snapshots. If the string is present +# in the snapshot description, then the retention time will be the number of +# seconds given in the value. If multiple strings match, which one is chosen +# is undefined. +RETENTION = { + "hour_snapshot": 0, + "day_snapshot": 86400, + "week_snapshot": 259200, + "month_snapshot": 259200, + "year_snapshot": 259200 +} +# Name of the tag on the snapshots to store the source snapshot id in. +SOURCE_TAG = "SourceSnapshotId" +# Name of the tag on the snapshots to store the retention period in. +RETENTION_TAG = "RetentionPeriod" +# Region the source snapshots are in. +REGION = "ca-central-1" + +print("START") +ec2 = boto3.client('ec2') + +def lambda_handler(event, context): + print("Fetching snapshots for source account " + SOURCE_ACCOUNT) + source_snapshots = fetch_snapshots(SOURCE_ACCOUNT) + print("Fetching snapshots for target account: " + TARGET_ACCOUNT) + target_snapshots = fetch_snapshots(TARGET_ACCOUNT) + source_snapshot_index = {} + target_snapshot_index = {} + target_snapshots_mapped = {} + target_snapshots_orphaned = {} + + results = [] + + for snapshot in source_snapshots: + source_snapshot_index[snapshot['SnapshotId']] = snapshot + + for snapshot in target_snapshots: + target_snapshot_index[snapshot['SnapshotId']] = snapshot + + for tag in snapshot['Tags']: + if tag['Key'] == SOURCE_TAG: + source_snapshot_id = tag['Value'] + if source_snapshot_id in source_snapshot_index.keys(): + target_snapshots_mapped[source_snapshot_id] = snapshot + else: + target_snapshots_orphaned[snapshot['SnapshotId']] = snapshot + + for source_snapshot_id, source_snapshot in source_snapshot_index.items(): + if source_snapshot_id in target_snapshots_mapped.keys(): + results.append(handle_snapshot(source_snapshot, target_snapshots_mapped[source_snapshot_id])) + else: + results.append(handle_snapshot(source_snapshot, None)) + + for target_snapshot_id in target_snapshots_orphaned: + results.append(handle_snapshot(None, target_snapshots_orphaned[target_snapshot_id])) + + results = list(filter(None, results)) + + return { + 'statusCode': 200, + 'body': json.dumps(results) + } + +def fetch_snapshots(account_id): + return ec2.describe_snapshots( + Filters = [ + { + 'Name': 'owner-id', + 'Values': [ account_id ] + } + ] + )['Snapshots'] + +def handle_snapshot(source_snapshot, target_snapshot): + if source_snapshot is not None and target_snapshot is not None: + # Source and target exist, nothing to do! + pass + elif source_snapshot is not None and target_snapshot is None: + # Source exists, no target. Copy! + print(source_snapshot['SnapshotId'] + " copying") + + # Quick guard -- make sure the snapshot is complete first. + if source_snapshot['Progress'] != '100%': + return [source_snapshot['SnapshotId'], None, "Source snapshot incomplete. Doing nothing."] + + copy_response = ec2.copy_snapshot( + Description = '/'.join([ + source_snapshot['OwnerId'], + source_snapshot['VolumeId'], + str(source_snapshot['StartTime']), + source_snapshot['Description'] + ]), + Encrypted = True, + KmsKeyId = KMS_KEYS[source_snapshot['KmsKeyId']], + SourceRegion = REGION, + SourceSnapshotId = source_snapshot['SnapshotId'], + TagSpecifications = [ + { + 'ResourceType': 'snapshot', + 'Tags': [ + { + "Key": SOURCE_TAG, + "Value": source_snapshot['SnapshotId'] + } + ] + } + ] + ) + + return [source_snapshot['SnapshotId'], copy_response['SnapshotId'], "Copied snapshot"] + + elif source_snapshot is None and target_snapshot is not None: + # Check if there's already a retention tag + + retention = None + + for tag in target_snapshot['Tags']: + if tag['Key'] == RETENTION_TAG: + retention = float(tag['Value']) + break + + if retention is None: + # Mark target with retention tag + print(target_snapshot['SnapshotId'] + ": marked for removal") + + retention = time.time() + description = target_snapshot['Description'] + for search_str, retention_seconds in RETENTION.items(): + if search_str in description: + retention = retention + retention_seconds + break + + ec2.create_tags( + Resources=[ + target_snapshot['SnapshotId'] + ], + Tags=[ + { + 'Key': RETENTION_TAG, + 'Value':str(retention) + } + ] + ) + + return [None, target_snapshot['SnapshotId'], "Source removed. Marking for deletion."] + + elif retention < time.time(): + print(target_snapshot['SnapshotId'] + ": retention time passed, removing") + ec2.delete_snapshot( + SnapshotId = target_snapshot['SnapshotId'] + ) + return [None, target_snapshot['SnapshotId'], "Retention period passed. Deleted."] + diff --git a/share_snapshots.py b/share_snapshots.py new file mode 100644 index 0000000..856d238 --- /dev/null +++ b/share_snapshots.py @@ -0,0 +1,36 @@ +import json +import boto3 + +VOLUME_ID = 'vol-123412341234' +TARGET_ACCOUNT = "123412341234" + +print("START") + +def lambda_handler(event, context): + ec2 = boto3.resource('ec2') + print("Fetching snapshots for volume " + VOLUME_ID) + volume = ec2.Volume(id=VOLUME_ID) + snapshots = volume.snapshots.all() + snapshot_ids = [] + + for snapshot in snapshots: + shared = snapshot.describe_attribute(Attribute='createVolumePermission') + already_shared = False + + for i in range(0, len(shared['CreateVolumePermissions'])): + if 'UserId' in shared['CreateVolumePermissions'][i] and shared['CreateVolumePermissions'][i]['UserId'] == TARGET_ACCOUNT: + already_shared = True + break + + if not already_shared: + print("Sharing " + snapshot.id) + snapshot_ids.append(snapshot.id) + snapshot.modify_attribute( + Attribute='createVolumePermission', + OperationType='add', + UserIds=[TARGET_ACCOUNT]) + + return { + 'statusCode': 200, + 'body': json.dumps(snapshot_ids) + }