Merge pull request #9421 from emonty/features/new-openstack

Add support for new OpenStack modules
This commit is contained in:
Brian Coca 2015-02-12 10:26:12 -05:00
commit 0912781357
3 changed files with 273 additions and 0 deletions

View file

@ -30,6 +30,9 @@ import os
def openstack_argument_spec():
# DEPRECATED: This argument spec is only used for the deprecated old
# OpenStack modules. It turns out that modern OpenStack auth is WAY
# more complex than this.
# Consume standard OpenStack environment variables.
# This is mainly only useful for ad-hoc command line operation as
# in playbooks one would assume variables would be used appropriately
@ -67,3 +70,40 @@ def openstack_find_nova_addresses(addresses, ext_tag, key_name=None):
ret.append(interface_spec['addr'])
return ret
def openstack_full_argument_spec(**kwargs):
spec = dict(
cloud=dict(default=None),
auth_plugin=dict(default=None),
auth=dict(default=None),
auth_token=dict(default=None),
region_name=dict(default=None),
availability_zone=dict(default=None),
state=dict(default='present', choices=['absent', 'present']),
wait=dict(default=True, type='bool'),
timeout=dict(default=180, type='int'),
endpoint_type=dict(
default='publicURL', choices=['publicURL', 'internalURL']
)
)
spec.update(kwargs)
return spec
def openstack_module_kwargs(**kwargs):
ret = dict(
required_one_of=[
['cloud', 'auth'],
],
mutually_exclusive=[
['auth', 'auth_token'],
['auth_plugin', 'auth_token'],
],
)
for key in ('mutually_exclusive', 'required_together', 'required_one_of'):
if key in kwargs:
if key in ret:
ret[key].extend(kwargs[key])
else:
ret[key] = kwargs[key]
return ret

View file

@ -0,0 +1,88 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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/>.
class ModuleDocFragment(object):
# Standard openstack documentation fragment
DOCUMENTATION = '''
options:
cloud:
description:
- Named cloud to operate against. Provides default values for I(auth) and I(auth_plugin)
required: false
auth:
description:
- Dictionary containing auth information as needed by the cloud's auth
plugin strategy. For the default I{password) plugin, this would contain
I(auth_url), I(username), I(password), I(project_name) and any
information about domains if the cloud supports them. For other plugins,
this param will need to contain whatever parameters that auth plugin
requires. This parameter is not needed if a named cloud is provided.
required: false
auth_plugin:
description:
- Name of the auth plugin to use. If the cloud uses something other than
password authentication, the name of the plugin should be indicated here
and the contents of the I(auth) parameter should be updated accordingly.
required: false
default: password
auth_token:
description:
- An auth token obtained previously. If I(auth_token) is given,
I(auth) and I(auth_plugin) are not needed.
region_name:
description:
- Name of the region.
required: false
availability_zone:
description:
- Name of the availability zone.
required: false
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
wait:
description:
- Should ansible wait until the requested resource is complete.
required: false
default: "yes"
choices: ["yes", "no"]
timeout:
description:
- How long should ansible wait for the requested resource.
required: false
default: 180
endpoint_type:
description:
- Endpoint URL type to fetch from the service catalog.
choices: [publicURL, internalURL]
required: false
default: publicURL
requirements:
- shade
notes:
- The standard OpenStack environment variables, such as C(OS_USERNAME)
may be user instead of providing explicit values.
- Auth information is driven by os-client-config, which means that values
can come from a yaml config file in /etc/ansible/openstack.yaml,
/etc/openstack/clouds.yaml or ~/.config/openstack/clouds.yaml, then from
standard environment variables, then finally by explicit parameters in
plays.
'''

145
plugins/inventory/openstack.py Executable file
View file

@ -0,0 +1,145 @@
#!/usr/bin/env python
# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com>
# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com>
# Copyright (c) 2014, Hewlett-Packard Development Company, L.P.
#
# This module 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.
#
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
import argparse
import collections
import os
import sys
import time
try:
import json
except:
import simplejson as json
import os_client_config
import shade
class OpenStackInventory(object):
def __init__(self, private=False, refresh=False):
self.openstack_config = os_client_config.config.OpenStackConfig(
os_client_config.config.CONFIG_FILES.append(
'/etc/ansible/openstack.yml'),
private)
self.clouds = shade.openstack_clouds(self.openstack_config)
self.refresh = refresh
self.cache_max_age = self.openstack_config.get_cache_max_age()
cache_path = self.openstack_config.get_cache_path()
# Cache related
if not os.path.exists(cache_path):
os.makedirs(cache_path)
self.cache_file = os.path.join(cache_path, "ansible-inventory.cache")
def is_cache_stale(self):
''' Determines if cache file has expired, or if it is still valid '''
if os.path.isfile(self.cache_file):
mod_time = os.path.getmtime(self.cache_file)
current_time = time.time()
if (mod_time + self.cache_max_age) > current_time:
return False
return True
def get_host_groups(self):
if self.refresh or self.is_cache_stale():
groups = self.get_host_groups_from_cloud()
self.write_cache(groups)
else:
return json.load(open(self.cache_file, 'r'))
return groups
def write_cache(self, groups):
with open(self.cache_file, 'w') as cache_file:
cache_file.write(self.json_format_dict(groups))
def get_host_groups_from_cloud(self):
groups = collections.defaultdict(list)
hostvars = collections.defaultdict(dict)
for cloud in self.clouds:
# Cycle on servers
for server in cloud.list_servers():
meta = cloud.get_server_meta(server)
if 'interface_ip' not in meta['server_vars']:
# skip this host if it doesn't have a network address
continue
server_vars = meta['server_vars']
hostvars[server.name][
'ansible_ssh_host'] = server_vars['interface_ip']
hostvars[server.name]['openstack'] = server_vars
for group in meta['groups']:
groups[group].append(server.name)
if hostvars:
groups['_meta'] = {'hostvars': hostvars}
return groups
def json_format_dict(self, data):
return json.dumps(data, sort_keys=True, indent=2)
def list_instances(self):
groups = self.get_host_groups()
# Return server list
print(self.json_format_dict(groups))
def get_host(self, hostname):
groups = self.get_host_groups()
hostvars = groups['_meta']['hostvars']
if hostname in hostvars:
print(self.json_format_dict(hostvars[hostname]))
def parse_args():
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
parser.add_argument('--private',
action='store_true',
help='Use private address for ansible host')
parser.add_argument('--refresh', action='store_true',
help='Refresh cached information')
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')
return parser.parse_args()
def main():
args = parse_args()
try:
inventory = OpenStackInventory(args.private, args.refresh)
if args.list:
inventory.list_instances()
elif args.host:
inventory.get_host(args.host)
except shade.OpenStackCloudException as e:
print(e.message)
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()