diff --git a/lib/ansible/module_utils/openstack.py b/lib/ansible/module_utils/openstack.py
index 64f9543714..5c4503f94c 100644
--- a/lib/ansible/module_utils/openstack.py
+++ b/lib/ansible/module_utils/openstack.py
@@ -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
diff --git a/lib/ansible/utils/module_docs_fragments/openstack.py b/lib/ansible/utils/module_docs_fragments/openstack.py
new file mode 100644
index 0000000000..d740bc719c
--- /dev/null
+++ b/lib/ansible/utils/module_docs_fragments/openstack.py
@@ -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 .
+
+
+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.
+'''
diff --git a/plugins/inventory/openstack.py b/plugins/inventory/openstack.py
new file mode 100755
index 0000000000..c49d3c1fc4
--- /dev/null
+++ b/plugins/inventory/openstack.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012, Marco Vito Moscaritolo
+# Copyright (c) 2013, Jesse Keating
+# 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 .
+
+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()