diff --git a/library/rax b/library/rax
new file mode 100644
index 0000000000..2bdc163824
--- /dev/null
+++ b/library/rax
@@ -0,0 +1,265 @@
+#!/usr/bin/env python -tt
+# 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 .
+
+DOCUMENTATION = '''
+---
+module: rax
+short_description: create an instance in Rackspace Public Cloud, return instanceid
+description:
+ - creates Rackspace Public Cloud instances and optionally waits for it to be 'running'.
+version_added: "1.2"
+options:
+ service:
+ description:
+ - Cloud service to interact with
+ required: false
+ choices: ['cloudservers', 'cloudfiles', 'cloud_databases', 'cloud_loadbalancers']
+ default: cloudservers
+ state:
+ description:
+ - Indicate desired state of the resource
+ required: false
+ choices: ['present', 'active', 'absent', 'deleted']
+ default: present
+ creds_file:
+ description:
+ - File to find the Rackspace Public Cloud credentials in
+ required: false
+ default: null
+ name:
+ description:
+ - Name to give the instance
+ required: false
+ default: null
+ flavor:
+ description:
+ - flavor to use for the instance
+ required: false
+ default: null
+ image:
+ description:
+ - image to use for the instance
+ required: false
+ default: null
+ meta:
+ description:
+ - A hash of metadata to associate with the instance
+ default: null
+ key_name:
+ description:
+ - key pair to use on the instance
+ required: false
+ default: null
+ aliases: ['keypair']
+ files:
+ description:
+ - Files to insert into the instance. remotefilename:localcontent
+ default: null
+ region:
+ description:
+ - Region to create an instance in
+ required: false
+ default: null
+ wait:
+ description:
+ - wait for the instance to be in state 'running' before returning
+ required: false
+ default: "no"
+ choices: [ "yes", "no" ]
+ wait_timeout:
+ description:
+ - how long before wait gives up, in seconds
+ default: 300
+examples:
+ - code: 'local_action: rax creds_file=~/.raxpub service=cloudservers name=rax-test1 flavor=5 image=b11d9567-e412-4255-96b9-bd63ab23bcfe wait=yes state=present'
+ description: "Examples from Ansible Playbooks"
+requirements: [ "pyrax" ]
+author: Jesse Keating
+notes:
+ - Two environment variables can be used, RAX_CREDS and RAX_REGION.
+ - RAX_CREDS points to a credentials file appropriate for pyrax
+ - RAX_REGION defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
+'''
+
+import sys
+import time
+import os
+
+try:
+ import pyrax
+except ImportError:
+ print("failed=True msg='pyrax required for this module'")
+ sys.exit(1)
+
+SUPPORTEDSERVICES = ['cloudservers', 'cloudfiles', 'cloud_blockstorage',
+ 'cloud_databases', 'cloud_loadbalancers']
+
+def cloudservers(module, state, name, flavor, image, meta, key_name, files,
+ wait, wait_timeout):
+ # Check our args (this could be done better)
+ for arg in (state, name, flavor, image):
+ if not arg:
+ module.fail_json(msg='%s is required for cloudservers' % arg)
+
+ instances = []
+ changed = False
+ servers = []
+ # See if we can find servers that match our options
+ for server in pyrax.cloudservers.list():
+ if name != server.name:
+ continue
+ if flavor != server.flavor['id']:
+ continue
+ if image != server.image['id']:
+ continue
+ if meta != server.metadata:
+ continue
+ # Nothing else ruled us not a match, so consider it a winner
+ servers.append(server)
+
+ # act on the state
+ if state in ('active', 'present'):
+ if not servers:
+ # Handle the file contents
+ for rpath in files.keys():
+ lpath = os.path.expanduser(files[rpath])
+ try:
+ fileobj = open(lpath, 'r')
+ files[rpath] = fileobj
+ except Exception, e:
+ module.fail_json(msg = 'Failed to load %s' % lpath)
+ try:
+ servers = [pyrax.cloudservers.servers.create(name=name,
+ image=image,
+ flavor=flavor,
+ key_name=key_name,
+ meta=meta,
+ files=files)]
+ changed = True
+ except Exception, e:
+ module.fail_json(msg = '%s' % e.message)
+
+ for server in servers:
+ # wait here until the instances are up
+ wait_timeout = time.time() + wait_timeout
+ while wait and wait_timeout > time.time():
+ # refresh the server details
+ server.get()
+ if server.status in ('ACTIVE', 'ERROR'):
+ break
+ time.sleep(5)
+ if wait and wait_timeout <= time.time():
+ # waiting took too long
+ module.fail_json(msg = 'Timeout waiting on %s' % server.id)
+ # Get a fresh copy of the server details
+ server.get()
+ if server.status == 'ERROR':
+ module.fail_json(msg = '%s failed to build' % server.id)
+ instance = {'id': server.id,
+ 'accessIPv4': server.accessIPv4,
+ 'name': server.name,
+ 'status': server.status}
+ instances.append(instance)
+
+ elif state in ('absent', 'deleted'):
+ deleted = []
+ # See if we can find a server that matches our credentials
+ for server in servers:
+ if server.name == name:
+ if server.flavor['id'] == flavor and \
+ server.image['id'] == image and \
+ server.metadata == meta:
+ try:
+ server.delete()
+ deleted.append(server)
+ except Exception, e:
+ module.fail_json(msg = e.message)
+ instance = {'id': server.id,
+ 'accessIPv4': server.accessIPv4,
+ 'name': server.name,
+ 'status': 'DELETING'}
+ instances.append(instance)
+ changed = True
+
+ module.exit_json(changed=changed, instances=instances)
+
+def main():
+ module = AnsibleModule(
+ argument_spec = dict(
+ service = dict(default='cloudservers', choices=SUPPORTEDSERVICES),
+ state = dict(default='present', choices=['active', 'present',
+ 'deleted', 'absent']),
+ creds_file = dict(),
+ name = dict(),
+ flavor = dict(),
+ image = dict(),
+ meta = dict(type='dict', default={}),
+ key_name = dict(aliases = ['keypair']),
+ files = dict(type='dict', default={}),
+ region = dict(),
+ wait = dict(type='bool', choices=BOOLEANS),
+ wait_timeout = dict(default=300),
+ )
+ )
+
+ service = module.params.get('service')
+ state = module.params.get('state')
+ creds_file = module.params.get('creds_file')
+ name = module.params.get('name')
+ flavor = module.params.get('flavor')
+ image = module.params.get('image')
+ meta = module.params.get('meta')
+ key_name = module.params.get('key_name')
+ files = module.params.get('files')
+ region = module.params.get('region')
+ wait = module.params.get('wait')
+ wait_timeout = int(module.params.get('wait_timeout'))
+
+ # Setup the credentials file
+ if not creds_file:
+ try:
+ creds_file = os.environ['RAX_CREDS_FILE']
+ except KeyError, e:
+ module.fail_json(msg = 'Unable to load %s' % e.message)
+
+ # Define the region
+ if not region:
+ try:
+ region = os.environ['RAX_REGION']
+ except KeyError, e:
+ module.fail_json(msg = 'Unable to load %s' % e.message)
+
+ # setup the auth
+ sys.stderr.write('region is %s' % region)
+ try:
+ pyrax.set_credential_file(creds_file, region=region)
+ except Exception, e:
+ module.fail_json(msg = '%s' % e.message)
+
+ # Act based on service
+ if service == 'cloudservers':
+ cloudservers(module, state, name, flavor, image, meta, key_name, files,
+ wait, wait_timeout)
+ elif service in ['cloudfiles', 'cloud_blockstorage',
+ 'cloud_databases', 'cloud_loadbalancers']:
+ module.fail_json(msg = 'Service %s is not supported at this time' %
+ service)
+
+
+# this is magic, see lib/ansible/module_common.py
+#<>
+
+main()
diff --git a/plugins/inventory/rax.py b/plugins/inventory/rax.py
new file mode 100755
index 0000000000..283486ef14
--- /dev/null
+++ b/plugins/inventory/rax.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+
+# (c) 2013, Jesse Keating
+#
+# 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 .
+
+DOCUMENTATION = '''
+---
+inventory: rax
+short_description: Rackspace Public Cloud external inventory script
+description:
+ - Generates inventory that Ansible can understand by making API request to Rackspace Public Cloud API
+ - |
+ When run against a specific host, this script returns the following variables:
+ 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
+
+ where some item can have nested structure.
+ - credentials are set in a credentials file
+version_added: None
+options:
+ creds_file:
+ description:
+ - File to find the Rackspace Public Cloud credentials in
+ required: true
+ default: null
+ region_name:
+ description:
+ - Region name to use in request
+ required: false
+ default: DFW
+author: Jesse Keating
+notes:
+ - Two environment variables need to be set, RAX_CREDS and RAX_REGION.
+ - RAX_CREDS points to a credentials file appropriate for pyrax
+ - RAX_REGION defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
+requirements: [ "pyrax" ]
+examples:
+ - description: List server instances
+ code: RAX_CREDS=~/.raxpub RAX_REGION=ORD rax.py --list
+ - description: List server instance properties
+ code: RAX_CREDS=~/.raxpub RAX_REGION=ORD rax.py --host
+'''
+
+import sys
+import re
+import os
+import argparse
+
+try:
+ import json
+except:
+ import simplejson as json
+
+try:
+ import pyrax
+except ImportError:
+ print('pyrax required for this module')
+ sys.exit(1)
+
+# Setup the parser
+parser = argparse.ArgumentParser(description='List active instances',
+ epilog='List by itself will list all the active \
+ instances. Listing a specific instance will show \
+ all the details about the instance.')
+
+parser.add_argument('--list', action='store_true', default=True,
+ help='List active servers')
+parser.add_argument('--host',
+ help='List details about the specific host (IP address)')
+
+args = parser.parse_args()
+
+# setup the auth
+try:
+ creds_file = os.environ['RAX_CREDS_FILE']
+ region = os.environ['RAX_REGION']
+except KeyError, e:
+ sys.stderr.write('Unable to load %s\n' % e.message)
+ sys.exit(1)
+
+try:
+ pyrax.set_credential_file(os.path.expanduser(creds_file),
+ region=region)
+except Exception, e:
+ sys.stderr.write("%s: %s\n" % (e, e.message))
+ sys.exit(1)
+
+# Execute the right stuff
+if not args.host:
+ groups = {}
+
+ # Cycle on servers
+ for server in pyrax.cloudservers.list():
+ # Define group (or set to empty string)
+ try:
+ group = server.metadata['group']
+ except KeyError:
+ group = 'undefined'
+
+ # Create group if not exist and add the server
+ groups.setdefault(group, []).append(server.accessIPv4)
+
+ # Return server list
+ print(json.dumps(groups))
+ sys.exit(0)
+
+# Get the deets for the instance asked for
+results = {}
+# This should be only one, but loop anyway
+for server in pyrax.cloudservers.list():
+ if server.accessIPv4 == args.host:
+ for key in [key for key in vars(server) if
+ key not in ('manager', '_info')]:
+ # Extract value
+ value = getattr(server, key)
+
+ # Generate sanitized key
+ key = 'rax_' + re.sub("[^A-Za-z0-9\-]", "_", key).lower()
+ results[key] = value
+
+print(json.dumps(results))
+sys.exit(0)