From 1233dba795b62766c0bbe3f42ea1cf16c152725b Mon Sep 17 00:00:00 2001 From: Craig Barr Date: Sun, 3 May 2020 11:09:42 +1000 Subject: [PATCH] support for _instance_tags_to_add --- README.rst | 25 +++++++++++++++++++- graffiti_monkey/cli.py | 32 +++++++++++++++++++++++--- graffiti_monkey/core.py | 51 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index b60d830..0195b54 100644 --- a/README.rst +++ b/README.rst @@ -39,6 +39,7 @@ Usage --snapshots snapshot(s) to tag --novolumes do not perform volume tagging --nosnapshots do not perform snapshot tagging + --noinstances do not perform instance tagging Examples -------- @@ -136,7 +137,7 @@ Graffiti-monkey itself can be configured using a yaml file _volume_tags_to_be_set: - key: 'NU_ROLE' value: 'ebs' - + _snapshot_tags_to_be_set: - key: 'NU_ROLE' value: 'ebs_snapshot' @@ -153,6 +154,24 @@ Graffiti-monkey itself can be configured using a yaml file # - 'snap-12ab3c45' # - 'snap-6de7f890' + # _instance_tags_to_add: + # An empty list will prevent additional tags being added to instances + # Example entries: + # - key: Name + # matches: + # # Tag owner + team when Name starts with dev + # - match: ^dev.* + # description: Development + # tags_to_set_if_match: + # owner: devmgr@mycompany.com + # team: dev + # # Tag unknown owner + team when no other match + # - match: .* + # description: No match + # tags_to_set_if_match: + # owner: unknown + # team: unknown + :code:`_instance_tags_to_propagate` is used to define the tags that are propagated from an instance to its volumes. :code:`_volume_tags_to_propagate` defines the tags that are propagated from a volume to its snapshots. @@ -165,6 +184,10 @@ by default. to tag all volumes. :code:`_snapshots_to_tag` is used to define the snapshots to be tagged. Leave empty to tag all snapshots. +:code:`_instance_tags_to_add` is used to define rules for assigning new tags based on existing tags. :code:`_instance_tags_to_add` defines the tags +that are queried for matches based on regular expressions. If a match is found, then :code:`tags_to_set_if_match` is used to define the tags to add or update. +Leave empty when instances do not require additional tags for existing ones. + If the configuration file is used, the _ entry headers must exist (those entries having no values or commented out values [as shown] is acceptable). diff --git a/graffiti_monkey/cli.py b/graffiti_monkey/cli.py index 2d51186..f769fdb 100644 --- a/graffiti_monkey/cli.py +++ b/graffiti_monkey/cli.py @@ -33,19 +33,23 @@ def __init__(self): self.profile = None self.monkey = None self.args = None - self.config = {"_instance_tags_to_propagate": ['Name'], - "_volume_tags_to_propagate": ['Name', 'instance_id', 'device'], + self.config = {"_instance_tags_to_propagate": [], + "_volume_tags_to_propagate": [], "_volume_tags_to_be_set": [], "_snapshot_tags_to_be_set": [], "_instance_filter": [], + "_instance_tags_to_add": [], } self.dryrun = False self.append = False self.volumes = None self.snapshots = None self.instancefilter = None + self.instancetags = None self.novolumes = False self.nosnapshots = False + self.noinstances = False + self.set_config_defaults() @staticmethod def _fail(message="Unknown failure", code=1): @@ -83,6 +87,8 @@ def set_cli_args(self): help='do not perform volume tagging') parser.add_argument('--nosnapshots', action='store_true', help='do not perform snapshot tagging') + parser.add_argument('--noinstances', action='store_true', + help='do not perform instance tagging') self.args = parser.parse_args(self.get_argv()) @staticmethod @@ -91,6 +97,14 @@ def fail_due_to_bad_config_file(self): "Make sure to use valid yaml syntax. " "Also the start of the file should not be marked with '---'.", 6) + def set_config_defaults(self): + if "_instance_tags_to_propagate" not in self.config.keys(): + self.config["_instance_tags_to_propagate"] = ['Name'] + if "_volume_tags_to_propagate" not in self.config.keys(): + self.config["_volume_tags_to_propagate"] = ['Name', 'instance_id', 'device'] + if "_instance_tags_to_add" not in self.config.keys(): + self.config["_instance_tags_to_add"] = [] + def set_config(self): if self.args.config: try: @@ -103,6 +117,7 @@ def set_config(self): try: #TODO: take default values and these can be overwritten by config self.config = yaml.load(self.args.config) + self.set_config_defaults() if self.config is None: self.fail_due_to_bad_config_file() except: @@ -158,12 +173,19 @@ def set_instancefilter(self): if "_instance_filter" in self.config.keys(): self.instancefilter = self.config["_instance_filter"] + def set_instancestags(self): + if "_instance_tags_to_add" in self.config.keys(): + self.instancetags = self.config["_instance_tags_to_add"] + def set_novolumes(self): self.novolumes = self.args.novolumes def set_nosnapshots(self): self.nosnapshots = self.args.nosnapshots + def set_noinstances(self): + self.noinstances = self.args.noinstances + def config_default(self, key): default_value = list() value = self.config.get(key) @@ -182,7 +204,9 @@ def initialize_monkey(self): self.snapshots, self.instancefilter, self.novolumes, - self.nosnapshots + self.nosnapshots, + self.noinstances, + self.instancetags ) def start_tags_propagation(self): @@ -208,6 +232,8 @@ def run(self): self.set_instancefilter() self.set_novolumes() self.set_nosnapshots() + self.set_noinstances() + self.set_instancestags() try: self.initialize_monkey() diff --git a/graffiti_monkey/core.py b/graffiti_monkey/core.py index 6d562f4..97752de 100644 --- a/graffiti_monkey/core.py +++ b/graffiti_monkey/core.py @@ -21,12 +21,14 @@ import time +import re + __all__ = ('GraffitiMonkey', 'Logging') log = logging.getLogger(__name__) class GraffitiMonkey(object): - def __init__(self, region, profile, instance_tags_to_propagate, volume_tags_to_propagate, volume_tags_to_be_set, snapshot_tags_to_be_set, dryrun, append, volumes_to_tag, snapshots_to_tag, instance_filter, novolumes, nosnapshots): + def __init__(self, region, profile, instance_tags_to_propagate, volume_tags_to_propagate, volume_tags_to_be_set, snapshot_tags_to_be_set, dryrun, append, volumes_to_tag, snapshots_to_tag, instance_filter, novolumes, nosnapshots, noinstances, instancetags): # This list of tags associated with an EC2 instance to propagate to # attached EBS volumes self._instance_tags_to_propagate = instance_tags_to_propagate @@ -68,8 +70,14 @@ def __init__(self, region, profile, instance_tags_to_propagate, volume_tags_to_p # If we process snapshots self._nosnapshots = nosnapshots + # If we process instances + self._noinstances = noinstances + + # Expand tags on the instance based on regular expressions + self._instance_tags_to_add = instancetags + log.info("Starting Graffiti Monkey") - log.info("Options: dryrun %s, append %s, novolumes %s, nosnapshots %s", self._dryrun, self._append, self._novolumes, self._nosnapshots) + log.info("Options: dryrun %s, append %s, novolumes %s, nosnapshots %s, noinstances %s", self._dryrun, self._append, self._novolumes, self._nosnapshots, self._noinstances) log.info("Connecting to region %s using profile %s", self._region, self._profile) try: self._conn = ec2.connect_to_region(self._region, profile_name=self._profile) @@ -96,6 +104,45 @@ def propagate_tags(self): if not self._nosnapshots: self.tag_snapshots(volumes) + if not self._noinstances: + self.tag_instances() + + def log_tag_action(self, tags): + s = 'added/updated keys\n' + if self._dryrun: + s = 'DRYRUN: Would have added/updated keys\n' + for key in tags.keys(): + s = s + ' ' + key + '=' + tags[key]+'\n' + log.info(s) + + def look_for_tag_match(self, instance, key, value, tag_config): + for m in tag_config['matches']: + if re.search(m['match'], value): + description = '' + if 'description' in m: + description = " - '%s'" % (m['description']) + log.info("[%s]%s: found a match for %s=%s with %s" % (instance.id, description, key, value, m['match'])) + self.log_tag_action(m['tags_to_set_if_match']) + if not self._dryrun: + instance.add_tags(m['tags_to_set_if_match']) + if not 'continue' in m: + return + log.warn("[%s] no match found for %s=%s despite matches defined in config" % (instance. id, key, value)) + + def tag_instance(self, instance, tags): + for tag_config in self._instance_tags_to_add: + for key in tags.keys(): + value = tags[key] + if tag_config['key'] == key: + self.look_for_tag_match(instance, key, value, tag_config) + + def tag_instances(self): + if len(self._instance_tags_to_add) > 0: + log.info('Using instance list from cli/config file') + for instance in self._conn.get_only_instances(filters=self._instance_filter): + self.tag_instance(instance, instance.tags) + log.info('Completed processing all instances') + def tag_volumes(self): ''' Gets a list of volumes, and then loops through them tagging them '''