diff --git a/plugins/inventory/rax.py b/plugins/inventory/rax.py index 6836db61f66..039233005d7 100755 --- a/plugins/inventory/rax.py +++ b/plugins/inventory/rax.py @@ -22,9 +22,11 @@ DOCUMENTATION = ''' inventory: rax short_description: Rackspace Public Cloud external inventory script description: - - Generates inventory that Ansible can understand by making API request to Rackspace Public Cloud API + - Generates inventory that Ansible can understand by making API request to + Rackspace Public Cloud API - | - When run against a specific host, this script returns the following variables: + When run against a specific host, this script returns the following + variables: rax_os-ext-sts_task_state rax_addresses rax_links @@ -65,12 +67,23 @@ options: authors: - Jesse Keating - Paul Durivage + - Matt Martz notes: - - RAX_CREDS_FILE is an optional environment variable that points to a pyrax-compatible credentials file. - - If RAX_CREDS_FILE is not supplied, rax.py will look for a credentials file at ~/.rackspace_cloud_credentials. + - RAX_CREDS_FILE is an optional environment variable that points to a + pyrax-compatible credentials file. + - If RAX_CREDS_FILE is not supplied, rax.py will look for a credentials file + at ~/.rackspace_cloud_credentials. - See https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating - - RAX_REGION is an optional environment variable to narrow inventory search scope - - RAX_REGION, if used, needs a value like ORD, DFW, SYD (a Rackspace datacenter) and optionally accepts a comma-separated list + - RAX_REGION is an optional environment variable to narrow inventory search + scope + - RAX_REGION, if used, needs a value like ORD, DFW, SYD (a Rackspace + datacenter) and optionally accepts a comma-separated list + - RAX_ENV is an environment variable that will use an environment as + configured in ~/.pyrax.cfg, see + https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration + - RAX_META_PREFIX is an environment variable that changes the prefix used + for meta key/value groups. For compatibility with ec2.py set to + RAX_META_PREFIX=tag requirements: [ "pyrax" ] examples: - description: List server instances @@ -83,13 +96,14 @@ examples: code: RAX_CREDS_FILE=~/.raxpub rax.py --host server.example.com ''' -import sys -import re import os - +import re +import sys import argparse import collections +from types import NoneType + try: import json except: @@ -98,9 +112,26 @@ except: try: import pyrax except ImportError: - print('pyrax required for this module') + print('pyrax is required for this module') sys.exit(1) +NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) + + +def rax_slugify(value): + return 'rax_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_')) + + +def to_dict(obj): + instance = {} + for key in dir(obj): + value = getattr(obj, key) + if (isinstance(value, NON_CALLABLES) and not key.startswith('_')): + key = rax_slugify(key) + instance[key] = value + + return instance + def host(regions, hostname): hostvars = {} @@ -110,15 +141,7 @@ def host(regions, hostname): cs = pyrax.connect_to_cloudservers(region=region) for server in cs.servers.list(): if server.name == hostname: - keys = [key for key in vars(server) if key not in ('manager', '_info')] - for key in keys: - # Extract value - value = getattr(server, key) - - # Generate sanitized key - key = 'rax_' + (re.sub("[^A-Za-z0-9\-]", "_", key) - .lower() - .lstrip("_")) + for key, value in to_dict(server).items(): hostvars[key] = value # And finally, add an IP address @@ -129,6 +152,7 @@ def host(regions, hostname): def _list(regions): groups = collections.defaultdict(list) hostvars = collections.defaultdict(dict) + images = {} # Go through all the regions looking for servers for region in regions: @@ -139,26 +163,39 @@ def _list(regions): groups[region].append(server.name) # Check if group metadata key in servers' metadata - try: - group = server.metadata['group'] - except KeyError: - pass - else: - # Create group if not exist and add the server + group = server.metadata.get('group') + if group: groups[group].append(server.name) + for extra_group in server.metadata.get('groups', '').split(','): + groups[extra_group].append(server.name) + # Add host metadata - keys = [key for key in vars(server) if key not in ('manager', '_info')] - for key in keys: - # Extract value - value = getattr(server, key) - - # Generate sanitized key - key = 'rax_' + (re.sub("[^A-Za-z0-9\-]", "_", key) - .lower() - .lstrip('_')) + for key, value in to_dict(server).items(): hostvars[server.name][key] = value + hostvars[server.name]['rax_region'] = region + + for key, value in server.metadata.iteritems(): + prefix = os.getenv('RAX_META_PREFIX', 'meta') + groups['%s_%s_%s' % (prefix, key, value)].append(server.name) + + groups['instance-%s' % server.id].append(server.name) + groups['flavor-%s' % server.flavor['id']].append(server.name) + try: + imagegroup = 'image-%s' % images[server.image['id']] + groups[imagegroup].append(server.name) + groups['image-%s' % server.image['id']].append(server.name) + except KeyError: + try: + image = cs.images.get(server.image['id']) + except cs.exceptions.NotFound: + groups['image-%s' % server.image['id']].append(server.name) + else: + images[image.id] = image.human_id + groups['image-%s' % image.human_id].append(server.name) + groups['image-%s' % server.image['id']].append(server.name) + # And finally, add an IP address hostvars[server.name]['ansible_ssh_host'] = server.accessIPv4 @@ -172,7 +209,7 @@ def parse_args(): 'inventory module') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true', - help='List active servers') + help='List active servers') group.add_argument('--host', help='List details about the specific host') return parser.parse_args() @@ -180,38 +217,54 @@ def parse_args(): def setup(): default_creds_file = os.path.expanduser('~/.rackspace_cloud_credentials') + env = os.getenv('RAX_ENV', None) + if env: + pyrax.set_environment(env) + + keyring_username = pyrax.get_setting('keyring_username') + # Attempt to grab credentials from environment first try: - creds_file = os.environ['RAX_CREDS_FILE'] + creds_file = os.path.expanduser(os.environ['RAX_CREDS_FILE']) except KeyError, e: - # But if that fails, use the default location of ~/.rackspace_cloud_credentials + # But if that fails, use the default location of + # ~/.rackspace_cloud_credentials if os.path.isfile(default_creds_file): creds_file = default_creds_file - else: + elif not keyring_username: sys.stderr.write('No value in environment variable %s and/or no ' 'credentials file at %s\n' % (e.message, default_creds_file)) sys.exit(1) - pyrax.set_setting('identity_type', 'rackspace') + identity_type = pyrax.get_setting('identity_type') + pyrax.set_setting('identity_type', identity_type or 'rackspace') + + region = pyrax.get_setting('region') try: - pyrax.set_credential_file(os.path.expanduser(creds_file)) + if keyring_username: + pyrax.keyring_auth(keyring_username, region=region) + else: + pyrax.set_credential_file(creds_file, region=region) except Exception, e: sys.stderr.write("%s: %s\n" % (e, e.message)) sys.exit(1) regions = [] - for region in os.getenv('RAX_REGION', 'all').split(','): - region = region.strip().upper() - if region == 'ALL': - regions = pyrax.regions - break - elif region not in pyrax.regions: - sys.stderr.write('Unsupported region %s' % region) - sys.exit(1) - elif region not in regions: - regions.append(region) + if region: + regions.append(region) + else: + for region in os.getenv('RAX_REGION', 'all').split(','): + region = region.strip().upper() + if region == 'ALL': + regions = pyrax.regions + break + elif region not in pyrax.regions: + sys.stderr.write('Unsupported region %s' % region) + sys.exit(1) + elif region not in regions: + regions.append(region) return regions @@ -225,5 +278,6 @@ def main(): host(regions, args.host) sys.exit(0) + if __name__ == '__main__': - main() \ No newline at end of file + main()