diff --git a/library/cloud/rax_cbs b/library/cloud/rax_cbs
new file mode 100644
index 00000000000..73106eb41ab
--- /dev/null
+++ b/library/cloud/rax_cbs
@@ -0,0 +1,282 @@
+#!/usr/bin/python
+# 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_cbs
+short_description: Manipulate Rackspace Cloud Block Storage Volumes
+description:
+ - Manipulate Rackspace Cloud Block Storage Volumes
+version_added: 1.6
+options:
+ api_key:
+ description:
+ - Rackspace API key (overrides I(credentials))
+ aliases:
+ - password
+ auth_endpoint:
+ description:
+ - The URI of the authentication service
+ default: https://identity.api.rackspacecloud.com/v2.0/
+ credentials:
+ description:
+ - File to find the Rackspace credentials in (ignored if I(api_key) and
+ I(username) are provided)
+ default: null
+ aliases:
+ - creds_file
+ env:
+ description:
+ - Environment as configured in ~/.pyrax.cfg,
+ see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration)
+ identity_type:
+ description:
+ - Authentication machanism to use, such as rackspace or keystone
+ default: rackspace
+ region:
+ description:
+ - Region to create an instance in
+ default: DFW
+ tenant_id:
+ description:
+ - The tenant ID used for authentication
+ tenant_name:
+ description:
+ - The tenant name used for authentication
+ username:
+ description:
+ - Rackspace username (overrides I(credentials))
+ verify_ssl:
+ description:
+ - Whether or not to require SSL validation of API endpoints
+ description:
+ description:
+ - Description to give the volume being created
+ default: null
+ meta:
+ description:
+ - A hash of metadata to associate with the volume
+ default: null
+ name:
+ description:
+ - Name to give the volume being created
+ default: null
+ required: true
+ size:
+ description:
+ - Size of the volume to create in Gigabytes
+ default: 100
+ required: true
+ snapshot_id:
+ description:
+ - The id of the snapshot to create the volume from
+ default: null
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices:
+ - present
+ - absent
+ default: present
+ required: true
+ volume_type:
+ description:
+ - Type of the volume being created
+ choices:
+ - SATA
+ - SSD
+ default: SATA
+ required: true
+ wait:
+ description:
+ - wait for the volume to be in state 'available' before returning
+ default: "no"
+ choices:
+ - "yes"
+ - "no"
+ wait_timeout:
+ description:
+ - how long before wait gives up, in seconds
+ default: 300
+requirements:
+ - pyrax
+author: Christopher H. Laco, Matt Martz
+notes:
+ - The following environment variables can be used, C(RAX_USERNAME),
+ C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION).
+ - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file
+ appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating)
+ - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file
+ - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
+'''
+
+EXAMPLES = '''
+- name: Build a Block Storage Volume
+ gather_facts: False
+ hosts: local
+ connection: local
+ tasks:
+ - name: Storage volume create request
+ local_action:
+ module: rax_cbs
+ credentials: ~/.raxpub
+ name: my-volume
+ description: My Volume
+ volume_type: SSD
+ size: 150
+ region: DFW
+ wait: yes
+ state: present
+ meta:
+ app: my-cool-app
+ register: my_volume
+'''
+
+import sys
+
+from uuid import UUID
+from types import NoneType
+
+try:
+ import pyrax
+ HAS_PYRAX = True
+except ImportError:
+ HAS_PYRAX = False
+
+NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
+VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use',
+ 'error', 'error_deleting')
+
+
+def cloud_block_storage(module, state, name, description, meta, size,
+ snapshot_id, volume_type, wait, wait_timeout):
+ for arg in (state, name, size, volume_type):
+ if not arg:
+ module.fail_json(msg='%s is required for rax_cbs' % arg)
+
+ if size < 100:
+ module.fail_json(msg='"size" must be greater than or equal to 100')
+
+ changed = False
+ volume = None
+ instance = {}
+
+ cbs = pyrax.cloud_blockstorage
+
+ if cbs is None:
+ module.fail_json(msg='Failed to instantiate client. This '
+ 'typically indicates an invalid region or an '
+ 'incorrectly capitalized region name.')
+
+ try:
+ UUID(name)
+ volume = cbs.get(name)
+ except ValueError:
+ try:
+ volume = cbs.find(name=name)
+ except Exception, e:
+ module.fail_json(msg='%s' % e)
+
+ if state == 'present':
+ if not volume:
+ try:
+ volume = cbs.create(name, size=size, volume_type=volume_type,
+ description=description,
+ metadata=meta,
+ snapshot_id=snapshot_id)
+ changed = True
+ except Exception, e:
+ module.fail_json(msg='%s' % e.message)
+ else:
+ if wait:
+ attempts = wait_timeout / 5
+ pyrax.utils.wait_for_build(volume, interval=5,
+ attempts=attempts)
+
+ volume.get()
+ for key, value in vars(volume).iteritems():
+ if (isinstance(value, NON_CALLABLES) and
+ not key.startswith('_')):
+ instance[key] = value
+
+ result = dict(changed=changed, volume=instance)
+
+ if volume.status == 'error':
+ result['msg'] = '%s failed to build' % volume.id
+ elif wait and volume.status not in VOLUME_STATUS:
+ result['msg'] = 'Timeout waiting on %s' % volume.id
+
+ if 'msg' in result:
+ module.fail_json(**result)
+ else:
+ module.exit_json(**result)
+
+ elif state == 'absent':
+ if volume:
+ try:
+ volume.delete()
+ changed = True
+ except Exception, e:
+ module.fail_json(msg='%s' % e.message)
+
+ module.exit_json(changed=changed, volume=instance)
+
+
+def main():
+ argument_spec = rax_argument_spec()
+ argument_spec.update(
+ dict(
+ description=dict(),
+ meta=dict(type='dict', default={}),
+ name=dict(required=True),
+ size=dict(type='int', default=100),
+ snapshot_id=dict(),
+ state=dict(default='present', choices=['present', 'absent']),
+ volume_type=dict(choices=['SSD', 'SATA'], default='SATA'),
+ wait=dict(type='bool', default=False),
+ wait_timeout=dict(type='int', default=300)
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=rax_required_together()
+ )
+
+ if not HAS_PYRAX:
+ module.fail_json(msg='pyrax is required for this module')
+
+ description = module.params.get('description')
+ meta = module.params.get('meta')
+ name = module.params.get('name')
+ size = module.params.get('size')
+ snapshot_id = module.params.get('snapshot_id')
+ state = module.params.get('state')
+ volume_type = module.params.get('volume_type')
+ wait = module.params.get('wait')
+ wait_timeout = module.params.get('wait_timeout')
+
+ setup_rax_module(module, pyrax)
+
+ cloud_block_storage(module, state, name, description, meta, size,
+ snapshot_id, volume_type, wait, wait_timeout)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.rax import *
+
+### invoke the module
+main()
diff --git a/library/cloud/rax_cbs_attachments b/library/cloud/rax_cbs_attachments
new file mode 100644
index 00000000000..c20c03a69ea
--- /dev/null
+++ b/library/cloud/rax_cbs_attachments
@@ -0,0 +1,314 @@
+#!/usr/bin/python
+# 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_cbs_attachments
+short_description: Manipulate Rackspace Cloud Block Storage Volume Attachments
+description:
+ - Manipulate Rackspace Cloud Block Storage Volume Attachments
+version_added: 1.6
+options:
+ api_key:
+ description:
+ - Rackspace API key (overrides I(credentials))
+ aliases:
+ - password
+ auth_endpoint:
+ description:
+ - The URI of the authentication service
+ default: https://identity.api.rackspacecloud.com/v2.0/
+ credentials:
+ description:
+ - File to find the Rackspace credentials in (ignored if I(api_key) and
+ I(username) are provided)
+ default: null
+ aliases:
+ - creds_file
+ env:
+ description:
+ - Environment as configured in ~/.pyrax.cfg,
+ see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration)
+ identity_type:
+ description:
+ - Authentication machanism to use, such as rackspace or keystone
+ default: rackspace
+ region:
+ description:
+ - Region to create an instance in
+ default: DFW
+ tenant_id:
+ description:
+ - The tenant ID used for authentication
+ tenant_name:
+ description:
+ - The tenant name used for authentication
+ username:
+ description:
+ - Rackspace username (overrides I(credentials))
+ verify_ssl:
+ description:
+ - Whether or not to require SSL validation of API endpoints
+ device:
+ description:
+ - The device path to attach the volume to, e.g. /dev/xvde
+ default: null
+ required: true
+ volume:
+ description:
+ - Name or id of the volume to attach/detach
+ default: null
+ required: true
+ server:
+ description:
+ - Name or id of the server to attach/detach
+ default: null
+ required: true
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices:
+ - present
+ - absent
+ default: present
+ required: true
+ wait:
+ description:
+ - wait for the volume to be in 'in-use'/'available' state before returning
+ default: "no"
+ choices:
+ - "yes"
+ - "no"
+ wait_timeout:
+ description:
+ - how long before wait gives up, in seconds
+ default: 300
+requirements:
+ - pyrax
+author: Christopher H. Laco, Matt Martz
+notes:
+ - The following environment variables can be used, C(RAX_USERNAME),
+ C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION).
+ - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file
+ appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating)
+ - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file
+ - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
+'''
+
+EXAMPLES = '''
+- name: Attach a Block Storage Volume
+ gather_facts: False
+ hosts: local
+ connection: local
+ tasks:
+ - name: Storage volume attach request
+ local_action:
+ module: rax_cbs_attachments
+ credentials: ~/.raxpub
+ volume: my-volume
+ server: my-server
+ device: /dev/xvdd
+ region: DFW
+ wait: yes
+ state: present
+ register: my_volume
+'''
+
+import sys
+
+from uuid import UUID
+from types import NoneType
+
+try:
+ import pyrax
+ HAS_PYRAX = True
+except ImportError:
+ HAS_PYRAX = False
+
+NON_CALLABLES = (basestring, bool, dict, int, list, NoneType)
+
+
+def cloud_block_storage_attachments(module, state, volume, server, device,
+ wait, wait_timeout):
+ for arg in (state, volume, server, device):
+ if not arg:
+ module.fail_json(msg='%s is required for rax_cbs_attachments' %
+ arg)
+
+ cbs = pyrax.cloud_blockstorage
+ cs = pyrax.cloudservers
+
+ if cbs is None or cs is None:
+ module.fail_json(msg='Failed to instantiate client. This '
+ 'typically indicates an invalid region or an '
+ 'incorrectly capitalized region name.')
+
+ changed = False
+ instance = {}
+
+ try:
+ UUID(volume)
+ volume = cbs.get(volume)
+ except ValueError:
+ try:
+ volume = cbs.find(name=volume)
+ except Exception, e:
+ module.fail_json(msg='%s' % e)
+
+ if not volume:
+ module.fail_json(msg='No matching storage volumes were found')
+
+ if state == 'present':
+ try:
+ UUID(server)
+ server = cs.servers.get(server)
+ except ValueError:
+ servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
+ if not servers:
+ module.fail_json(msg='No Server was matched by name, '
+ 'try using the Server ID instead')
+ if len(servers) > 1:
+ module.fail_json(msg='Multiple servers matched by name, '
+ 'try using the Server ID instead')
+
+ # We made it this far, grab the first and hopefully only server
+ # in the list
+ server = servers[0]
+
+ if (volume.attachments and
+ volume.attachments[0]['server_id'] == server.id):
+ changed = False
+ elif volume.attachments:
+ module.fail_json(msg='Volume is attached to another server')
+ else:
+ try:
+ volume.attach_to_instance(server, mountpoint=device)
+ changed = True
+ except Exception, e:
+ module.fail_json(msg='%s' % e.message)
+
+ volume.get()
+
+ for key, value in vars(volume).iteritems():
+ if (isinstance(value, NON_CALLABLES) and
+ not key.startswith('_')):
+ instance[key] = value
+
+ result = dict(changed=changed, volume=instance)
+
+ if volume.status == 'error':
+ result['msg'] = '%s failed to build' % volume.id
+ elif wait:
+ attempts = wait_timeout / 5
+ pyrax.utils.wait_until(volume, 'status', 'in-use',
+ interval=5, attempts=attempts)
+
+ if 'msg' in result:
+ module.fail_json(**result)
+ else:
+ module.exit_json(**result)
+
+ elif state == 'absent':
+ try:
+ UUID(server)
+ server = cs.servers.get(server)
+ except ValueError:
+ servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
+ if not servers:
+ module.fail_json(msg='No Server was matched by name, '
+ 'try using the Server ID instead')
+ if len(servers) > 1:
+ module.fail_json(msg='Multiple servers matched by name, '
+ 'try using the Server ID instead')
+
+ # We made it this far, grab the first and hopefully only server
+ # in the list
+ server = servers[0]
+
+ if (volume.attachments and
+ volume.attachments[0]['server_id'] == server.id):
+ try:
+ volume.detach()
+ if wait:
+ pyrax.utils.wait_until(volume, 'status', 'available',
+ interval=3, attempts=0,
+ verbose=False)
+ changed = True
+ except Exception, e:
+ module.fail_json(msg='%s' % e.message)
+
+ volume.get()
+ changed = True
+ elif volume.attachments:
+ module.fail_json(msg='Volume is attached to another server')
+
+ for key, value in vars(volume).iteritems():
+ if (isinstance(value, NON_CALLABLES) and
+ not key.startswith('_')):
+ instance[key] = value
+
+ result = dict(changed=changed, volume=instance)
+
+ if volume.status == 'error':
+ result['msg'] = '%s failed to build' % volume.id
+
+ if 'msg' in result:
+ module.fail_json(**result)
+ else:
+ module.exit_json(**result)
+
+ module.exit_json(changed=changed, volume=instance)
+
+
+def main():
+ argument_spec = rax_argument_spec()
+ argument_spec.update(
+ dict(
+ device=dict(required=True),
+ volume=dict(required=True),
+ server=dict(required=True),
+ state=dict(default='present', choices=['present', 'absent']),
+ wait=dict(type='bool', default=False),
+ wait_timeout=dict(type='int', default=300)
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=rax_required_together()
+ )
+
+ if not HAS_PYRAX:
+ module.fail_json(msg='pyrax is required for this module')
+
+ device = module.params.get('device')
+ volume = module.params.get('volume')
+ server = module.params.get('server')
+ state = module.params.get('state')
+ wait = module.params.get('wait')
+ wait_timeout = module.params.get('wait_timeout')
+
+ setup_rax_module(module, pyrax)
+
+ cloud_block_storage_attachments(module, state, volume, server, device,
+ wait, wait_timeout)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.rax import *
+
+### invoke the module
+main()