diff options
author | Mike Crute <mike@crute.us> | 2017-12-25 04:53:29 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2017-12-25 04:53:29 +0000 |
commit | 24cac6b1a44f67e721f48fa5d76d096c7fd77d1e (patch) | |
tree | d75ee41d7e333feea1553505fa00ab46767c8a32 | |
parent | 638be8d8b674acfb56bf59d586859a4622b9ad22 (diff) | |
download | alpine-ec2-ami-24cac6b1a44f67e721f48fa5d76d096c7fd77d1e.tar.bz2 alpine-ec2-ami-24cac6b1a44f67e721f48fa5d76d096c7fd77d1e.tar.xz alpine-ec2-ami-24cac6b1a44f67e721f48fa5d76d096c7fd77d1e.zip |
Add AMI scrub tool
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | scrub-old-amis.py.in | 67 |
3 files changed, 74 insertions, 2 deletions
@@ -1,3 +1,4 @@ | |||
1 | /build/ | 1 | /build/ |
2 | /.py3/ | 2 | /.py3/ |
3 | /variables.json | 3 | /variables.json |
4 | /scrub-old-amis.py | ||
@@ -5,7 +5,7 @@ ami: build/convert | |||
5 | 5 | ||
6 | build/convert: | 6 | build/convert: |
7 | [ -d ".py3" ] || python3 -m venv .py3 | 7 | [ -d ".py3" ] || python3 -m venv .py3 |
8 | .py3/bin/pip install pyyaml | 8 | .py3/bin/pip install pyyaml boto3 |
9 | 9 | ||
10 | [ -d "build" ] || mkdir build | 10 | [ -d "build" ] || mkdir build |
11 | 11 | ||
@@ -17,6 +17,10 @@ build/convert: | |||
17 | @echo "json.dump(yaml.load(open(sys.argv[1])), sys.stdout, indent=4, separators=(',', ': '))" >> build/convert | 17 | @echo "json.dump(yaml.load(open(sys.argv[1])), sys.stdout, indent=4, separators=(',', ': '))" >> build/convert |
18 | @chmod +x build/convert | 18 | @chmod +x build/convert |
19 | 19 | ||
20 | scrub-old-amis.py: scrub-old-amis.py.in | ||
21 | sed "s|@PYTHON@|#!`pwd`/.py3/bin/python|" $< > $@ | ||
22 | chmod +x $@ | ||
23 | |||
20 | .PHONY: clean | 24 | .PHONY: clean |
21 | clean: | 25 | clean: |
22 | rm -rf build .py3 | 26 | rm -rf build .py3 scrub-old-amis.py |
diff --git a/scrub-old-amis.py.in b/scrub-old-amis.py.in new file mode 100644 index 0000000..756f537 --- /dev/null +++ b/scrub-old-amis.py.in | |||
@@ -0,0 +1,67 @@ | |||
1 | @PYTHON@ | ||
2 | |||
3 | import re | ||
4 | import yaml | ||
5 | import boto3 | ||
6 | |||
7 | |||
8 | # All Alpine AMIs should match this regex if they're valid | ||
9 | AMI_RE = re.compile("^Alpine-(\d+\.\d+)(?:-r(\d+))?-Hardened-EC2") | ||
10 | |||
11 | |||
12 | # Load current AMI version from config | ||
13 | with open("alpine-ami.yaml") as fp: | ||
14 | ami_cfg = yaml.load(fp)["variables"] | ||
15 | current = (float(ami_cfg["alpine_release"]), int(ami_cfg["ami_release"])) | ||
16 | |||
17 | |||
18 | # Fetch all matching AMIs | ||
19 | amis = [] | ||
20 | |||
21 | for region in boto3.session.Session().get_available_regions("ec2"): | ||
22 | ec2 = boto3.client("ec2", region_name=region) | ||
23 | |||
24 | for image in ec2.describe_images(Owners=["self"])["Images"]: | ||
25 | match = AMI_RE.match(image["Name"]) | ||
26 | if not match: | ||
27 | continue | ||
28 | |||
29 | os_rel, ami_rel = match.groups() | ||
30 | amis.append(( | ||
31 | region, image["ImageId"], float(os_rel), | ||
32 | int(ami_rel) if ami_rel else 0)) | ||
33 | |||
34 | |||
35 | # Determine the set to discard based region and version | ||
36 | ok_regions = set() | ||
37 | discards = [] | ||
38 | |||
39 | # Cluster candidates by region/version pair, newest in a region first. | ||
40 | # This should result in the first match for a region always being the newest | ||
41 | # AMI for that region and all subsequent matches in the region being old. | ||
42 | # Even so we must keep track of regions with current images on the off-chance | ||
43 | # that a region only has old images. In that case we want to preserve the old | ||
44 | # images till we can publish new ones manually so users can still launch | ||
45 | # Alpine systems without interruption. | ||
46 | candidates = sorted(amis, key=lambda i: (i[0], (i[1], i[2])), reverse=True) | ||
47 | |||
48 | for ami in candidates: | ||
49 | (region, ami), version = ami[:2], ami[2:] | ||
50 | |||
51 | if version > current: | ||
52 | print("{} has AMI '{}' newer than current".format(region, ami)) | ||
53 | continue | ||
54 | elif version == current: | ||
55 | ok_regions.add(region) | ||
56 | continue | ||
57 | elif version < current and region in ok_regions: | ||
58 | discards.append((region, ami)) | ||
59 | else: | ||
60 | print("Not discarding old image in {}".format(region)) | ||
61 | continue | ||
62 | |||
63 | |||
64 | # Scrub the old ones | ||
65 | for region, image in discards: | ||
66 | print("Removing image '{}' in {}".format(image, region)) | ||
67 | boto3.client("ec2", region_name=region).deregister_image(ImageId=image) | ||