1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Merge pull request #9530 from sivel/rax-inventory-access-network

rax.py inventory: improvements
This commit is contained in:
Michael DeHaan 2014-11-17 12:12:13 -08:00
commit c9ecc51a5e
2 changed files with 261 additions and 68 deletions

57
plugins/inventory/rax.ini Normal file
View file

@ -0,0 +1,57 @@
# Ansible Rackspace external inventory script settings
#
[rax]
# Environment Variable: RAX_CREDS_FILE
#
# An optional configuration that points to a pyrax-compatible credentials
# file.
#
# If not supplied, rax.py will look for a credentials file
# at ~/.rackspace_cloud_credentials. It uses the Rackspace Python SDK,
# and therefore requires a file formatted per the SDK's specifications.
#
# https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
# creds_file = ~/.rackspace_cloud_credentials
# Environment Variable: RAX_REGION
#
# An optional environment variable to narrow inventory search
# scope. If used, needs a value like ORD, DFW, SYD (a Rackspace
# datacenter) and optionally accepts a comma-separated list.
# regions = IAD,ORD,DFW
# Environment Variable: RAX_ENV
#
# A configuration that will use an environment as configured in
# ~/.pyrax.cfg, see
# https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
# env = prod
# Environment Variable: RAX_META_PREFIX
# Default: meta
#
# A configuration that changes the prefix used for meta key/value groups.
# For compatibility with ec2.py set to "tag"
# meta_prefix = meta
# Environment Variable: RAX_ACCESS_NETWORK
# Default: public
#
# A configuration that will tell the inventory script to use a specific
# server network to determine the ansible_ssh_host value. If no address
# is found, ansible_ssh_host will not be set. Accepts a comma-separated
# list of network names, the first found wins.
# access_network = public
# Environment Variable: RAX_ACCESS_IP_VERSION
# Default: 4
#
# A configuration related to "access_network" that will attempt to
# determine the ansible_ssh_host value for either IPv4 or IPv6. If no
# address is found, ansible_ssh_host will not be set.
# Acceptable values are: 4 or 6. Values other than 4 or 6
# will be ignored, and 4 will be used. Accepts a comma separated list,
# the first found wins.
# access_ip_version = 4

