Merge pull request #9421 from emonty/features/new-openstack
Add support for new OpenStack modules
This commit is contained in:
commit
0912781357
3 changed files with 273 additions and 0 deletions
|
@ -30,6 +30,9 @@ import os
|
||||||
|
|
||||||
|
|
||||||
def openstack_argument_spec():
|
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.
|
# Consume standard OpenStack environment variables.
|
||||||
# This is mainly only useful for ad-hoc command line operation as
|
# This is mainly only useful for ad-hoc command line operation as
|
||||||
# in playbooks one would assume variables would be used appropriately
|
# 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'])
|
ret.append(interface_spec['addr'])
|
||||||
return ret
|
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
|
||||||
|
|
88
lib/ansible/utils/module_docs_fragments/openstack.py
Normal file
88
lib/ansible/utils/module_docs_fragments/openstack.py
Normal 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
145
plugins/inventory/openstack.py
Executable 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()
|
Loading…
Reference in a new issue