Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------
Expand Down Expand Up @@ -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'
Expand All @@ -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.
Expand All @@ -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).

Expand Down
32 changes: 29 additions & 3 deletions graffiti_monkey/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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()
Expand Down
51 changes: 49 additions & 2 deletions graffiti_monkey/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 '''
Expand Down