-
Notifications
You must be signed in to change notification settings - Fork 9
/
handler.py
156 lines (115 loc) · 4.89 KB
/
handler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from datetime import datetime, timedelta
import json
import os
import re
# To get updated botocore data files
os.environ['AWS_DATA_PATH'] = '.'
import boto3
import botocore
from botocore.exceptions import ClientError
from botocore.vendored import requests
CLIENT = boto3.client('dynamodb')
SLACK_WEBHOOK = os.environ.get('SLACK_WEBHOOK')
REGION = os.environ.get('AWS_DEFAULT_REGION')
CONSOLE_ENDPOINT = 'https://console.aws.amazon.com/dynamodb/home?region={region}#backups:'.format(region=REGION)
def main(event, context):
tables = get_tables_to_backup()
results = {
"success": [],
"failure": []
}
for table in tables:
try:
create_backup(table)
results['success'].append(table)
except Exception as e:
print("Error creating backup for table {table}.\n. Error: {err}".format(table=table, err=str(e)))
results['failure'].append(table)
if os.environ.get('BACKUP_REMOVAL_ENABLED') == 'true':
try:
remove_stale_backups(tables)
except Exception as e:
print("Error removing stale backups. Error: {err}".format(err=str(e)))
message = format_message(results)
send_to_slack(message)
def create_backup(table):
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
backup_name = table + "_" + timestamp
CLIENT.create_backup(
TableName=table,
BackupName=backup_name
)
def remove_stale_backups(tables):
upper_bound = datetime.now() - timedelta(days=int(os.environ.get('BACKUP_RETENTION_DAYS')))
print("Removing backups before the following date: {date}".format(date=upper_bound))
if CLIENT.can_paginate('list_backups'):
paginator = CLIENT.get_paginator('list_backups')
for page in paginator.paginate(TimeRangeUpperBound=upper_bound):
for table in page['BackupSummaries']:
if table['TableName'] in tables:
CLIENT.delete_backup(BackupArn=table['BackupArn'])
else:
backup = CLIENT.list_backups(TimeRangeUpperBound=upper_bound)
for table in backup['BackupSummaries']:
if table['TableName'] in tables:
CLIENT.delete_backup(BackupArn=table['BackupArn'])
def format_message(results):
msg = ""
if not results['success'] and not results['failure']:
return "Tried running DynamoDB backup, but no tables were specified.\nPlease check your configuration."
msg += "Tried to backup {total} DynamoDB tables. {successes} succeeded, and {failures} failed. See all backups <{url}|here>.".format(
total=(len(results['success']) + len(results['failure'])),
successes=len(results['success']),
failures=len(results['failure']),
url=CONSOLE_ENDPOINT.format(region=REGION)
)
if results['success']:
msg += "\nThe following tables were successful:\n - "
msg += "\n - ".join(results['success'])
if results['failure']:
msg += "\nThe following tables failed:\n - "
msg += "\n - ".join(results['failure'])
return msg
def send_to_slack(message):
if not SLACK_WEBHOOK:
print('No SLACK_WEBHOOK provided. Not sending a message...')
return
data = {"text": message}
resp = requests.post(SLACK_WEBHOOK, json=data)
resp.raise_for_status()
def get_tables_to_backup():
"""Determines which tables to backup. The determination is made based on
the config options. Return value is a list.
Order is as follows:
1. If the TABLE_REGEX environment variable is set, call the `ListTables` API
for DynamoDB and return tables that match the given TABLE_REGEX;
2. If the TABLE_FILE environment variable is set, load the TABLE_FILE and
return the tables list.
3. If the TABLE_NAME environment variable is set, return the TABLE_NAME.
It will not combine multiple options. It will return the value(s) from the first
option with the environment variable present.
"""
if os.environ.get('TABLE_REGEX'):
return get_tables_regex(os.environ.get('TABLE_REGEX'))
elif os.environ.get('TABLE_FILE'):
return get_tables_from_file(os.environ.get('TABLE_FILE'))
elif os.environ.get('TABLE_NAME'):
return [os.environ.get('TABLE_NAME')]
print("No tables configured. Please use TABLE_REGEX, TABLE_FILE, OR TABLE_NAME environment variables.")
return []
def get_tables_regex(pattern):
print("Using regex pattern {} to find tables.".format(pattern))
tables = []
paginator = CLIENT.get_paginator('list_tables')
for page in paginator.paginate():
for table in page['TableNames']:
if re.match(pattern, table):
tables.append(table)
return tables
def get_tables_from_file(filename):
print("Using local file {} to find tables.".format(filename))
with open(filename, 'r') as f:
tables = json.load(f)
return tables
if __name__ == "__main__":
main('', '')