%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/pillar/ |
Current File : //lib/python2.7/site-packages/salt/pillar/ec2_pillar.py |
# -*- coding: utf-8 -*- ''' Retrieve EC2 instance data for minions for ec2_tags and ec2_tags_list The minion id must be the AWS instance-id or value in ``tag_match_key``. For example set ``tag_match_key`` to ``Name`` to have the minion-id matched against the tag 'Name'. The tag contents must be unique. The value of ``tag_match_value`` can be 'uqdn' or 'asis'. if 'uqdn', then the domain will be stripped before comparison. Additionally, the ``use_grain`` option can be set to ``True``. This allows the use of an instance-id grain instead of the minion-id. Since this is a potential security risk, the configuration can be further expanded to include a list of minions that are trusted to only allow the alternate id of the instances to specific hosts. There is no glob matching at this time. .. note:: If you are using ``use_grain: True`` in the configuration for this external pillar module, the minion must have :conf_minion:`metadata_server_grains` enabled in the minion config file (see also :py:mod:`here <salt.grains.metadata>`). It is important to also note that enabling the ``use_grain`` option allows the minion to manipulate the pillar data returned, as described above. The optional ``tag_list_key`` indicates which keys should be added to ``ec2_tags_list`` and be split by ``tag_list_sep`` (by default ``;``). If a tag key is included in ``tag_list_key`` it is removed from ec2_tags. If a tag does not exist it is still included as an empty list. ..note:: As with any master configuration change, restart the salt-master daemon for changes to take effect. .. code-block:: yaml ext_pillar: - ec2_pillar: tag_match_key: 'Name' tag_match_value: 'asis' tag_list_key: - Role tag_list_sep: ';' use_grain: True minion_ids: - trusted-minion-1 - trusted-minion-2 - trusted-minion-3 This is a very simple pillar configuration that simply retrieves the instance data from AWS. Currently the only portion implemented are EC2 tags, which returns a list of key/value pairs for all of the EC2 tags assigned to the instance. ''' # Import python libs from __future__ import absolute_import, print_function, unicode_literals import re import logging import salt.ext.six as six from salt.ext.six.moves import range # Import salt libs from salt.utils.versions import StrictVersion as _StrictVersion # Import AWS Boto libs try: import boto.ec2 import boto.utils import boto.exception HAS_BOTO = True except ImportError: HAS_BOTO = False # Set up logging log = logging.getLogger(__name__) # DEBUG boto is far too verbose logging.getLogger('boto').setLevel(logging.WARNING) def __virtual__(): ''' Check for required version of boto and make this pillar available depending on outcome. ''' if not HAS_BOTO: return False boto_version = _StrictVersion(boto.__version__) required_boto_version = _StrictVersion('2.8.0') if boto_version < required_boto_version: log.error("%s: installed boto version %s < %s, can't retrieve instance data", __name__, boto_version, required_boto_version) return False return True def _get_instance_info(): ''' Helper function to return the instance ID and region of the master where this pillar is run. ''' identity = boto.utils.get_instance_identity()['document'] return (identity['instanceId'], identity['region']) def ext_pillar(minion_id, pillar, # pylint: disable=W0613 use_grain=False, minion_ids=None, tag_match_key=None, tag_match_value='asis', tag_list_key=None, tag_list_sep=';'): ''' Execute a command and read the output as YAML ''' valid_tag_match_value = ['uqdn', 'asis'] # meta-data:instance-id grain_instance_id = __grains__.get('meta-data', {}).get('instance-id', None) if not grain_instance_id: # dynamic:instance-identity:document:instanceId grain_instance_id = \ __grains__.get('dynamic', {}).get('instance-identity', {}).get('document', {}).get('instance-id', None) if grain_instance_id and re.search(r'^i-([0-9a-z]{17}|[0-9a-z]{8})$', grain_instance_id) is None: log.error('External pillar %s, instance-id \'%s\' is not valid for ' '\'%s\'', __name__, grain_instance_id, minion_id) grain_instance_id = None # invalid instance id found, remove it from use. # Check AWS Tag restrictions .i.e. letters, spaces, and numbers and + - = . _ : / @ if tag_match_key and re.match(r'[\w=.:/@-]+$', tag_match_key) is None: log.error('External pillar %s, tag_match_key \'%s\' is not valid ', __name__, tag_match_key if isinstance(tag_match_key, six.text_type) else 'non-string') return {} if tag_match_key and tag_match_value not in valid_tag_match_value: log.error('External pillar %s, tag_value \'%s\' is not valid must be one ' 'of %s', __name__, tag_match_value, ' '.join(valid_tag_match_value)) return {} if not tag_match_key: base_msg = ('External pillar %s, querying EC2 tags for minion id \'%s\' ' 'against instance-id', __name__, minion_id) else: base_msg = ('External pillar %s, querying EC2 tags for minion id \'%s\' ' 'against instance-id or \'%s\' against \'%s\'', __name__, minion_id, tag_match_key, tag_match_value) log.debug(base_msg) find_filter = None find_id = None if re.search(r'^i-([0-9a-z]{17}|[0-9a-z]{8})$', minion_id) is not None: find_filter = None find_id = minion_id elif tag_match_key: if tag_match_value == 'uqdn': find_filter = {'tag:{0}'.format(tag_match_key): minion_id.split('.', 1)[0]} else: find_filter = {'tag:{0}'.format(tag_match_key): minion_id} if grain_instance_id: # we have an untrusted grain_instance_id, use it to narrow the search # even more. Combination will be unique even if uqdn is set. find_filter.update({'instance-id': grain_instance_id}) # Add this if running state is not dependant on EC2Config # find_filter.update('instance-state-name': 'running') # no minion-id is instance-id and no suitable filter, try use_grain if enabled if not find_filter and not find_id and use_grain: if not grain_instance_id: log.debug('Minion-id is not in AWS instance-id formation, and there ' 'is no instance-id grain for minion %s', minion_id) return {} if minion_ids is not None and minion_id not in minion_ids: log.debug('Minion-id is not in AWS instance ID format, and minion_ids ' 'is set in the ec2_pillar configuration, but minion %s is ' 'not in the list of allowed minions %s', minion_id, minion_ids) return {} find_id = grain_instance_id if not (find_filter or find_id): log.debug('External pillar %s, querying EC2 tags for minion id \'%s\' against ' 'instance-id or \'%s\' against \'%s\' noughthing to match against', __name__, minion_id, tag_match_key, tag_match_value) return {} myself = boto.utils.get_instance_metadata(timeout=0.1, num_retries=1) if len(myself.keys()) < 1: log.info("%s: salt master not an EC2 instance, skipping", __name__) return {} # Get the Master's instance info, primarily the region (_, region) = _get_instance_info() try: conn = boto.ec2.connect_to_region(region) except boto.exception.AWSConnectionError as exc: log.error('%s: invalid AWS credentials, %s', __name__, exc) return {} if conn is None: log.error('%s: Could not connect to region %s', __name__, region) return {} try: if find_id: instance_data = conn.get_only_instances(instance_ids=[find_id], dry_run=False) else: # filters and max_results can not be used togther. instance_data = conn.get_only_instances(filters=find_filter, dry_run=False) except boto.exception.EC2ResponseError as exc: log.error('%s failed with \'%s\'', base_msg, exc) return {} if not instance_data: log.debug('%s no match using \'%s\'', base_msg, find_id if find_id else find_filter) return {} # Find a active instance, i.e. ignore terminated and stopped instances active_inst = [] for inst in range(0, len(instance_data)): if instance_data[inst].state not in ['terminated', 'stopped']: active_inst.append(inst) valid_inst = len(active_inst) if not valid_inst: log.debug('%s match found but not active \'%s\'', base_msg, find_id if find_id else find_filter) return {} if valid_inst > 1: log.error('%s multiple matches, ignored, using \'%s\'', base_msg, find_id if find_id else find_filter) return {} instance = instance_data[active_inst[0]] if instance.tags: ec2_tags = instance.tags ec2_tags_list = {} log.debug('External pillar %s, for minion id \'%s\', tags: %s', __name__, minion_id, instance.tags) if tag_list_key and isinstance(tag_list_key, list): for item in tag_list_key: if item in ec2_tags: ec2_tags_list[item] = ec2_tags[item].split(tag_list_sep) del ec2_tags[item] # make sure its only in ec2_tags_list else: ec2_tags_list[item] = [] # always return a result return {'ec2_tags': ec2_tags, 'ec2_tags_list': ec2_tags_list} return {}