270
plugins/inventory/rax.py Executable file → Normal file
View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# (c) 2013, Jesse Keating <jesse.keating@rackspace.com> # (c) 2013, Jesse Keating <jesse.keating@rackspace.com,
# Paul Durivage <paul.durivage@rackspace.com>,
# Matt Martz <matt@sivel.net>
# #
# This file is part of Ansible, # This file is part of Ansible.
# #
# Ansible is free software: you can redistribute it and/or modify # Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -17,16 +19,20 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = ''' """
--- Rackspace Cloud Inventory
inventory: rax
short_description: Rackspace Public Cloud external inventory script Authors:
description: Jesse Keating <jesse.keating@rackspace.com,
- Generates inventory that Ansible can understand by making API request to Paul Durivage <paul.durivage@rackspace.com>,
Matt Martz <matt@sivel.net>
Description:
Generates inventory that Ansible can understand by making API request to
Rackspace Public Cloud API Rackspace Public Cloud API
- |
When run against a specific host, this script returns the following When run against a specific host, this script returns variables similar to:
variables:
rax_os-ext-sts_task_state rax_os-ext-sts_task_state
rax_addresses rax_addresses
rax_links rax_links
@ -50,72 +56,131 @@ description:
rax_tenant_id rax_tenant_id
rax_loaded rax_loaded
where some item can have nested structure. Configuration:
- credentials are set in a credentials file rax.py can be configured using a rax.ini file or via environment
version_added: None variables. The rax.ini file should live in the same directory along side
options: this script.
The section header for configuration values related to this
inventory plugin is [rax]
[rax]
creds_file = ~/.rackspace_cloud_credentials
regions = IAD,ORD,DFW
env = prod
meta_prefix = meta
access_network = public
access_ip_version = 4
Each of these configurations also has a corresponding environment variable.
An environment variable will override a configuration file value.
creds_file: creds_file:
description: Environment Variable: RAX_CREDS_FILE
- File to find the Rackspace Public Cloud credentials in
required: true An optional configuration that points to a pyrax-compatible credentials
default: null file.
region:
description: If not supplied, rax.py will look for a credentials file
- An optional value to narrow inventory scope, i.e. DFW, ORD, IAD, LON at ~/.rackspace_cloud_credentials. It uses the Rackspace Python SDK,
required: false and therefore requires a file formatted per the SDK's specifications.
default: null
authors: https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
- Jesse Keating <jesse.keating@rackspace.com>
- Paul Durivage <paul.durivage@rackspace.com> regions:
- Matt Martz <matt@sivel.net> Environment Variable: RAX_REGION
notes:
- RAX_CREDS_FILE is an optional environment variable that points to a An optional environment variable to narrow inventory search
pyrax-compatible credentials file. scope. If used, needs a value like ORD, DFW, SYD (a Rackspace
- If RAX_CREDS_FILE is not supplied, rax.py will look for a credentials file datacenter) and optionally accepts a comma-separated list.
at ~/.rackspace_cloud_credentials.
- See https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating environment:
- RAX_REGION is an optional environment variable to narrow inventory search Environment Variable: RAX_ENV
scope
- RAX_REGION, if used, needs a value like ORD, DFW, SYD (a Rackspace A configuration that will use an environment as configured in
datacenter) and optionally accepts a comma-separated list ~/.pyrax.cfg, see
- RAX_ENV is an environment variable that will use an environment as https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
configured in ~/.pyrax.cfg, see
https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration meta_prefix:
- RAX_META_PREFIX is an environment variable that changes the prefix used Environment Variable: RAX_META_PREFIX
for meta key/value groups. For compatibility with ec2.py set to Default: meta
RAX_META_PREFIX=tag
requirements: [ "pyrax" ] A configuration that changes the prefix used for meta key/value groups.
examples: For compatibility with ec2.py set to "tag"
- description: List server instances
code: RAX_CREDS_FILE=~/.raxpub rax.py --list access_network:
- description: List servers in ORD datacenter only Environment Variable: RAX_ACCESS_NETWORK
code: RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD rax.py --list Default: public
- description: List servers in ORD and DFW datacenters
code: RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD,DFW rax.py --list A configuration that will tell the inventory script to use a specific
- description: Get server details for server named "server.example.com" server network to determine the ansible_ssh_host value. If no address
code: RAX_CREDS_FILE=~/.raxpub rax.py --host server.example.com is found, ansible_ssh_host will not be set. Accepts a comma-separated
''' list of network names, the first found wins.
access_ip_version:
Environment Variable: RAX_ACCESS_IP_VERSION
Default: 4
A configuration related to "access_network" that will attempt to
determine the ansible_ssh_host value for either IPv4 or IPv6. If no
address is found, ansible_ssh_host will not be set.
Acceptable values are: 4 or 6. Values other than 4 or 6
will be ignored, and 4 will be used. Accepts a comma-separated list,
the first found wins.
Examples:
List server instances
$ RAX_CREDS_FILE=~/.raxpub rax.py --list
List servers in ORD datacenter only
$ RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD rax.py --list
List servers in ORD and DFW datacenters
$ RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD,DFW rax.py --list
Get server details for server named "server.example.com"
$ RAX_CREDS_FILE=~/.raxpub rax.py --host server.example.com
Use the instance private IP to connect (instead of public IP)
$ RAX_CREDS_FILE=~/.raxpub RAX_ACCESS_NETWORK=private rax.py --list
"""
import os import os
import re import re
import sys import sys
import argparse import argparse
import warnings
import collections import collections
import ConfigParser
from types import NoneType from ansible.constants import get_config, mk_boolean
try: try:
import json import json
except: except ImportError:
import simplejson as json import simplejson as json
try: try:
import pyrax import pyrax
from pyrax.utils import slugify
except ImportError: except ImportError:
print('pyrax is required for this module') print('pyrax is required for this module')
sys.exit(1) sys.exit(1)
NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
def load_config_file():
p = ConfigParser.ConfigParser()
config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'rax.ini')
try:
p.read(config_file)
except ConfigParser.Error:
return None
else:
return p
p = load_config_file()
def rax_slugify(value): def rax_slugify(value):
@ -126,7 +191,7 @@ def to_dict(obj):
instance = {} instance = {}
for key in dir(obj): for key in dir(obj):
value = getattr(obj, key) value = getattr(obj, key)
if (isinstance(value, NON_CALLABLES) and not key.startswith('_')): if isinstance(value, NON_CALLABLES) and not key.startswith('_'):
key = rax_slugify(key) key = rax_slugify(key)
instance[key] = value instance[key] = value
@ -153,11 +218,33 @@ def _list(regions):
groups = collections.defaultdict(list) groups = collections.defaultdict(list)
hostvars = collections.defaultdict(dict) hostvars = collections.defaultdict(dict)
images = {} images = {}
cbs_attachments = collections.defaultdict(dict)
prefix = get_config(p, 'rax', 'meta_prefix', 'RAX_META_PREFIX', 'meta')
networks = get_config(p, 'rax', 'access_network', 'RAX_ACCESS_NETWORK',
'public', islist=True)
try:
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
'RAX_ACCESS_IP_VERSION', 4,
islist=True))
except:
ip_versions = [4]
else:
ip_versions = [v for v in ip_versions if v in [4, 6]]
if not ip_versions:
ip_versions = [4]
# Go through all the regions looking for servers # Go through all the regions looking for servers
for region in regions: for region in regions:
# Connect to the region # Connect to the region
cs = pyrax.connect_to_cloudservers(region=region) cs = pyrax.connect_to_cloudservers(region=region)
if cs is None:
warnings.warn(
'Connecting to Rackspace region "%s" has caused Pyrax to '
'return a NoneType. Is this a valid region?' % region,
RuntimeWarning)
continue
for server in cs.servers.list(): for server in cs.servers.list():
# Create a group on region # Create a group on region
groups[region].append(server.name) groups[region].append(server.name)
@ -178,11 +265,33 @@ def _list(regions):
hostvars[server.name]['rax_region'] = region hostvars[server.name]['rax_region'] = region
for key, value in server.metadata.iteritems(): 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['%s_%s_%s' % (prefix, key, value)].append(server.name)
groups['instance-%s' % server.id].append(server.name) groups['instance-%s' % server.id].append(server.name)
groups['flavor-%s' % server.flavor['id']].append(server.name) groups['flavor-%s' % server.flavor['id']].append(server.name)
# Handle boot from volume
if not server.image:
if not cbs_attachments[region]:
cbs = pyrax.connect_to_cloud_blockstorage(region)
for vol in cbs.list():
if mk_boolean(vol.bootable):
for attachment in vol.attachments:
metadata = vol.volume_image_metadata
server_id = attachment['server_id']
cbs_attachments[region][server_id] = {
'id': metadata['image_id'],
'name': slugify(metadata['image_name'])
}
image = cbs_attachments[region].get(server.id)
if image:
server.image = {'id': image['id']}
hostvars[server.name]['rax_image'] = server.image
hostvars[server.name]['rax_boot_source'] = 'volume'
images[image['id']] = image['name']
else:
hostvars[server.name]['rax_boot_source'] = 'local'
try: try:
imagegroup = 'image-%s' % images[server.image['id']] imagegroup = 'image-%s' % images[server.image['id']]
groups[imagegroup].append(server.name) groups[imagegroup].append(server.name)
@ -198,7 +307,30 @@ def _list(regions):
groups['image-%s' % server.image['id']].append(server.name) groups['image-%s' % server.image['id']].append(server.name)
# And finally, add an IP address # And finally, add an IP address
hostvars[server.name]['ansible_ssh_host'] = server.accessIPv4 ansible_ssh_host = None
# use accessIPv[46] instead of looping address for 'public'
for network_name in networks:
if ansible_ssh_host:
break
if network_name == 'public':
for version_name in ip_versions:
if ansible_ssh_host:
break
if version_name == 6 and server.accessIPv6:
ansible_ssh_host = server.accessIPv6
elif server.accessIPv4:
ansible_ssh_host = server.accessIPv4
if not ansible_ssh_host:
addresses = server.addresses.get(network_name, [])
for address in addresses:
for version_name in ip_versions:
if ansible_ssh_host:
break
if address.get('version') == version_name:
ansible_ssh_host = address.get('addr')
break
if ansible_ssh_host:
hostvars[server.name]['ansible_ssh_host'] = ansible_ssh_host
if hostvars: if hostvars:
groups['_meta'] = {'hostvars': hostvars} groups['_meta'] = {'hostvars': hostvars}
@ -218,16 +350,18 @@ def parse_args():
def setup(): def setup():
default_creds_file = os.path.expanduser('~/.rackspace_cloud_credentials') default_creds_file = os.path.expanduser('~/.rackspace_cloud_credentials')
env = os.getenv('RAX_ENV', None) env = get_config(p, 'rax', 'environment', 'RAX_ENV', None)
if env: if env:
pyrax.set_environment(env) pyrax.set_environment(env)
keyring_username = pyrax.get_setting('keyring_username') keyring_username = pyrax.get_setting('keyring_username')
# Attempt to grab credentials from environment first # Attempt to grab credentials from environment first
try: creds_file = get_config(p, 'rax', 'creds_file',
creds_file = os.path.expanduser(os.environ['RAX_CREDS_FILE']) 'RAX_CREDS_FILE', None)
except KeyError, e: if creds_file is not None:
creds_file = os.path.expanduser(creds_file)
else:
# But if that fails, use the default location of # But if that fails, use the default location of
# ~/.rackspace_cloud_credentials # ~/.rackspace_cloud_credentials
if os.path.isfile(default_creds_file): if os.path.isfile(default_creds_file):
@ -235,7 +369,7 @@ def setup():
elif not keyring_username: elif not keyring_username:
sys.stderr.write('No value in environment variable %s and/or no ' sys.stderr.write('No value in environment variable %s and/or no '
'credentials file at %s\n' 'credentials file at %s\n'
% (e.message, default_creds_file)) % ('RAX_CREDS_FILE', default_creds_file))
sys.exit(1) sys.exit(1)
identity_type = pyrax.get_setting('identity_type') identity_type = pyrax.get_setting('identity_type')
@ -256,7 +390,9 @@ def setup():
if region: if region:
regions.append(region) regions.append(region)
else: else:
for region in os.getenv('RAX_REGION', 'all').split(','): region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
islist=True)
for region in region_list:
region = region.strip().upper() region = region.strip().upper()
if region == 'ALL': if region == 'ALL':
regions = pyrax.regions regions = pyrax.regions