mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 21:49:27 -02:30
Dependency Updates
* Dynamic Inventory Source Template against ansible 2.3 dynamic inventory sources. The major change is removal of `rax.py`. Most upstream scripts except `foreman.py` has quite trivial coding style changes, or minor functional extensions that does not affect Tower inventory update runs. `foreman.py`, on the other hand, went through quite a major refactoring, but functionalities stay the same. Major python dependency updates include apache-libcloud (1.3.0 --> 2.0.0), boto (2.45.0 --> 2.46.1) and shade (1.19.0 --> 1.20.0). Minor python dependency updates include indirect updates via `pip-compile`, which are determined by base dependencies. Some minor `task.py` extensions: - `.ini` file for ec2 has one more field `stack_filter=False`, which reveals changes in `ec2.py`. - `.ini` file for cloudforms will catch these four options from `source_vars_dict` of inventory update: `'version', 'purge_actions', 'clean_group_keys', 'nest_tags'`. These four options have always been available in `cloudforms.py` but `cloudforms.ini.example` has not mentioned them until the latest version. For consistency with upstream docs, we should make these fields available for tower user to customize. - YAML file of openstack will catch ansible options `use_hostnames`, `expand_hostvars` and `fail_on_errors` from `source_vars_dict` of inventory update as a response to issue #6075. * Remove Rackspace support Supports of Rackspace as both a dynamic inventory source and a cloud credential are fully removed. Data migrations have been added to support arbitrary credential types feature and delete rackspace inventory sources. Note also requirement `jsonschema` has been moved from `requirements.txt` to `requirements.in` as a primary dependency to reflect it's usage in `/main/fields.py`. Connected issue: #6080. * `pexpect` major update `pexpect` stands at the very core of our task system and underwent a major update from 3.1 to 4.2.1. Although verified during devel, please still be mindful of any suspicious issues on celery side even after this PR gets merged. * Miscellaneous - requests now explicitly declared in `requirements.in` at version 2.11.1 in response to upstream issue - celery: 3.1.17 -> 3.1.25 - django-extensions: 1.7.4 -> 1.7.8 - django-polymorphic: 0.7.2 -> 1.2 - django-split-settings: 0.2.2 -> 0.2.5 - django-taggit: 0.21.3 -> 0.22.1 - irc: 15.0.4 -> 15.1.1 - pygerduty: 0.35.1 -> 0.35.2 - pyOpenSSL: 16.2.0 -> 17.0.0 - python-saml: 2.2.0 -> 2.2.1 - redbaron: 0.6.2 -> 0.6.3 - slackclient: 1.0.2 -> 1.0.5 - tacacs_plus: 0.1 -> 0.2 - xmltodict: 0.10.2 -> 0.11.0 - pip: 8.1.2 -> 9.0.1 - setuptools: 23.0.0 -> 35.0.2 - (requirements_ansible.in only)kombu: 3.0.35 -> 3.0.37
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Configuration file for azure_rm.py
|
||||
#
|
||||
#
|
||||
[azure]
|
||||
# Control which resource groups are included. By default all resources groups are included.
|
||||
# Set resource_groups to a comma separated list of resource groups names.
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Azure External Inventory Script
|
||||
===============================
|
||||
Generates dynamic inventory by making API requests to the Azure Resource
|
||||
Manager using the AAzure Python SDK. For instruction on installing the
|
||||
Manager using the Azure Python SDK. For instruction on installing the
|
||||
Azure Python SDK see http://azure-sdk-for-python.readthedocs.org/
|
||||
|
||||
Authentication
|
||||
@@ -32,7 +32,7 @@ The order of precedence is command line arguments, environment variables,
|
||||
and finally the [default] profile found in ~/.azure/credentials.
|
||||
|
||||
If using a credentials file, it should be an ini formatted file with one or
|
||||
more sections, which we refer to as profiles. The script looks for a
|
||||
more sections, which we refer to as profiles. The script looks for a
|
||||
[default] section, if a profile is not specified either on the command line
|
||||
or with an environment variable. The keys in a profile will match the
|
||||
list of command line arguments below.
|
||||
@@ -42,7 +42,7 @@ in your ~/.azure/credentials file, or a service principal or Active Directory
|
||||
user.
|
||||
|
||||
Command line arguments:
|
||||
- profile
|
||||
- profile
|
||||
- client_id
|
||||
- secret
|
||||
- subscription_id
|
||||
@@ -61,7 +61,7 @@ Environment variables:
|
||||
|
||||
Run for Specific Host
|
||||
-----------------------
|
||||
When run for a specific host using the --host option, a resource group is
|
||||
When run for a specific host using the --host option, a resource group is
|
||||
required. For a specific host, this script returns the following variables:
|
||||
|
||||
{
|
||||
@@ -191,7 +191,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
from packaging.version import Version
|
||||
|
||||
from os.path import expanduser
|
||||
|
||||
@@ -309,7 +309,7 @@ class AzureRM(object):
|
||||
|
||||
def _get_env_credentials(self):
|
||||
env_credentials = dict()
|
||||
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems():
|
||||
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
|
||||
env_credentials[attribute] = os.environ.get(env_variable, None)
|
||||
|
||||
if env_credentials['profile'] is not None:
|
||||
@@ -328,7 +328,7 @@ class AzureRM(object):
|
||||
self.log('Getting credentials')
|
||||
|
||||
arg_credentials = dict()
|
||||
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems():
|
||||
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
|
||||
arg_credentials[attribute] = getattr(params, attribute)
|
||||
|
||||
# try module params
|
||||
@@ -362,7 +362,11 @@ class AzureRM(object):
|
||||
resource_client = self.rm_client
|
||||
resource_client.providers.register(key)
|
||||
except Exception as exc:
|
||||
self.fail("One-time registration of {0} failed - {1}".format(key, str(exc)))
|
||||
self.log("One-time registration of {0} failed - {1}".format(key, str(exc)))
|
||||
self.log("You might need to register {0} using an admin account".format(key))
|
||||
self.log(("To register a provider using the Python CLI: "
|
||||
"https://docs.microsoft.com/azure/azure-resource-manager/"
|
||||
"resource-manager-common-deployment-errors#noregisteredproviderfound"))
|
||||
|
||||
@property
|
||||
def network_client(self):
|
||||
@@ -442,7 +446,7 @@ class AzureInventory(object):
|
||||
def _parse_cli_args(self):
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce an Ansible Inventory file for an Azure subscription')
|
||||
description='Produce an Ansible Inventory file for an Azure subscription')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--debug', action='store_true', default=False,
|
||||
@@ -664,7 +668,7 @@ class AzureInventory(object):
|
||||
self._inventory['azure'].append(host_name)
|
||||
|
||||
if self.group_by_tag and vars.get('tags'):
|
||||
for key, value in vars['tags'].iteritems():
|
||||
for key, value in vars['tags'].items():
|
||||
safe_key = self._to_safe(key)
|
||||
safe_value = safe_key + '_' + self._to_safe(value)
|
||||
if not self._inventory.get(safe_key):
|
||||
@@ -724,7 +728,7 @@ class AzureInventory(object):
|
||||
|
||||
def _get_env_settings(self):
|
||||
env_settings = dict()
|
||||
for attribute, env_variable in AZURE_CONFIG_SETTINGS.iteritems():
|
||||
for attribute, env_variable in AZURE_CONFIG_SETTINGS.items():
|
||||
env_settings[attribute] = os.environ.get(env_variable, None)
|
||||
return env_settings
|
||||
|
||||
@@ -786,11 +790,11 @@ class AzureInventory(object):
|
||||
|
||||
def main():
|
||||
if not HAS_AZURE:
|
||||
sys.exit("The Azure python sdk is not installed (try 'pip install azure>=2.0.0rc5') - {0}".format(HAS_AZURE_EXC))
|
||||
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>=2.0.0rc5' --upgrade`) - {0}".format(HAS_AZURE_EXC))
|
||||
|
||||
if LooseVersion(azure_compute_version) < LooseVersion(AZURE_MIN_VERSION):
|
||||
if Version(azure_compute_version) < Version(AZURE_MIN_VERSION):
|
||||
sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} "
|
||||
"Do you have Azure >= 2.0.0rc5 installed?".format(AZURE_MIN_VERSION, azure_compute_version))
|
||||
"Do you have Azure >= 2.0.0rc5 installed? (try `pip install 'azure>=2.0.0rc5' --upgrade`)".format(AZURE_MIN_VERSION, azure_compute_version))
|
||||
|
||||
AzureInventory()
|
||||
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
# Ansible CloudForms external inventory script settings
|
||||
#
|
||||
|
||||
[cloudforms]
|
||||
|
||||
# The version of CloudForms (this is not used yet)
|
||||
version = 3.1
|
||||
# the version of CloudForms ; currently not used, but tested with
|
||||
version = 4.1
|
||||
|
||||
# The hostname of the CloudForms server
|
||||
hostname = #insert your hostname here
|
||||
# This should be the hostname of the CloudForms server
|
||||
url = https://cfme.example.com
|
||||
|
||||
# Username for CloudForms
|
||||
username = #insert your cloudforms user here
|
||||
# This will more than likely need to be a local CloudForms username
|
||||
username = <set your username here>
|
||||
|
||||
# Password for CloudForms user
|
||||
password = #password
|
||||
# The password for said username
|
||||
password = <set your password here>
|
||||
|
||||
# True = verify SSL certificate / False = trust anything
|
||||
ssl_verify = True
|
||||
|
||||
# limit the number of vms returned per request
|
||||
limit = 100
|
||||
|
||||
# purge the CloudForms actions from hosts
|
||||
purge_actions = True
|
||||
|
||||
# Clean up group names (from tags and other groupings so Ansible doesn't complain)
|
||||
clean_group_keys = True
|
||||
|
||||
# Explode tags into nested groups / subgroups
|
||||
nest_tags = False
|
||||
|
||||
[cache]
|
||||
|
||||
# Maximum time to trust the cache in seconds
|
||||
max_age = 600
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
|
||||
# AWS regions to make calls to. Set this to 'all' to make request to all regions
|
||||
# in AWS and merge the results together. Alternatively, set this to a comma
|
||||
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
|
||||
# separated list of regions. E.g. 'us-east-1, us-west-1, us-west-2'
|
||||
# 'auto' is AWS_REGION or AWS_DEFAULT_REGION environment variable.
|
||||
regions = all
|
||||
regions_exclude = us-gov-west-1,cn-north-1
|
||||
regions_exclude = us-gov-west-1, cn-north-1
|
||||
|
||||
# When generating inventory, Ansible needs to know how to address a server.
|
||||
# Each EC2 instance has a lot of variables associated with it. Here is the list:
|
||||
@@ -56,14 +57,19 @@ vpc_destination_variable = ip_address
|
||||
#destination_format_tags = Name,environment
|
||||
|
||||
# To tag instances on EC2 with the resource records that point to them from
|
||||
# Route53, uncomment and set 'route53' to True.
|
||||
# Route53, set 'route53' to True.
|
||||
route53 = False
|
||||
|
||||
# To use Route53 records as the inventory hostnames, uncomment and set
|
||||
# to equal the domain name you wish to use. You must also have 'route53' (above)
|
||||
# set to True.
|
||||
# route53_hostnames = .example.com
|
||||
|
||||
# To exclude RDS instances from the inventory, uncomment and set to False.
|
||||
rds = False
|
||||
#rds = False
|
||||
|
||||
# To exclude ElastiCache instances from the inventory, uncomment and set to False.
|
||||
elasticache = False
|
||||
#elasticache = False
|
||||
|
||||
# Additionally, you can specify the list of zones to exclude looking up in
|
||||
# 'route53_excluded_zones' as a comma-separated list.
|
||||
@@ -75,7 +81,7 @@ all_instances = False
|
||||
|
||||
# By default, only EC2 instances in the 'running' state are returned. Specify
|
||||
# EC2 instance states to return as a comma-separated list. This
|
||||
# option is overriden when 'all_instances' is True.
|
||||
# option is overridden when 'all_instances' is True.
|
||||
# instance_states = pending, running, shutting-down, terminated, stopping, stopped
|
||||
|
||||
# By default, only RDS instances in the 'available' state are returned. Set
|
||||
@@ -107,7 +113,7 @@ cache_path = ~/.ansible/tmp
|
||||
# The number of seconds a cache file is considered valid. After this many
|
||||
# seconds, a new API call will be made, and the cache file will be updated.
|
||||
# To disable the cache, set this value to 0
|
||||
cache_max_age = 0
|
||||
cache_max_age = 300
|
||||
|
||||
# Organize groups into a nested/hierarchy instead of a flat namespace.
|
||||
nested_groups = False
|
||||
@@ -117,15 +123,17 @@ replace_dash_in_groups = True
|
||||
|
||||
# If set to true, any tag of the form "a,b,c" is expanded into a list
|
||||
# and the results are used to create additional tag_* inventory groups.
|
||||
expand_csv_tags = True
|
||||
expand_csv_tags = False
|
||||
|
||||
# The EC2 inventory output can become very large. To manage its size,
|
||||
# configure which groups should be created.
|
||||
group_by_instance_id = True
|
||||
group_by_region = True
|
||||
group_by_availability_zone = True
|
||||
group_by_aws_account = False
|
||||
group_by_ami_id = True
|
||||
group_by_instance_type = True
|
||||
group_by_instance_state = False
|
||||
group_by_key_pair = True
|
||||
group_by_vpc_id = True
|
||||
group_by_security_group = True
|
||||
@@ -151,6 +159,12 @@ group_by_elasticache_replication_group = True
|
||||
# Filters are key/value pairs separated by '=', to list multiple filters use
|
||||
# a list separated by commas. See examples below.
|
||||
|
||||
# If you want to apply multiple filters simultaneously, set stack_filters to
|
||||
# True. Default behaviour is to combine the results of all filters. Stacking
|
||||
# allows the use of multiple conditions to filter down, for example by
|
||||
# environment and type of host.
|
||||
stack_filters = False
|
||||
|
||||
# Retrieve only instances with (key=value) env=staging tag
|
||||
# instance_filters = tag:env=staging
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ variables needed for Boto have already been set:
|
||||
export AWS_ACCESS_KEY_ID='AK123'
|
||||
export AWS_SECRET_ACCESS_KEY='abc123'
|
||||
|
||||
optional region environement variable if region is 'auto'
|
||||
|
||||
This script also assumes there is an ec2.ini file alongside it. To specify a
|
||||
different path to ec2.ini, define the EC2_INI_PATH environment variable:
|
||||
|
||||
@@ -162,6 +164,8 @@ class Ec2Inventory(object):
|
||||
# and availability zones
|
||||
self.inventory = self._empty_inventory()
|
||||
|
||||
self.aws_account_id = None
|
||||
|
||||
# Index of hostname (address) to instance ID
|
||||
self.index = {}
|
||||
|
||||
@@ -216,12 +220,22 @@ class Ec2Inventory(object):
|
||||
|
||||
def read_settings(self):
|
||||
''' Reads the settings from the ec2.ini file '''
|
||||
|
||||
scriptbasename = __file__
|
||||
scriptbasename = os.path.basename(scriptbasename)
|
||||
scriptbasename = scriptbasename.replace('.py', '')
|
||||
|
||||
defaults = {'ec2': {
|
||||
'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename)
|
||||
}
|
||||
}
|
||||
|
||||
if six.PY3:
|
||||
config = configparser.ConfigParser()
|
||||
else:
|
||||
config = configparser.SafeConfigParser()
|
||||
ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini')
|
||||
ec2_ini_path = os.path.expanduser(os.path.expandvars(os.environ.get('EC2_INI_PATH', ec2_default_ini_path)))
|
||||
ec2_ini_path = os.environ.get('EC2_INI_PATH', defaults['ec2']['ini_path'])
|
||||
ec2_ini_path = os.path.expanduser(os.path.expandvars(ec2_ini_path))
|
||||
config.read(ec2_ini_path)
|
||||
|
||||
# is eucalyptus?
|
||||
@@ -245,6 +259,11 @@ class Ec2Inventory(object):
|
||||
self.regions.append(regionInfo.name)
|
||||
else:
|
||||
self.regions = configRegions.split(",")
|
||||
if 'auto' in self.regions:
|
||||
env_region = os.environ.get('AWS_REGION')
|
||||
if env_region is None:
|
||||
env_region = os.environ.get('AWS_DEFAULT_REGION')
|
||||
self.regions = [ env_region ]
|
||||
|
||||
# Destination addresses
|
||||
self.destination_variable = config.get('ec2', 'destination_variable')
|
||||
@@ -265,6 +284,10 @@ class Ec2Inventory(object):
|
||||
|
||||
# Route53
|
||||
self.route53_enabled = config.getboolean('ec2', 'route53')
|
||||
if config.has_option('ec2', 'route53_hostnames'):
|
||||
self.route53_hostnames = config.get('ec2', 'route53_hostnames')
|
||||
else:
|
||||
self.route53_hostnames = None
|
||||
self.route53_excluded_zones = []
|
||||
if config.has_option('ec2', 'route53_excluded_zones'):
|
||||
self.route53_excluded_zones.extend(
|
||||
@@ -306,13 +329,13 @@ class Ec2Inventory(object):
|
||||
if self.all_instances:
|
||||
self.ec2_instance_states = ec2_valid_instance_states
|
||||
elif config.has_option('ec2', 'instance_states'):
|
||||
for instance_state in config.get('ec2', 'instance_states').split(','):
|
||||
instance_state = instance_state.strip()
|
||||
if instance_state not in ec2_valid_instance_states:
|
||||
continue
|
||||
self.ec2_instance_states.append(instance_state)
|
||||
for instance_state in config.get('ec2', 'instance_states').split(','):
|
||||
instance_state = instance_state.strip()
|
||||
if instance_state not in ec2_valid_instance_states:
|
||||
continue
|
||||
self.ec2_instance_states.append(instance_state)
|
||||
else:
|
||||
self.ec2_instance_states = ['running']
|
||||
self.ec2_instance_states = ['running']
|
||||
|
||||
# Return all RDS instances? (if RDS is enabled)
|
||||
if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
|
||||
@@ -338,8 +361,8 @@ class Ec2Inventory(object):
|
||||
else:
|
||||
self.all_elasticache_nodes = False
|
||||
|
||||
# boto configuration profile (prefer CLI argument)
|
||||
self.boto_profile = self.args.boto_profile
|
||||
# boto configuration profile (prefer CLI argument then environment variables then config file)
|
||||
self.boto_profile = self.args.boto_profile or os.environ.get('AWS_PROFILE')
|
||||
if config.has_option('ec2', 'boto_profile') and not self.boto_profile:
|
||||
self.boto_profile = config.get('ec2', 'boto_profile')
|
||||
|
||||
@@ -374,14 +397,11 @@ class Ec2Inventory(object):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
cache_name = 'ansible-ec2'
|
||||
aws_profile = lambda: (self.boto_profile or
|
||||
os.environ.get('AWS_PROFILE') or
|
||||
os.environ.get('AWS_ACCESS_KEY_ID') or
|
||||
self.credentials.get('aws_access_key_id', None))
|
||||
if aws_profile():
|
||||
cache_name = '%s-%s' % (cache_name, aws_profile())
|
||||
self.cache_path_cache = cache_dir + "/%s.cache" % cache_name
|
||||
self.cache_path_index = cache_dir + "/%s.index" % cache_name
|
||||
cache_id = self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID', self.credentials.get('aws_access_key_id'))
|
||||
if cache_id:
|
||||
cache_name = '%s-%s' % (cache_name, cache_id)
|
||||
self.cache_path_cache = os.path.join(cache_dir, "%s.cache" % cache_name)
|
||||
self.cache_path_index = os.path.join(cache_dir, "%s.index" % cache_name)
|
||||
self.cache_max_age = config.getint('ec2', 'cache_max_age')
|
||||
|
||||
if config.has_option('ec2', 'expand_csv_tags'):
|
||||
@@ -408,6 +428,7 @@ class Ec2Inventory(object):
|
||||
'group_by_availability_zone',
|
||||
'group_by_ami_id',
|
||||
'group_by_instance_type',
|
||||
'group_by_instance_state',
|
||||
'group_by_key_pair',
|
||||
'group_by_vpc_id',
|
||||
'group_by_security_group',
|
||||
@@ -420,6 +441,7 @@ class Ec2Inventory(object):
|
||||
'group_by_elasticache_cluster',
|
||||
'group_by_elasticache_parameter_group',
|
||||
'group_by_elasticache_replication_group',
|
||||
'group_by_aws_account',
|
||||
]
|
||||
for option in group_by_options:
|
||||
if config.has_option('ec2', option):
|
||||
@@ -439,7 +461,7 @@ class Ec2Inventory(object):
|
||||
|
||||
# Do we need to exclude hosts that match a pattern?
|
||||
try:
|
||||
pattern_exclude = config.get('ec2', 'pattern_exclude');
|
||||
pattern_exclude = config.get('ec2', 'pattern_exclude')
|
||||
if pattern_exclude and len(pattern_exclude) > 0:
|
||||
self.pattern_exclude = re.compile(pattern_exclude)
|
||||
else:
|
||||
@@ -447,6 +469,12 @@ class Ec2Inventory(object):
|
||||
except configparser.NoOptionError:
|
||||
self.pattern_exclude = None
|
||||
|
||||
# Do we want to stack multiple filters?
|
||||
if config.has_option('ec2', 'stack_filters'):
|
||||
self.stack_filters = config.getboolean('ec2', 'stack_filters')
|
||||
else:
|
||||
self.stack_filters = False
|
||||
|
||||
# Instance filters (see boto and EC2 API docs). Ignore invalid filters.
|
||||
self.ec2_instance_filters = defaultdict(list)
|
||||
if config.has_option('ec2', 'instance_filters'):
|
||||
@@ -534,8 +562,14 @@ class Ec2Inventory(object):
|
||||
conn = self.connect(region)
|
||||
reservations = []
|
||||
if self.ec2_instance_filters:
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
|
||||
if self.stack_filters:
|
||||
filters_dict = {}
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
filters_dict[filter_key] = filter_values
|
||||
reservations.extend(conn.get_all_instances(filters = filters_dict))
|
||||
else:
|
||||
for filter_key, filter_values in self.ec2_instance_filters.items():
|
||||
reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
|
||||
else:
|
||||
reservations = conn.get_all_instances()
|
||||
|
||||
@@ -555,6 +589,9 @@ class Ec2Inventory(object):
|
||||
for tag in tags:
|
||||
tags_by_instance_id[tag.res_id][tag.name] = tag.value
|
||||
|
||||
if (not self.aws_account_id) and reservations:
|
||||
self.aws_account_id = reservations[0].owner_id
|
||||
|
||||
for reservation in reservations:
|
||||
for instance in reservation.instances:
|
||||
instance.tags = tags_by_instance_id[instance.id]
|
||||
@@ -676,7 +713,7 @@ class Ec2Inventory(object):
|
||||
|
||||
try:
|
||||
# Boto also doesn't provide wrapper classes to CacheClusters or
|
||||
# CacheNodes. Because of that wo can't make use of the get_list
|
||||
# CacheNodes. Because of that we can't make use of the get_list
|
||||
# method in the AWSQueryConnection. Let's do the work manually
|
||||
clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters']
|
||||
|
||||
@@ -710,7 +747,7 @@ class Ec2Inventory(object):
|
||||
|
||||
try:
|
||||
# Boto also doesn't provide wrapper classes to ReplicationGroups
|
||||
# Because of that wo can't make use of the get_list method in the
|
||||
# Because of that we can't make use of the get_list method in the
|
||||
# AWSQueryConnection. Let's do the work manually
|
||||
replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups']
|
||||
|
||||
@@ -786,9 +823,19 @@ class Ec2Inventory(object):
|
||||
else:
|
||||
hostname = getattr(instance, self.hostname_variable)
|
||||
|
||||
# set the hostname from route53
|
||||
if self.route53_enabled and self.route53_hostnames:
|
||||
route53_names = self.get_instance_route53_names(instance)
|
||||
for name in route53_names:
|
||||
if name.endswith(self.route53_hostnames):
|
||||
hostname = name
|
||||
|
||||
# If we can't get a nice hostname, use the destination address
|
||||
if not hostname:
|
||||
hostname = dest
|
||||
# to_safe strips hostname characters like dots, so don't strip route53 hostnames
|
||||
elif self.route53_enabled and self.route53_hostnames and hostname.endswith(self.route53_hostnames):
|
||||
hostname = hostname.lower()
|
||||
else:
|
||||
hostname = self.to_safe(hostname).lower()
|
||||
|
||||
@@ -837,6 +884,13 @@ class Ec2Inventory(object):
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'types', type_name)
|
||||
|
||||
# Inventory: Group by instance state
|
||||
if self.group_by_instance_state:
|
||||
state_name = self.to_safe('instance_state_' + instance.state)
|
||||
self.push(self.inventory, state_name, hostname)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'instance_states', state_name)
|
||||
|
||||
# Inventory: Group by key pair
|
||||
if self.group_by_key_pair and instance.key_name:
|
||||
key_name = self.to_safe('key_' + instance.key_name)
|
||||
@@ -863,6 +917,12 @@ class Ec2Inventory(object):
|
||||
self.fail_with_error('\n'.join(['Package boto seems a bit older.',
|
||||
'Please upgrade boto >= 2.3.0.']))
|
||||
|
||||
# Inventory: Group by AWS account ID
|
||||
if self.group_by_aws_account:
|
||||
self.push(self.inventory, self.aws_account_id, dest)
|
||||
if self.nested_groups:
|
||||
self.push_group(self.inventory, 'accounts', self.aws_account_id)
|
||||
|
||||
# Inventory: Group by tag keys
|
||||
if self.group_by_tag_keys:
|
||||
for k, v in instance.tags.items():
|
||||
@@ -1194,13 +1254,14 @@ class Ec2Inventory(object):
|
||||
if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available':
|
||||
return
|
||||
|
||||
# Skip clusters we cannot address (e.g. private VPC subnet or clustered redis)
|
||||
if replication_group['NodeGroups'][0]['PrimaryEndpoint'] is None or \
|
||||
replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] is None:
|
||||
return
|
||||
|
||||
# Select the best destination address (PrimaryEndpoint)
|
||||
dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address']
|
||||
|
||||
if not dest:
|
||||
# Skip clusters we cannot address (e.g. private VPC subnet)
|
||||
return
|
||||
|
||||
# Add to index
|
||||
self.index[dest] = [region, replication_group['ReplicationGroupId']]
|
||||
|
||||
@@ -1243,7 +1304,10 @@ class Ec2Inventory(object):
|
||||
''' Get and store the map of resource records to domain names that
|
||||
point to them. '''
|
||||
|
||||
r53_conn = route53.Route53Connection()
|
||||
if self.boto_profile:
|
||||
r53_conn = route53.Route53Connection(profile_name=self.boto_profile)
|
||||
else:
|
||||
r53_conn = route53.Route53Connection()
|
||||
all_zones = r53_conn.get_zones()
|
||||
|
||||
route53_zones = [ zone for zone in all_zones if zone.name[:-1]
|
||||
@@ -1304,7 +1368,7 @@ class Ec2Inventory(object):
|
||||
instance_vars[key] = value
|
||||
elif isinstance(value, six.string_types):
|
||||
instance_vars[key] = value.strip()
|
||||
elif type(value) == type(None):
|
||||
elif value is None:
|
||||
instance_vars[key] = ''
|
||||
elif key == 'ec2_region':
|
||||
instance_vars[key] = value.name
|
||||
@@ -1335,6 +1399,8 @@ class Ec2Inventory(object):
|
||||
#print type(value)
|
||||
#print value
|
||||
|
||||
instance_vars[self.to_safe('ec2_account_id')] = self.aws_account_id
|
||||
|
||||
return instance_vars
|
||||
|
||||
def get_host_info_dict_from_describe_dict(self, describe_dict):
|
||||
@@ -1413,7 +1479,7 @@ class Ec2Inventory(object):
|
||||
|
||||
# Target: Everything
|
||||
# Replace None by an empty string
|
||||
elif type(value) == type(None):
|
||||
elif value is None:
|
||||
host_info[key] = ''
|
||||
|
||||
else:
|
||||
@@ -1464,26 +1530,22 @@ class Ec2Inventory(object):
|
||||
''' Reads the inventory from the cache file and returns it as a JSON
|
||||
object '''
|
||||
|
||||
cache = open(self.cache_path_cache, 'r')
|
||||
json_inventory = cache.read()
|
||||
return json_inventory
|
||||
|
||||
with open(self.cache_path_cache, 'r') as f:
|
||||
json_inventory = f.read()
|
||||
return json_inventory
|
||||
|
||||
def load_index_from_cache(self):
|
||||
''' Reads the index from the cache file sets self.index '''
|
||||
|
||||
cache = open(self.cache_path_index, 'r')
|
||||
json_index = cache.read()
|
||||
self.index = json.loads(json_index)
|
||||
|
||||
with open(self.cache_path_index, 'rb') as f:
|
||||
self.index = json.load(f)
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
''' Writes data in JSON format to a file '''
|
||||
|
||||
json_data = self.json_format_dict(data, True)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
with open(filename, 'w') as f:
|
||||
f.write(json_data)
|
||||
|
||||
def uncammelize(self, key):
|
||||
temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
|
||||
@@ -1506,5 +1568,6 @@ class Ec2Inventory(object):
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
# Run the script
|
||||
Ec2Inventory()
|
||||
if __name__ == '__main__':
|
||||
# Run the script
|
||||
Ec2Inventory()
|
||||
|
||||
@@ -1,3 +1,107 @@
|
||||
# Foreman inventory (https://github.com/theforeman/foreman_ansible_inventory)
|
||||
#
|
||||
# This script can be used as an Ansible dynamic inventory.
|
||||
# The connection parameters are set up via *foreman.ini*
|
||||
# This is how the script founds the configuration file in
|
||||
# order of discovery.
|
||||
#
|
||||
# * `/etc/ansible/foreman.ini`
|
||||
# * Current directory of your inventory script.
|
||||
# * `FOREMAN_INI_PATH` environment variable.
|
||||
#
|
||||
# ## Variables and Parameters
|
||||
#
|
||||
# The data returned from Foreman for each host is stored in a foreman
|
||||
# hash so they're available as *host_vars* along with the parameters
|
||||
# of the host and it's hostgroups:
|
||||
#
|
||||
# "foo.example.com": {
|
||||
# "foreman": {
|
||||
# "architecture_id": 1,
|
||||
# "architecture_name": "x86_64",
|
||||
# "build": false,
|
||||
# "build_status": 0,
|
||||
# "build_status_label": "Installed",
|
||||
# "capabilities": [
|
||||
# "build",
|
||||
# "image"
|
||||
# ],
|
||||
# "compute_profile_id": 4,
|
||||
# "hostgroup_name": "webtier/myapp",
|
||||
# "id": 70,
|
||||
# "image_name": "debian8.1",
|
||||
# ...
|
||||
# "uuid": "50197c10-5ebb-b5cf-b384-a1e203e19e77"
|
||||
# },
|
||||
# "foreman_params": {
|
||||
# "testparam1": "foobar",
|
||||
# "testparam2": "small",
|
||||
# ...
|
||||
# }
|
||||
#
|
||||
# and could therefore be used in Ansible like:
|
||||
#
|
||||
# - debug: msg="From Foreman host {{ foreman['uuid'] }}"
|
||||
#
|
||||
# Which yields
|
||||
#
|
||||
# TASK [test_foreman : debug] ****************************************************
|
||||
# ok: [foo.example.com] => {
|
||||
# "msg": "From Foreman host 50190bd1-052a-a34a-3c9c-df37a39550bf"
|
||||
# }
|
||||
#
|
||||
# ## Automatic Ansible groups
|
||||
#
|
||||
# The inventory will provide a set of groups, by default prefixed by
|
||||
# 'foreman_'. If you want to customize this prefix, change the
|
||||
# group_prefix option in /etc/ansible/foreman.ini. The rest of this
|
||||
# guide will assume the default prefix of 'foreman'
|
||||
#
|
||||
# The hostgroup, location, organization, content view, and lifecycle
|
||||
# environment of each host are created as Ansible groups with a
|
||||
# foreman_<grouptype> prefix, all lowercase and problematic parameters
|
||||
# removed. So e.g. the foreman hostgroup
|
||||
#
|
||||
# myapp / webtier / datacenter1
|
||||
#
|
||||
# would turn into the Ansible group:
|
||||
#
|
||||
# foreman_hostgroup_myapp_webtier_datacenter1
|
||||
#
|
||||
# Furthermore Ansible groups can be created on the fly using the
|
||||
# *group_patterns* variable in *foreman.ini* so that you can build up
|
||||
# hierarchies using parameters on the hostgroup and host variables.
|
||||
#
|
||||
# Lets assume you have a host that is built using this nested hostgroup:
|
||||
#
|
||||
# myapp / webtier / datacenter1
|
||||
#
|
||||
# and each of the hostgroups defines a parameters respectively:
|
||||
#
|
||||
# myapp: app_param = myapp
|
||||
# webtier: tier_param = webtier
|
||||
# datacenter1: dc_param = datacenter1
|
||||
#
|
||||
# The host is also in a subnet called "mysubnet" and provisioned via an image
|
||||
# then *group_patterns* like:
|
||||
#
|
||||
# [ansible]
|
||||
# group_patterns = ["{app_param}-{tier_param}-{dc_param}",
|
||||
# "{app_param}-{tier_param}",
|
||||
# "{app_param}",
|
||||
# "{subnet_name}-{provision_method}"]
|
||||
#
|
||||
# would put the host into the additional Ansible groups:
|
||||
#
|
||||
# - myapp-webtier-datacenter1
|
||||
# - myapp-webtier
|
||||
# - myapp
|
||||
# - mysubnet-image
|
||||
#
|
||||
# by recursively resolving the hostgroups, getting the parameter keys
|
||||
# and values and doing a Python *string.format()* like replacement on
|
||||
# it.
|
||||
#
|
||||
[foreman]
|
||||
url = http://localhost:3000/
|
||||
user = foreman
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: set fileencoding=utf-8 :
|
||||
#
|
||||
# Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>
|
||||
# Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>,
|
||||
# Daniel Lobato Garcia <dlobatog@redhat.com>
|
||||
#
|
||||
# This script is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,106 +19,62 @@
|
||||
#
|
||||
# This is somewhat based on cobbler inventory
|
||||
|
||||
# Stdlib imports
|
||||
# __future__ imports must occur at the beginning of file
|
||||
from __future__ import print_function
|
||||
|
||||
try:
|
||||
# Python 2 version
|
||||
import ConfigParser
|
||||
except ImportError:
|
||||
# Python 3 version
|
||||
import configparser as ConfigParser
|
||||
import json
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
import sys
|
||||
from time import time
|
||||
from collections import defaultdict
|
||||
from distutils.version import LooseVersion, StrictVersion
|
||||
|
||||
try:
|
||||
import ConfigParser
|
||||
except ImportError:
|
||||
import configparser as ConfigParser
|
||||
# 3rd party imports
|
||||
import requests
|
||||
if LooseVersion(requests.__version__) < LooseVersion('1.1.0'):
|
||||
print('This script requires python-requests 1.1 as a minimum version')
|
||||
sys.exit(1)
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
def json_format_dict(data, pretty=False):
|
||||
"""Converts a dict to a JSON object and dumps it as a formatted string"""
|
||||
|
||||
if pretty:
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
class ForemanInventory(object):
|
||||
config_paths = [
|
||||
"/etc/ansible/foreman.ini",
|
||||
os.path.dirname(os.path.realpath(__file__)) + '/foreman.ini',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.inventory = dict() # A list of groups and the hosts in that group
|
||||
self.inventory = defaultdict(list) # A list of groups and the hosts in that group
|
||||
self.cache = dict() # Details about hosts in the inventory
|
||||
self.params = dict() # Params of each host
|
||||
self.facts = dict() # Facts of each host
|
||||
self.hostgroups = dict() # host groups
|
||||
self.session = None # Requests session
|
||||
|
||||
def run(self):
|
||||
if not self._read_settings():
|
||||
return False
|
||||
self._get_inventory()
|
||||
self._print_data()
|
||||
return True
|
||||
|
||||
def _read_settings(self):
|
||||
# Read settings and parse CLI arguments
|
||||
if not self.read_settings():
|
||||
return False
|
||||
self.parse_cli_args()
|
||||
return True
|
||||
|
||||
def _get_inventory(self):
|
||||
if self.args.refresh_cache:
|
||||
self.update_cache()
|
||||
elif not self.is_cache_valid():
|
||||
self.update_cache()
|
||||
else:
|
||||
self.load_inventory_from_cache()
|
||||
self.load_params_from_cache()
|
||||
self.load_facts_from_cache()
|
||||
self.load_cache_from_cache()
|
||||
|
||||
def _print_data(self):
|
||||
data_to_print = ""
|
||||
if self.args.host:
|
||||
data_to_print += self.get_host_info()
|
||||
else:
|
||||
self.inventory['_meta'] = {'hostvars': {}}
|
||||
for hostname in self.cache:
|
||||
self.inventory['_meta']['hostvars'][hostname] = {
|
||||
'foreman': self.cache[hostname],
|
||||
'foreman_params': self.params[hostname],
|
||||
}
|
||||
if self.want_facts:
|
||||
self.inventory['_meta']['hostvars'][hostname]['foreman_facts'] = self.facts[hostname]
|
||||
|
||||
data_to_print += self.json_format_dict(self.inventory, True)
|
||||
|
||||
print(data_to_print)
|
||||
|
||||
def is_cache_valid(self):
|
||||
"""Determines if the cache is still valid"""
|
||||
if os.path.isfile(self.cache_path_cache):
|
||||
mod_time = os.path.getmtime(self.cache_path_cache)
|
||||
current_time = time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
if (os.path.isfile(self.cache_path_inventory) and
|
||||
os.path.isfile(self.cache_path_params) and
|
||||
os.path.isfile(self.cache_path_facts)):
|
||||
return True
|
||||
return False
|
||||
self.config_paths = [
|
||||
"/etc/ansible/foreman.ini",
|
||||
os.path.dirname(os.path.realpath(__file__)) + '/foreman.ini',
|
||||
]
|
||||
env_value = os.environ.get('FOREMAN_INI_PATH')
|
||||
if env_value is not None:
|
||||
self.config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))
|
||||
|
||||
def read_settings(self):
|
||||
"""Reads the settings from the foreman.ini file"""
|
||||
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
env_value = os.environ.get('FOREMAN_INI_PATH')
|
||||
if env_value is not None:
|
||||
self.config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))
|
||||
|
||||
config.read(self.config_paths)
|
||||
|
||||
# Foreman API related
|
||||
@@ -136,7 +93,7 @@ class ForemanInventory(object):
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
|
||||
group_patterns = "[]"
|
||||
|
||||
self.group_patterns = eval(group_patterns)
|
||||
self.group_patterns = json.loads(group_patterns)
|
||||
|
||||
try:
|
||||
self.group_prefix = config.get('ansible', 'group_prefix')
|
||||
@@ -212,12 +169,6 @@ class ForemanInventory(object):
|
||||
def _get_hosts(self):
|
||||
return self._get_json("%s/api/v2/hosts" % self.foreman_url)
|
||||
|
||||
def _get_hostgroup_by_id(self, hid):
|
||||
if hid not in self.hostgroups:
|
||||
url = "%s/api/v2/hostgroups/%s" % (self.foreman_url, hid)
|
||||
self.hostgroups[hid] = self._get_json(url)
|
||||
return self.hostgroups[hid]
|
||||
|
||||
def _get_all_params_by_id(self, hid):
|
||||
url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid)
|
||||
ret = self._get_json(url, [404])
|
||||
@@ -225,10 +176,6 @@ class ForemanInventory(object):
|
||||
ret = {}
|
||||
return ret.get('all_parameters', {})
|
||||
|
||||
def _get_facts_by_id(self, hid):
|
||||
url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
|
||||
return self._get_json(url)
|
||||
|
||||
def _resolve_params(self, host):
|
||||
"""Fetch host params and convert to dict"""
|
||||
params = {}
|
||||
@@ -239,6 +186,10 @@ class ForemanInventory(object):
|
||||
|
||||
return params
|
||||
|
||||
def _get_facts_by_id(self, hid):
|
||||
url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
|
||||
return self._get_json(url)
|
||||
|
||||
def _get_facts(self, host):
|
||||
"""Fetch all host facts of the host"""
|
||||
if not self.want_facts:
|
||||
@@ -253,6 +204,29 @@ class ForemanInventory(object):
|
||||
raise ValueError("More than one set of facts returned for '%s'" % host)
|
||||
return facts
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
"""Write data in JSON format to a file"""
|
||||
json_data = json_format_dict(data, True)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
def _write_cache(self):
|
||||
self.write_to_cache(self.cache, self.cache_path_cache)
|
||||
self.write_to_cache(self.inventory, self.cache_path_inventory)
|
||||
self.write_to_cache(self.params, self.cache_path_params)
|
||||
self.write_to_cache(self.facts, self.cache_path_facts)
|
||||
|
||||
def to_safe(self, word):
|
||||
'''Converts 'bad' characters in a string to underscores
|
||||
so they can be used as Ansible groups
|
||||
|
||||
>>> ForemanInventory.to_safe("foo-bar baz")
|
||||
'foo_barbaz'
|
||||
'''
|
||||
regex = "[^A-Za-z0-9\_]"
|
||||
return re.sub(regex, "_", word.replace(" ", ""))
|
||||
|
||||
def update_cache(self):
|
||||
"""Make calls to foreman and save the output in a cache"""
|
||||
|
||||
@@ -267,20 +241,20 @@ class ForemanInventory(object):
|
||||
val = host.get('%s_title' % group) or host.get('%s_name' % group)
|
||||
if val:
|
||||
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
|
||||
self.push(self.inventory, safe_key, dns_name)
|
||||
self.inventory[safe_key].append(dns_name)
|
||||
|
||||
# Create ansible groups for environment, location and organization
|
||||
for group in ['environment', 'location', 'organization']:
|
||||
val = host.get('%s_name' % group)
|
||||
if val:
|
||||
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
|
||||
self.push(self.inventory, safe_key, dns_name)
|
||||
self.inventory[safe_key].append(dns_name)
|
||||
|
||||
for group in ['lifecycle_environment', 'content_view']:
|
||||
val = host.get('content_facet_attributes', {}).get('%s_name' % group)
|
||||
if val:
|
||||
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
|
||||
self.push(self.inventory, safe_key, dns_name)
|
||||
self.inventory[safe_key].append(dns_name)
|
||||
|
||||
params = self._resolve_params(host)
|
||||
|
||||
@@ -297,44 +271,27 @@ class ForemanInventory(object):
|
||||
for pattern in self.group_patterns:
|
||||
try:
|
||||
key = pattern.format(**groupby)
|
||||
self.push(self.inventory, key, dns_name)
|
||||
self.inventory[key].append(dns_name)
|
||||
except KeyError:
|
||||
pass # Host not part of this group
|
||||
|
||||
self.cache[dns_name] = host
|
||||
self.params[dns_name] = params
|
||||
self.facts[dns_name] = self._get_facts(host)
|
||||
self.push(self.inventory, 'all', dns_name)
|
||||
self.inventory['all'].append(dns_name)
|
||||
self._write_cache()
|
||||
|
||||
def _write_cache(self):
|
||||
self.write_to_cache(self.cache, self.cache_path_cache)
|
||||
self.write_to_cache(self.inventory, self.cache_path_inventory)
|
||||
self.write_to_cache(self.params, self.cache_path_params)
|
||||
self.write_to_cache(self.facts, self.cache_path_facts)
|
||||
|
||||
def get_host_info(self):
|
||||
"""Get variables about a specific host"""
|
||||
|
||||
if not self.cache or len(self.cache) == 0:
|
||||
# Need to load index from cache
|
||||
self.load_cache_from_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
# try updating the cache
|
||||
self.update_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
return self.json_format_dict(self.cache[self.args.host], True)
|
||||
|
||||
def push(self, d, k, v):
|
||||
if k in d:
|
||||
d[k].append(v)
|
||||
else:
|
||||
d[k] = [v]
|
||||
def is_cache_valid(self):
|
||||
"""Determines if the cache is still valid"""
|
||||
if os.path.isfile(self.cache_path_cache):
|
||||
mod_time = os.path.getmtime(self.cache_path_cache)
|
||||
current_time = time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
if (os.path.isfile(self.cache_path_inventory) and
|
||||
os.path.isfile(self.cache_path_params) and
|
||||
os.path.isfile(self.cache_path_facts)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def load_inventory_from_cache(self):
|
||||
"""Read the index from the cache file sets self.index"""
|
||||
@@ -365,33 +322,58 @@ class ForemanInventory(object):
|
||||
json_cache = cache.read()
|
||||
self.cache = json.loads(json_cache)
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
"""Write data in JSON format to a file"""
|
||||
json_data = self.json_format_dict(data, True)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
@staticmethod
|
||||
def to_safe(word):
|
||||
'''Converts 'bad' characters in a string to underscores
|
||||
|
||||
so they can be used as Ansible groups
|
||||
|
||||
>>> ForemanInventory.to_safe("foo-bar baz")
|
||||
'foo_barbaz'
|
||||
'''
|
||||
regex = "[^A-Za-z0-9\_]"
|
||||
return re.sub(regex, "_", word.replace(" ", ""))
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
"""Converts a dict to a JSON object and dumps it as a formatted string"""
|
||||
|
||||
if pretty:
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
def get_inventory(self):
|
||||
if self.args.refresh_cache or not self.is_cache_valid():
|
||||
self.update_cache()
|
||||
else:
|
||||
return json.dumps(data)
|
||||
self.load_inventory_from_cache()
|
||||
self.load_params_from_cache()
|
||||
self.load_facts_from_cache()
|
||||
self.load_cache_from_cache()
|
||||
|
||||
def get_host_info(self):
|
||||
"""Get variables about a specific host"""
|
||||
|
||||
if not self.cache or len(self.cache) == 0:
|
||||
# Need to load index from cache
|
||||
self.load_cache_from_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
# try updating the cache
|
||||
self.update_cache()
|
||||
|
||||
if self.args.host not in self.cache:
|
||||
# host might not exist anymore
|
||||
return json_format_dict({}, True)
|
||||
|
||||
return json_format_dict(self.cache[self.args.host], True)
|
||||
|
||||
def _print_data(self):
|
||||
data_to_print = ""
|
||||
if self.args.host:
|
||||
data_to_print += self.get_host_info()
|
||||
else:
|
||||
self.inventory['_meta'] = {'hostvars': {}}
|
||||
for hostname in self.cache:
|
||||
self.inventory['_meta']['hostvars'][hostname] = {
|
||||
'foreman': self.cache[hostname],
|
||||
'foreman_params': self.params[hostname],
|
||||
}
|
||||
if self.want_facts:
|
||||
self.inventory['_meta']['hostvars'][hostname]['foreman_facts'] = self.facts[hostname]
|
||||
|
||||
data_to_print += json_format_dict(self.inventory, True)
|
||||
|
||||
print(data_to_print)
|
||||
|
||||
def run(self):
|
||||
# Read settings and parse CLI arguments
|
||||
if not self.read_settings():
|
||||
return False
|
||||
self.parse_cli_args()
|
||||
self.get_inventory()
|
||||
self._print_data()
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
inv = ForemanInventory()
|
||||
sys.exit(not inv.run())
|
||||
sys.exit(not ForemanInventory().run())
|
||||
|
||||
@@ -312,7 +312,7 @@ class GceInventory(object):
|
||||
return gce
|
||||
|
||||
def parse_env_zones(self):
|
||||
'''returns a list of comma seperated zones parsed from the GCE_ZONE environment variable.
|
||||
'''returns a list of comma separated zones parsed from the GCE_ZONE environment variable.
|
||||
If provided, this will be used to filter the results of the grouped_instances call'''
|
||||
import csv
|
||||
reader = csv.reader([os.environ.get('GCE_ZONE',"")], skipinitialspace=True)
|
||||
@@ -323,7 +323,7 @@ class GceInventory(object):
|
||||
''' Command line argument processing '''
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce an Ansible Inventory file based on GCE')
|
||||
description='Produce an Ansible Inventory file based on GCE')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store',
|
||||
@@ -428,8 +428,10 @@ class GceInventory(object):
|
||||
if zones and zone not in zones:
|
||||
continue
|
||||
|
||||
if zone in groups: groups[zone].append(name)
|
||||
else: groups[zone] = [name]
|
||||
if zone in groups:
|
||||
groups[zone].append(name)
|
||||
else:
|
||||
groups[zone] = [name]
|
||||
|
||||
tags = node.extra['tags']
|
||||
for t in tags:
|
||||
@@ -437,26 +439,36 @@ class GceInventory(object):
|
||||
tag = t[6:]
|
||||
else:
|
||||
tag = 'tag_%s' % t
|
||||
if tag in groups: groups[tag].append(name)
|
||||
else: groups[tag] = [name]
|
||||
if tag in groups:
|
||||
groups[tag].append(name)
|
||||
else:
|
||||
groups[tag] = [name]
|
||||
|
||||
net = node.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||
net = 'network_%s' % net
|
||||
if net in groups: groups[net].append(name)
|
||||
else: groups[net] = [name]
|
||||
if net in groups:
|
||||
groups[net].append(name)
|
||||
else:
|
||||
groups[net] = [name]
|
||||
|
||||
machine_type = node.size
|
||||
if machine_type in groups: groups[machine_type].append(name)
|
||||
else: groups[machine_type] = [name]
|
||||
if machine_type in groups:
|
||||
groups[machine_type].append(name)
|
||||
else:
|
||||
groups[machine_type] = [name]
|
||||
|
||||
image = node.image and node.image or 'persistent_disk'
|
||||
if image in groups: groups[image].append(name)
|
||||
else: groups[image] = [name]
|
||||
if image in groups:
|
||||
groups[image].append(name)
|
||||
else:
|
||||
groups[image] = [name]
|
||||
|
||||
status = node.extra['status']
|
||||
stat = 'status_%s' % status.lower()
|
||||
if stat in groups: groups[stat].append(name)
|
||||
else: groups[stat] = [name]
|
||||
if stat in groups:
|
||||
groups[stat].append(name)
|
||||
else:
|
||||
groups[stat] = [name]
|
||||
|
||||
groups["_meta"] = meta
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ clouds:
|
||||
password: stack
|
||||
project_name: stack
|
||||
ansible:
|
||||
use_hostnames: False
|
||||
expand_hostvars: True
|
||||
use_hostnames: True
|
||||
expand_hostvars: False
|
||||
fail_on_errors: True
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (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.
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
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 variables similar to:
|
||||
rax_os-ext-sts_task_state
|
||||
rax_addresses
|
||||
rax_links
|
||||
rax_image
|
||||
rax_os-ext-sts_vm_state
|
||||
rax_flavor
|
||||
rax_id
|
||||
rax_rax-bandwidth_bandwidth
|
||||
rax_user_id
|
||||
rax_os-dcf_diskconfig
|
||||
rax_accessipv4
|
||||
rax_accessipv6
|
||||
rax_progress
|
||||
rax_os-ext-sts_power_state
|
||||
rax_metadata
|
||||
rax_status
|
||||
rax_updated
|
||||
rax_hostid
|
||||
rax_name
|
||||
rax_created
|
||||
rax_tenant_id
|
||||
rax_loaded
|
||||
|
||||
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 six import iteritems
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
from pyrax.utils import slugify
|
||||
except ImportError:
|
||||
sys.exit('pyrax is required for this module')
|
||||
|
||||
from time import time
|
||||
|
||||
from ansible.constants import get_config, mk_boolean
|
||||
|
||||
|
||||
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):
|
||||
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 = {}
|
||||
|
||||
for region in regions:
|
||||
# Connect to the region
|
||||
cs = pyrax.connect_to_cloudservers(region=region)
|
||||
for server in cs.servers.list():
|
||||
if server.name == hostname:
|
||||
for key, value in to_dict(server).items():
|
||||
hostvars[key] = value
|
||||
|
||||
# And finally, add an IP address
|
||||
hostvars['ansible_ssh_host'] = server.accessIPv4
|
||||
print(json.dumps(hostvars, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
def _list_into_cache(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')
|
||||
|
||||
try:
|
||||
# Ansible 2.3+
|
||||
networks = get_config(p, 'rax', 'access_network',
|
||||
'RAX_ACCESS_NETWORK', 'public', value_type='list')
|
||||
except TypeError:
|
||||
# Ansible 2.2.x and below
|
||||
networks = get_config(p, 'rax', 'access_network',
|
||||
'RAX_ACCESS_NETWORK', 'public', islist=True)
|
||||
try:
|
||||
try:
|
||||
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
|
||||
'RAX_ACCESS_IP_VERSION', 4, value_type='list'))
|
||||
except TypeError:
|
||||
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 None. Is this a valid region?' % region,
|
||||
RuntimeWarning)
|
||||
continue
|
||||
for server in cs.servers.list():
|
||||
# Create a group on region
|
||||
groups[region].append(server.name)
|
||||
|
||||
# Check if group metadata key in servers' metadata
|
||||
group = server.metadata.get('group')
|
||||
if group:
|
||||
groups[group].append(server.name)
|
||||
|
||||
for extra_group in server.metadata.get('groups', '').split(','):
|
||||
if extra_group:
|
||||
groups[extra_group].append(server.name)
|
||||
|
||||
# Add host metadata
|
||||
for key, value in to_dict(server).items():
|
||||
hostvars[server.name][key] = value
|
||||
|
||||
hostvars[server.name]['rax_region'] = region
|
||||
|
||||
for key, value in iteritems(server.metadata):
|
||||
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)
|
||||
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
|
||||
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}
|
||||
|
||||
with open(get_cache_file_path(regions), 'w') as cache_file:
|
||||
json.dump(groups, cache_file)
|
||||
|
||||
|
||||
def get_cache_file_path(regions):
|
||||
regions_str = '.'.join([reg.strip().lower() for reg in regions])
|
||||
ansible_tmp_path = os.path.join(os.path.expanduser("~"), '.ansible', 'tmp')
|
||||
if not os.path.exists(ansible_tmp_path):
|
||||
os.makedirs(ansible_tmp_path)
|
||||
return os.path.join(ansible_tmp_path,
|
||||
'ansible-rax-%s-%s.cache' % (
|
||||
pyrax.identity.username, regions_str))
|
||||
|
||||
|
||||
def _list(regions, refresh_cache=True):
|
||||
cache_max_age = int(get_config(p, 'rax', 'cache_max_age',
|
||||
'RAX_CACHE_MAX_AGE', 600))
|
||||
|
||||
if (not os.path.exists(get_cache_file_path(regions)) or
|
||||
refresh_cache or
|
||||
(time() - os.stat(get_cache_file_path(regions))[-1]) > cache_max_age):
|
||||
# Cache file doesn't exist or older than 10m or refresh cache requested
|
||||
_list_into_cache(regions)
|
||||
|
||||
with open(get_cache_file_path(regions), 'r') as cache_file:
|
||||
groups = json.load(cache_file)
|
||||
print(json.dumps(groups, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Ansible Rackspace Cloud '
|
||||
'inventory module')
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('--list', action='store_true',
|
||||
help='List active servers')
|
||||
group.add_argument('--host', help='List details about the specific host')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help=('Force refresh of cache, making API requests to'
|
||||
'RackSpace (default: False - use cache files)'))
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def setup():
|
||||
default_creds_file = os.path.expanduser('~/.rackspace_cloud_credentials')
|
||||
|
||||
# pyrax does not honor the environment variable CLOUD_VERIFY_SSL=False, so let's help pyrax
|
||||
if 'CLOUD_VERIFY_SSL' in os.environ:
|
||||
pyrax.set_setting('verify_ssl', os.environ['CLOUD_VERIFY_SSL'] in [1, 'true', 'True'])
|
||||
|
||||
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
|
||||
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):
|
||||
creds_file = default_creds_file
|
||||
elif not keyring_username:
|
||||
sys.exit('No value in environment variable %s and/or no '
|
||||
'credentials file at %s'
|
||||
% ('RAX_CREDS_FILE', default_creds_file))
|
||||
|
||||
identity_type = pyrax.get_setting('identity_type')
|
||||
pyrax.set_setting('identity_type', identity_type or 'rackspace')
|
||||
|
||||
region = pyrax.get_setting('region')
|
||||
|
||||
try:
|
||||
if keyring_username:
|
||||
pyrax.keyring_auth(keyring_username, region=region)
|
||||
else:
|
||||
pyrax.set_credential_file(creds_file, region=region)
|
||||
except Exception as e:
|
||||
sys.exit("%s: %s" % (e, e.message))
|
||||
|
||||
regions = []
|
||||
if region:
|
||||
regions.append(region)
|
||||
else:
|
||||
try:
|
||||
# Ansible 2.3+
|
||||
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
|
||||
value_type='list')
|
||||
except TypeError:
|
||||
# Ansible 2.2.x and below
|
||||
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
|
||||
break
|
||||
elif region not in pyrax.regions:
|
||||
sys.exit('Unsupported region %s' % region)
|
||||
elif region not in regions:
|
||||
regions.append(region)
|
||||
|
||||
return regions
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
regions = setup()
|
||||
if args.list:
|
||||
_list(regions, refresh_cache=args.refresh_cache)
|
||||
elif args.host:
|
||||
host(regions, args.host)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -374,7 +374,7 @@ class VMWareInventory(object):
|
||||
if cfm is not None and cfm.field:
|
||||
for f in cfm.field:
|
||||
if f.managedObjectType == vim.VirtualMachine:
|
||||
self.custom_fields[f.key] = f.name;
|
||||
self.custom_fields[f.key] = f.name
|
||||
self.debugl('%d custom fieds collected' % len(self.custom_fields))
|
||||
return instance_tuples
|
||||
|
||||
@@ -628,7 +628,10 @@ class VMWareInventory(object):
|
||||
elif type(vobj) in self.vimTable:
|
||||
rdata = {}
|
||||
for key in self.vimTable[type(vobj)]:
|
||||
rdata[key] = getattr(vobj, key)
|
||||
try:
|
||||
rdata[key] = getattr(vobj, key)
|
||||
except Exception as e:
|
||||
self.debugl(e)
|
||||
|
||||
elif issubclass(type(vobj), str) or isinstance(vobj, str):
|
||||
if vobj.isalnum():
|
||||
@@ -685,12 +688,15 @@ class VMWareInventory(object):
|
||||
if self.lowerkeys:
|
||||
method = method.lower()
|
||||
if level + 1 <= self.maxlevel:
|
||||
rdata[method] = self._process_object_types(
|
||||
methodToCall,
|
||||
thisvm=thisvm,
|
||||
inkey=inkey + '.' + method,
|
||||
level=(level + 1)
|
||||
)
|
||||
try:
|
||||
rdata[method] = self._process_object_types(
|
||||
methodToCall,
|
||||
thisvm=thisvm,
|
||||
inkey=inkey + '.' + method,
|
||||
level=(level + 1)
|
||||
)
|
||||
except vim.fault.NoPermission:
|
||||
self.debugl("Skipping method %s (NoPermission)" % method)
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -719,5 +725,3 @@ class VMWareInventory(object):
|
||||
if __name__ == "__main__":
|
||||
# Run the script
|
||||
print(VMWareInventory().show())
|
||||
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ class AzureInventory(object):
|
||||
def push(self, my_dict, key, element):
|
||||
"""Pushed an element onto an array that may not have been defined in the dict."""
|
||||
if key in my_dict:
|
||||
my_dict[key].append(element);
|
||||
my_dict[key].append(element)
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user