From 9027a9b02103f27a757ef1e06784cff72a791bfd Mon Sep 17 00:00:00 2001 From: Jesse Keating Date: Sat, 30 Mar 2013 07:38:42 -0700 Subject: [PATCH] Initial commit of rax library This library provides functionality for the Rackspace Public Cloud by way of the official pyrax SDK (https://github.com/rackspace/pyrax). At this time only the cloudservers service is functional. Instances can be created or deleted. Idempotency is provided on matching instances with the same name, flavor, image, and metadata values within a given region. pyrax usage does require a credentials file written out to hold username and API key. See pyrax documentation for details (https://github.com/rackspace/pyrax/blob/master/docs/pyrax_doc.md) --- library/rax | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 library/rax diff --git a/library/rax b/library/rax new file mode 100644 index 0000000000..7b8b42af3f --- /dev/null +++ b/library/rax @@ -0,0 +1,247 @@ +#!/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 + key_name: + description: + - key pair to use on the instance + required: false + default: null + aliases: ['keypair'] + 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, 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'): + # See if we already have any servers: + if not servers: + try: + servers = [pyrax.cloudservers.servers.create(name=name, + image=image, + flavor=flavor, + key_name=key_name, + meta=meta)] + 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(), + key_name = dict(aliases = ['keypair']), + flavor = dict(), + image = dict(), + meta = 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') + key_name = module.params.get('key_name') + flavor = module.params.get('flavor') + image = module.params.get('image') + meta = module.params.get('meta') + 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, 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()