From 22e0c9449b5552acf95ea17967b13bcfca84095b Mon Sep 17 00:00:00 2001 From: Chris Church Date: Fri, 3 Apr 2015 00:30:58 -0400 Subject: [PATCH] Update EC2/GCE inventory scripts from core. --- awx/plugins/inventory/ec2.py | 88 +++++++++++++++++++++--------------- awx/plugins/inventory/gce.py | 26 ++++++----- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/awx/plugins/inventory/ec2.py b/awx/plugins/inventory/ec2.py index 0f7c198575..e93df1053d 100755 --- a/awx/plugins/inventory/ec2.py +++ b/awx/plugins/inventory/ec2.py @@ -334,23 +334,24 @@ class Ec2Inventory(object): self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) + def connect(self, region): + ''' create connection to api server''' + if self.eucalyptus: + conn = boto.connect_euca(host=self.eucalyptus_host) + conn.APIVersion = '2010-08-31' + else: + conn = ec2.connect_to_region(region) + # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported + if conn is None: + self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region) + return conn def get_instances_by_region(self, region): ''' Makes an AWS EC2 API call to the list of instances in a particular region ''' try: - if self.eucalyptus: - conn = boto.connect_euca(host=self.eucalyptus_host) - conn.APIVersion = '2010-08-31' - else: - conn = ec2.connect_to_region(region) - - # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported - if conn is None: - print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) - sys.exit(1) - + conn = self.connect(region) reservations = [] if self.ec2_instance_filters: for filter_key, filter_values in self.ec2_instance_filters.iteritems(): @@ -363,10 +364,12 @@ class Ec2Inventory(object): self.add_instance(instance, region) except boto.exception.BotoServerError, e: - if not self.eucalyptus: - print "Looks like AWS is down again:" - print e - sys.exit(1) + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + else: + backend = 'Eucalyptus' if self.eucalyptus else 'AWS' + error = "Error connecting to %s backend.\n%s" % (backend, e.message) + self.fail_with_error(error) def get_rds_instances_by_region(self, region): ''' Makes an AWS API call to the list of RDS instances in a particular @@ -379,23 +382,38 @@ class Ec2Inventory(object): for instance in instances: self.add_rds_instance(instance, region) except boto.exception.BotoServerError, e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() if not e.reason == "Forbidden": - print "Looks like AWS RDS is down: " - print e - sys.exit(1) + error = "Looks like AWS RDS is down:\n%s" % e.message + self.fail_with_error(error) + + def get_auth_error_message(self): + ''' create an informative error message if there is an issue authenticating''' + errors = ["Authentication error retrieving ec2 inventory."] + if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: + errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found') + else: + errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct') + + boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] + boto_config_found = list(p for p in boto_paths if os.path.isfile(os.path.expanduser(p))) + if len(boto_config_found) > 0: + errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found)) + else: + errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) + + return '\n'.join(errors) + + def fail_with_error(self, err_msg): + '''log an error to std err for ansible-playbook to consume and exit''' + sys.stderr.write(err_msg) + sys.exit(1) def get_instance(self, region, instance_id): - ''' Gets details about a specific instance ''' - if self.eucalyptus: - conn = boto.connect_euca(self.eucalyptus_host) - conn.APIVersion = '2010-08-31' - else: - conn = ec2.connect_to_region(region) - - # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported - if conn is None: - print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) - sys.exit(1) + conn = self.connect(region) reservations = conn.get_all_instances([instance_id]) for reservation in reservations: @@ -492,9 +510,8 @@ class Ec2Inventory(object): if self.nested_groups: self.push_group(self.inventory, 'security_groups', key) except AttributeError: - print 'Package boto seems a bit older.' - print 'Please upgrade boto >= 2.3.0.' - sys.exit(1) + self.fail_with_error('\n'.join(['Package boto seems a bit older.', + 'Please upgrade boto >= 2.3.0.'])) # Inventory: Group by tag keys if self.group_by_tag_keys: @@ -587,9 +604,9 @@ class Ec2Inventory(object): self.push_group(self.inventory, 'security_groups', key) except AttributeError: - print 'Package boto seems a bit older.' - print 'Please upgrade boto >= 2.3.0.' - sys.exit(1) + self.fail_with_error('\n'.join(['Package boto seems a bit older.', + 'Please upgrade boto >= 2.3.0.'])) + # Inventory: Group by engine if self.group_by_rds_engine: @@ -785,4 +802,3 @@ class Ec2Inventory(object): # Run the script Ec2Inventory() - diff --git a/awx/plugins/inventory/gce.py b/awx/plugins/inventory/gce.py index 2da449fb20..e77178c16b 100755 --- a/awx/plugins/inventory/gce.py +++ b/awx/plugins/inventory/gce.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright 2013 Google Inc. # # This file is part of Ansible @@ -72,14 +72,6 @@ Author: Eric Johnson Version: 0.0.1 ''' -# We need to use pycrypto >= 2.6 -# These lines are necessary because some of the Ansible OS packages install -# pycrypto 2.0, and it's actually possible through OS packaging to have 2.0 and -# 2.6 installed alongside one another, and 2.0 can then win on precedence -# order. This gets around that. -__requires__ = ['pycrypto>=2.6'] -import pkg_resources - USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin" USER_AGENT_VERSION="v1" @@ -111,11 +103,13 @@ class GceInventory(object): # Just display data for specific host if self.args.host: print self.json_format_dict(self.node_to_dict( - self.get_instance(self.args.host))) + self.get_instance(self.args.host)), + pretty=self.args.pretty) sys.exit(0) # Otherwise, assume user wants all instances grouped - print(self.json_format_dict(self.group_instances())) + print(self.json_format_dict(self.group_instances(), + pretty=self.args.pretty)) sys.exit(0) def get_gce_driver(self): @@ -195,6 +189,8 @@ class GceInventory(object): help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all information about an instance') + parser.add_argument('--pretty', action='store_true', default=False, + help='Pretty format (default: False)') self.args = parser.parse_args() @@ -237,9 +233,14 @@ class GceInventory(object): def group_instances(self): '''Group all instances''' groups = {} + meta = {} + meta["hostvars"] = {} + for node in self.driver.list_nodes(): name = node.name + meta["hostvars"][name] = self.node_to_dict(node) + zone = node.extra['zone'].name if groups.has_key(zone): groups[zone].append(name) else: groups[zone] = [name] @@ -267,6 +268,9 @@ class GceInventory(object): stat = 'status_%s' % status.lower() if groups.has_key(stat): groups[stat].append(name) else: groups[stat] = [name] + + groups["_meta"] = meta + return groups def json_format_dict(self, data, pretty=False):