diff --git a/amicleaner/cli.py b/amicleaner/cli.py index 0a20acf..e85797a 100755 --- a/amicleaner/cli.py +++ b/amicleaner/cli.py @@ -28,6 +28,7 @@ def __init__(self, args): self.from_ids = args.from_ids self.full_report = args.full_report self.force_delete = args.force_delete + self.ami_min_days = args.ami_min_days self.mapping_strategy = { "key": self.mapping_key, @@ -86,7 +87,7 @@ def prepare_candidates(self, candidates_amis=None): if not group_name: report["no-tags (excluded)"] = amis else: - reduced = c.reduce_candidates(amis, self.keep_previous) + reduced = c.reduce_candidates(amis, self.keep_previous, self.ami_min_days) if reduced: report[group_name] = reduced candidates.extend(reduced) @@ -143,6 +144,7 @@ def print_defaults(self): print(TERM.green("mapping_values : {0}".format(self.mapping_values))) print(TERM.green("excluded_mapping_values : {0}".format(self.excluded_mapping_values))) print(TERM.green("keep_previous : {0}".format(self.keep_previous))) + print(TERM.green("ami_min_days : {0}".format(self.ami_min_days))) @staticmethod def print_version(): diff --git a/amicleaner/core.py b/amicleaner/core.py index 792d57c..7949d3c 100644 --- a/amicleaner/core.py +++ b/amicleaner/core.py @@ -7,6 +7,7 @@ import boto3 from botocore.exceptions import ClientError from .resources.models import AMI +from datetime import datetime class OrphanSnapshotCleaner(object): @@ -232,7 +233,7 @@ def tags_values_to_string(tags, filters=None): return ".".join(sorted(tag_values)) - def reduce_candidates(self, mapped_candidates_ami, keep_previous=0): + def reduce_candidates(self, mapped_candidates_ami, keep_previous=0, ami_min_days=-1): """ Given a array of AMIs to clean this function return a subsequent @@ -240,6 +241,19 @@ def reduce_candidates(self, mapped_candidates_ami, keep_previous=0): time and rotation_strategy param """ + result_amis = [] + result_amis.extend(mapped_candidates_ami) + + if ami_min_days > 0: + for ami in mapped_candidates_ami: + f_date = datetime.strptime(ami.creation_date, '%Y-%m-%dT%H:%M:%S.%fZ') + present = datetime.now() + delta = present - f_date + if delta.days < ami_min_days: + result_amis.remove(ami) + + mapped_candidates_ami = result_amis + if not keep_previous: return mapped_candidates_ami diff --git a/amicleaner/resources/config.py b/amicleaner/resources/config.py index cb829a8..72da975 100644 --- a/amicleaner/resources/config.py +++ b/amicleaner/resources/config.py @@ -27,3 +27,8 @@ EXCLUDED_MAPPING_VALUES = [] + + +# Number of days amis to keep based on creation date and grouping strategy +# not including the ami currently running by an ec2 instance +AMI_MIN_DAYS = -1 diff --git a/amicleaner/utils.py b/amicleaner/utils.py index 7c5d3bf..6c85196 100644 --- a/amicleaner/utils.py +++ b/amicleaner/utils.py @@ -8,7 +8,7 @@ from prettytable import PrettyTable -from .resources.config import KEEP_PREVIOUS +from .resources.config import KEEP_PREVIOUS, AMI_MIN_DAYS class Printer(object): @@ -112,6 +112,13 @@ def parse_args(args): action="store_true", help="Check and clean orphaned snapshots") + parser.add_argument("--ami-min-days", + dest='ami_min_days', + type=int, + default=AMI_MIN_DAYS, + help="Number of days AMI to keep excluding those " + "currently being running") + parsed_args = parser.parse_args(args) if parsed_args.mapping_key and not parsed_args.mapping_values: print("missing mapping-values\n") diff --git a/tests/test_cli.py b/tests/test_cli.py index 3cd11ae..7f3e6fe 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -66,6 +66,57 @@ def test_deletion(): assert len(f.fetch_available_amis()) == 0 +@mock_ec2 +@mock_autoscaling +def test_deletion_ami_min_days(): + """ Test deletion methods """ + + # creating tests objects + first_ami = AMI() + first_ami.name = "test-ami" + first_ami.id = 'ami-28c2b348' + first_ami.creation_date = "2017-11-04T01:35:31.000Z" + + second_ami = AMI() + second_ami.name = "test-ami" + second_ami.id = 'ami-28c2b349' + second_ami.creation_date = "2017-11-04T01:35:31.000Z" + + # constructing dicts + amis_dict = dict() + amis_dict[first_ami.id] = first_ami + amis_dict[second_ami.id] = second_ami + + parser = parse_args( + [ + '--keep-previous', '0', + '--ami-min-days', '1', + '--mapping-key', 'name', + '--mapping-values', 'test-ami'] + ) + + app = App(parser) + # testing filter + candidates = app.fetch_candidates(amis_dict) + + candidates_tobedeleted = app.prepare_candidates(candidates) + assert len(candidates) == 2 + assert len(candidates_tobedeleted) == 2 + + parser = parse_args( + [ + '--keep-previous', '0', + '--ami-min-days', '10000', + '--mapping-key', 'name', + '--mapping-values', 'test-ami'] + ) + + app = App(parser) + candidates_tobedeleted2 = app.prepare_candidates(candidates) + assert len(candidates) == 2 + assert len(candidates_tobedeleted2) == 0 + + def test_fetch_candidates(): # creating tests objects first_ami = AMI() @@ -114,6 +165,7 @@ def test_parse_args_no_args(): assert parser.mapping_key is None assert parser.mapping_values is None assert parser.keep_previous is 4 + assert parser.ami_min_days is -1 def test_parse_args(): @@ -129,6 +181,10 @@ def test_parse_args(): assert parser.mapping_key == "tags" assert len(parser.mapping_values) == 2 + parser = parse_args(['--ami-min-days', '10', '--full-report']) + assert parser.ami_min_days == 10 + assert parser.full_report is True + def test_print_report(): assert Printer.print_report({}) is None