diff --git a/rax b/rax
new file mode 100644
index 00000000000..2bdc1638249
--- /dev/null
+++ b/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()