34e585024c
Files can be inserted during server creation (like a fully formed authorized_keys file). This code allows that to happen. Docs were updated for formatting, location, and to add the new entry for files.
265 lines
9.1 KiB
Python
265 lines
9.1 KiB
Python
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
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
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|