Merge pull request #9530 from sivel/rax-inventory-access-network
rax.py inventory: improvements
This commit is contained in:
commit
c9ecc51a5e
2 changed files with 261 additions and 68 deletions
57
plugins/inventory/rax.ini
Normal file
57
plugins/inventory/rax.ini
Normal 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
|
272
plugins/inventory/rax.py
Executable file → Normal file
272
plugins/inventory/rax.py
Executable file → Normal file
|
@ -1,8 +1,10 @@
|
|||
#!/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
|
||||
# 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
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
inventory: rax
|
||||
short_description: Rackspace Public Cloud external inventory script
|
||||
description:
|
||||
- Generates inventory that Ansible can understand by making API request to
|
||||
"""
|
||||
Rackspace Cloud Inventory
|
||||
|
||||
Authors:
|
||||
Jesse Keating <jesse.keating@rackspace.com,
|
||||
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
|
||||
- |
|
||||
When run against a specific host, this script returns the following
|
||||
variables:
|
||||
|
||||
When run against a specific host, this script returns variables similar to:
|
||||
rax_os-ext-sts_task_state
|
||||
rax_addresses
|
||||
rax_links
|
||||
|
@ -50,72 +56,131 @@ description:
|
|||
rax_tenant_id
|
||||
rax_loaded
|
||||
|
||||
where some item can have nested structure.
|
||||
- credentials are set in a credentials file
|
||||
version_added: None
|
||||
options:
|
||||
creds_file:
|
||||
description:
|
||||
- File to find the Rackspace Public Cloud credentials in
|
||||
required: true
|
||||
default: null
|
||||
region:
|
||||
description:
|
||||
- An optional value to narrow inventory scope, i.e. DFW, ORD, IAD, LON
|
||||
required: false
|
||||
default: null
|
||||
authors:
|
||||
- Jesse Keating <jesse.keating@rackspace.com>
|
||||
- Paul Durivage <paul.durivage@rackspace.com>
|
||||
- Matt Martz <matt@sivel.net>
|
||||
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.
|
||||
- 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_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
|
||||
code: RAX_CREDS_FILE=~/.raxpub rax.py --list
|
||||
- description: List servers in ORD datacenter only
|
||||
code: RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD rax.py --list
|
||||
- description: List servers in ORD and DFW datacenters
|
||||
code: RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD,DFW rax.py --list
|
||||
- description: Get server details for server named "server.example.com"
|
||||
code: RAX_CREDS_FILE=~/.raxpub rax.py --host server.example.com
|
||||
'''
|
||||
Configuration:
|
||||
rax.py can be configured using a rax.ini file or via environment
|
||||
variables. The rax.ini file should live in the same directory along side
|
||||
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:
|
||||
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
|
||||
|
||||
regions:
|
||||
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.
|
||||
|
||||
environment:
|
||||
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
|
||||
|
||||
meta_prefix:
|
||||
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"
|
||||
|
||||
access_network:
|
||||
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_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 re
|
||||
import sys
|
||||
import argparse
|
||||
import warnings
|
||||
import collections
|
||||
import ConfigParser
|
||||
|
||||
from types import NoneType
|
||||
from ansible.constants import get_config, mk_boolean
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
from pyrax.utils import slugify
|
||||
except ImportError:
|
||||
print('pyrax is required for this module')
|
||||
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):
|
||||
|
@ -126,7 +191,7 @@ def to_dict(obj):
|
|||
instance = {}
|
||||
for key in dir(obj):
|
||||
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)
|
||||
instance[key] = value
|
||||
|
||||
|
@ -153,11 +218,33 @@ def _list(regions):
|
|||
groups = collections.defaultdict(list)
|
||||
hostvars = collections.defaultdict(dict)
|
||||
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
|
||||
for region in regions:
|
||||
# Connect to the 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():
|
||||
# Create a group on region
|
||||
groups[region].append(server.name)
|
||||
|
@ -178,11 +265,33 @@ def _list(regions):
|
|||
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)
|
||||
|
||||
# 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:
|
||||
imagegroup = 'image-%s' % images[server.image['id']]
|
||||
groups[imagegroup].append(server.name)
|
||||
|
@ -198,7 +307,30 @@ def _list(regions):
|
|||
groups['image-%s' % server.image['id']].append(server.name)
|
||||
|
||||
# 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:
|
||||
groups['_meta'] = {'hostvars': hostvars}
|
||||
|
@ -218,16 +350,18 @@ def parse_args():
|
|||
def setup():
|
||||
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:
|
||||
pyrax.set_environment(env)
|
||||
|
||||
keyring_username = pyrax.get_setting('keyring_username')
|
||||
|
||||
# Attempt to grab credentials from environment first
|
||||
try:
|
||||
creds_file = os.path.expanduser(os.environ['RAX_CREDS_FILE'])
|
||||
except KeyError, e:
|
||||
creds_file = get_config(p, 'rax', 'creds_file',
|
||||
'RAX_CREDS_FILE', None)
|
||||
if creds_file is not None:
|
||||
creds_file = os.path.expanduser(creds_file)
|
||||
else:
|
||||
# But if that fails, use the default location of
|
||||
# ~/.rackspace_cloud_credentials
|
||||
if os.path.isfile(default_creds_file):
|
||||
|
@ -235,7 +369,7 @@ def setup():
|
|||
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))
|
||||
% ('RAX_CREDS_FILE', default_creds_file))
|
||||
sys.exit(1)
|
||||
|
||||
identity_type = pyrax.get_setting('identity_type')
|
||||
|
@ -256,7 +390,9 @@ def setup():
|
|||
if region:
|
||||
regions.append(region)
|
||||
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()
|
||||
if region == 'ALL':
|
||||
regions = pyrax.regions
|
||||
|
|
Loading…
Reference in a new issue