mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 22:02:50 +00:00
Allow modules to be categorized, and also sort them when generating the documentation.
This commit is contained in:
245
library/cloud/cloudformation
Normal file
245
library/cloud/cloudformation
Normal file
@@ -0,0 +1,245 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cloudformation
|
||||
short_description: create a AWS CloudFormation stack
|
||||
description:
|
||||
- Launches an AWS CloudFormation stack and waits for it complete.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
stack_name:
|
||||
description:
|
||||
- name of the cloudformation stack
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
disable_rollback:
|
||||
description:
|
||||
- If a stacks fails to form, rollback will remove the stack
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
aliases: []
|
||||
template_parameters:
|
||||
description:
|
||||
- a list of hashes of all the template variables for the stack
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
region:
|
||||
description:
|
||||
- The AWS region the stack will be launched in
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- If state is "present", stack will be created. If state is "present" and if stack exists and template has changed, it will be updated.
|
||||
If state is absent, stack will be removed.
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
template:
|
||||
description:
|
||||
- the path of the cloudformation template
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
wait_for:
|
||||
description:
|
||||
- Wait while the stack is being created/updated/deleted.
|
||||
required: false
|
||||
default: "yes"
|
||||
choices: [ "yes", "no" ]
|
||||
aliases: []
|
||||
requirements: [ "boto" ]
|
||||
author: James S. Martin
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Basic task example
|
||||
tasks:
|
||||
- name: launch ansible cloudformation example
|
||||
action: cloudformation >
|
||||
stack_name="ansible-cloudformation" state=present
|
||||
region=us-east-1 disable_rollback=yes
|
||||
template=files/cloudformation-example.json
|
||||
args:
|
||||
template_parameters:
|
||||
KeyName: jmartin
|
||||
DiskType: ephemeral
|
||||
InstanceType: m1.small
|
||||
ClusterSize: 3
|
||||
'''
|
||||
|
||||
import boto.cloudformation.connection
|
||||
import json
|
||||
|
||||
|
||||
class Region:
|
||||
def __init__(self, region):
|
||||
self.name = region
|
||||
self.endpoint = 'cloudformation.%s.amazonaws.com' % region
|
||||
|
||||
|
||||
def boto_exception(err):
|
||||
|
||||
if hasattr(err, 'error_message'):
|
||||
error = err.error_message
|
||||
elif hasattr(err, 'message'):
|
||||
error = err.message
|
||||
else:
|
||||
error = '%s: %s' % (Exception, err)
|
||||
try:
|
||||
error_msg = json.loads(error)
|
||||
except:
|
||||
error_msg = {'Error': error}
|
||||
return error_msg
|
||||
|
||||
|
||||
def stack_operation(cfn, stack_name, operation):
|
||||
existed = []
|
||||
result = {}
|
||||
operation_complete = False
|
||||
while operation_complete == False:
|
||||
try:
|
||||
stack = cfn.describe_stacks(stack_name)[0]
|
||||
existed.append('yes')
|
||||
except:
|
||||
if 'yes' in existed:
|
||||
result = {'changed': True, 'output': 'Stack Deleted'}
|
||||
result['events'] = map(str, list(stack.describe_events()))
|
||||
else:
|
||||
result = {'changed': True, 'output': 'Stack Not Found'}
|
||||
break
|
||||
if '%s_COMPLETE' % operation == stack.stack_status:
|
||||
result['changed'] = True
|
||||
result['events'] = map(str, list(stack.describe_events()))
|
||||
result['output'] = 'Stack %s complete' % operation
|
||||
break
|
||||
elif '%s_FAILED' % operation == stack.stack_status:
|
||||
result['changed'] = False
|
||||
result['events'] = map(str, list(stack.describe_events()))
|
||||
result['output'] = 'Stack %s failed' % operation
|
||||
break
|
||||
else:
|
||||
time.sleep(5)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
stack_name=dict(required=True),
|
||||
template_parameters=dict(required=False),
|
||||
region=dict(required=True,
|
||||
choices=['ap-northeast-1', 'ap-southeast-1',
|
||||
'ap-southeast-2', 'eu-west-1',
|
||||
'sa-east-1', 'us-east-1', 'us-west-1',
|
||||
'us-west-2']),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
template=dict(default=None, required=True),
|
||||
disable_rollback=dict(default=False),
|
||||
wait_for=dict(default=True)
|
||||
)
|
||||
)
|
||||
|
||||
wait_for = module.params['wait_for']
|
||||
state = module.params['state']
|
||||
stack_name = module.params['stack_name']
|
||||
region = Region(module.params['region'])
|
||||
template_body = open(module.params['template'], 'r').read()
|
||||
disable_rollback = module.params['disable_rollback']
|
||||
template_parameters = module.params['template_parameters']
|
||||
|
||||
template_parameters_tup = [(k, v) for k, v in template_parameters.items()]
|
||||
stack_outputs = {}
|
||||
stack_outputs[module.params['region']] = {}
|
||||
stack_outputs[module.params['region']][stack_name] = {}
|
||||
|
||||
try:
|
||||
cfn = boto.cloudformation.connection.CloudFormationConnection(
|
||||
region=region)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
update = False
|
||||
stack_events = []
|
||||
result = {}
|
||||
operation = None
|
||||
output = ''
|
||||
|
||||
if state == 'present':
|
||||
try:
|
||||
cfn.create_stack(stack_name, parameters=template_parameters_tup,
|
||||
template_body=template_body,
|
||||
disable_rollback=disable_rollback,
|
||||
capabilities=['CAPABILITY_IAM'])
|
||||
operation = 'CREATE'
|
||||
except Exception, err:
|
||||
error_msg = boto_exception(err)
|
||||
if error_msg['Error']['Code'] == 'AlreadyExistsException':
|
||||
update = True
|
||||
else:
|
||||
result = {'changed': False, 'output': error_msg}
|
||||
module.fail_json(**result)
|
||||
if not update:
|
||||
result = stack_operation(cfn, stack_name, operation)
|
||||
if update:
|
||||
try:
|
||||
cfn.update_stack(stack_name, parameters=template_parameters_tup,
|
||||
template_body=template_body,
|
||||
disable_rollback=disable_rollback,
|
||||
capabilities=['CAPABILITY_IAM'])
|
||||
operation = 'UPDATE'
|
||||
except Exception, err:
|
||||
error_msg = boto_exception(err)
|
||||
if error_msg['Error']['Message'] == 'No updates are to be performed.':
|
||||
output = error_msg['Error']['Message']
|
||||
result = {'changed': False, 'output': output}
|
||||
if operation == 'UPDATE':
|
||||
result = stack_operation(cfn, stack_name, operation)
|
||||
|
||||
if state == 'present' or update:
|
||||
stack = cfn.describe_stacks(stack_name)[0]
|
||||
for output in stack.outputs:
|
||||
stack_outputs[module.params['region']][stack_name][
|
||||
output.key] = output.value
|
||||
result['stack_outputs'] = stack_outputs
|
||||
|
||||
# absent state is different because of the way delete_stack works.
|
||||
# problem is it it doesn't give an error if stack isn't found
|
||||
# so must describe the stack first
|
||||
|
||||
if state == 'absent':
|
||||
try:
|
||||
cfn.describe_stacks(stack_name)
|
||||
operation = 'DELETE'
|
||||
except Exception, err:
|
||||
error_msg = boto_exception(err)
|
||||
result = {'changed': False, 'output': error_msg}
|
||||
module.fail_json(result)
|
||||
if operation == 'DELETE':
|
||||
cfn.delete_stack(stack_name)
|
||||
result = stack_operation(cfn, stack_name, operation)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
||||
389
library/cloud/ec2
Normal file
389
library/cloud/ec2
Normal file
@@ -0,0 +1,389 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ec2
|
||||
short_description: create an instance in ec2, return instanceid
|
||||
description:
|
||||
- creates ec2 instances and optionally waits for it to be 'running'. This module has a dependency on python-boto >= 2.5
|
||||
version_added: "0.9"
|
||||
options:
|
||||
key_name:
|
||||
description:
|
||||
- key pair to use on the instance
|
||||
required: true
|
||||
default: null
|
||||
aliases: ['keypair']
|
||||
id:
|
||||
description:
|
||||
- identifier for this instance or set of instances, so that the module will be idempotent with respect to EC2 instances.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
group:
|
||||
description:
|
||||
- security group to use with the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
group_id:
|
||||
version_added: "1.1"
|
||||
description:
|
||||
- security group id to use with the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
region:
|
||||
version_added: "1.2"
|
||||
description:
|
||||
- the EC2 region to use
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
zone:
|
||||
version_added: "1.2"
|
||||
description:
|
||||
- availability zone in which to launch the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
instance_type:
|
||||
description:
|
||||
- instance type to use for the instance
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
image:
|
||||
description:
|
||||
- I(emi) (or I(ami)) to use for the instance
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
kernel:
|
||||
description:
|
||||
- kernel I(eki) to use for the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
ramdisk:
|
||||
description:
|
||||
- ramdisk I(eri) to use for the instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
wait:
|
||||
description:
|
||||
- wait for the instance to be in state 'running' before returning
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
aliases: []
|
||||
wait_timeout:
|
||||
description:
|
||||
- how long before wait gives up, in seconds
|
||||
default: 300
|
||||
aliases: []
|
||||
ec2_url:
|
||||
description:
|
||||
- url to use to connect to EC2 or your Eucalyptus cloud (by default the module will use EC2 endpoints)
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
ec2_secret_key:
|
||||
description:
|
||||
- ec2 secret key
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
ec2_access_key:
|
||||
description:
|
||||
- ec2 access key
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
count:
|
||||
description:
|
||||
- number of instances to launch
|
||||
required: False
|
||||
default: 1
|
||||
aliases: []
|
||||
monitor:
|
||||
version_added: "1.1"
|
||||
description:
|
||||
- enable detailed monitoring (CloudWatch) for instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
user_data:
|
||||
version_added: "0.9"
|
||||
description:
|
||||
- opaque blob of data which is made available to the ec2 instance
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
instance_tags:
|
||||
version_added: "1.0"
|
||||
description:
|
||||
- a hash/dictionary of tags to add to the new instance; '{"key":"value"}' and '{"key":"value","key":"value"}'
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
vpc_subnet_id:
|
||||
version_added: "1.1"
|
||||
description:
|
||||
- the subnet ID in which to launch the instance (VPC)
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
private_ip:
|
||||
version_added: "1.2"
|
||||
description:
|
||||
- the private ip address to assign the instance (from the vpc subnet)
|
||||
required: false
|
||||
defualt: null
|
||||
aliases: []
|
||||
requirements: [ "boto" ]
|
||||
author: Seth Vidal, Tim Gerla, Lester Wade
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Basic provisioning example
|
||||
local_action:
|
||||
module: ec2
|
||||
keypair: mykey
|
||||
instance_type: c1.medium
|
||||
image: emi-40603AD1
|
||||
wait: yes
|
||||
group: webserver
|
||||
count: 3
|
||||
|
||||
# Advanced example with tagging and CloudWatch
|
||||
local_action:
|
||||
module: ec2
|
||||
keypair: mykey
|
||||
group: databases
|
||||
instance_type: m1.large
|
||||
image: ami-6e649707
|
||||
wait: yes
|
||||
wait_timeout: 500
|
||||
count: 5
|
||||
instance_tags: '{"db":"postgres"}' monitoring=true'
|
||||
|
||||
# VPC example
|
||||
local_action:
|
||||
module: ec2
|
||||
keypair: mykey
|
||||
group_id: sg-1dc53f72
|
||||
instance_type: m1.small
|
||||
image: ami-6e649707
|
||||
wait: yes
|
||||
vpc_subnet_id: subnet-29e63245'
|
||||
'''
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import boto.ec2
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
key_name = dict(required=True, aliases = ['keypair']),
|
||||
id = dict(),
|
||||
group = dict(),
|
||||
group_id = dict(),
|
||||
region = dict(choices=['eu-west-1', 'sa-east-1', 'us-east-1', 'ap-northeast-1', 'us-west-2', 'us-west-1', 'ap-southeast-1', 'ap-southeast-2']),
|
||||
zone = dict(),
|
||||
instance_type = dict(aliases=['type']),
|
||||
image = dict(required=True),
|
||||
kernel = dict(),
|
||||
count = dict(default='1'),
|
||||
monitoring = dict(choices=BOOLEANS, default=False),
|
||||
ramdisk = dict(),
|
||||
wait = dict(choices=BOOLEANS, default=False),
|
||||
wait_timeout = dict(default=300),
|
||||
ec2_url = dict(aliases=['EC2_URL']),
|
||||
ec2_secret_key = dict(aliases=['EC2_SECRET_KEY'], no_log=True),
|
||||
ec2_access_key = dict(aliases=['EC2_ACCESS_KEY']),
|
||||
user_data = dict(),
|
||||
instance_tags = dict(),
|
||||
vpc_subnet_id = dict(),
|
||||
private_ip = dict(),
|
||||
)
|
||||
)
|
||||
|
||||
key_name = module.params.get('key_name')
|
||||
id = module.params.get('id')
|
||||
group_name = module.params.get('group')
|
||||
group_id = module.params.get('group_id')
|
||||
region = module.params.get('region')
|
||||
zone = module.params.get('zone')
|
||||
instance_type = module.params.get('instance_type')
|
||||
image = module.params.get('image')
|
||||
count = module.params.get('count')
|
||||
monitoring = module.params.get('monitoring')
|
||||
kernel = module.params.get('kernel')
|
||||
ramdisk = module.params.get('ramdisk')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = int(module.params.get('wait_timeout'))
|
||||
ec2_url = module.params.get('ec2_url')
|
||||
ec2_secret_key = module.params.get('ec2_secret_key')
|
||||
ec2_access_key = module.params.get('ec2_access_key')
|
||||
user_data = module.params.get('user_data')
|
||||
instance_tags = module.params.get('instance_tags')
|
||||
vpc_subnet_id = module.params.get('vpc_subnet_id')
|
||||
private_ip = module.params.get('private_ip')
|
||||
|
||||
# allow eucarc environment variables to be used if ansible vars aren't set
|
||||
if not ec2_url and 'EC2_URL' in os.environ:
|
||||
ec2_url = os.environ['EC2_URL']
|
||||
if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ:
|
||||
ec2_secret_key = os.environ['EC2_SECRET_KEY']
|
||||
if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ:
|
||||
ec2_access_key = os.environ['EC2_ACCESS_KEY']
|
||||
|
||||
# If we have a region specified, connect to its endpoint.
|
||||
if region:
|
||||
try:
|
||||
ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=ec2_access_key, aws_secret_access_key=ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
# Otherwise, no region so we fallback to the old connection method
|
||||
else:
|
||||
try:
|
||||
if ec2_url: # if we have an URL set, connect to the specified endpoint
|
||||
ec2 = boto.connect_ec2_endpoint(ec2_url, ec2_access_key, ec2_secret_key)
|
||||
else: # otherwise it's Amazon.
|
||||
ec2 = boto.connect_ec2(ec2_access_key, ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
# Here we try to lookup the group name from the security group id - if group_id is set.
|
||||
try:
|
||||
if group_id:
|
||||
grp_details = ec2.get_all_security_groups(group_ids=group_id)
|
||||
grp_item = grp_details[0]
|
||||
group_name = grp_item.name
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
# Lookup any instances that much our run id.
|
||||
|
||||
running_instances = []
|
||||
count_remaining = int(count)
|
||||
|
||||
if id != None:
|
||||
filter_dict = {'client-token':id, 'instance-state-name' : 'running'}
|
||||
previous_reservations = ec2.get_all_instances(None, filter_dict)
|
||||
for res in previous_reservations:
|
||||
for prev_instance in res.instances:
|
||||
running_instances.append(prev_instance)
|
||||
count_remaining = count_remaining - len(running_instances)
|
||||
|
||||
# Both min_count and max_count equal count parameter. This means the launch request is explicit (we want count, or fail) in how many instances we want.
|
||||
|
||||
|
||||
if count_remaining > 0:
|
||||
try:
|
||||
res = ec2.run_instances(image, key_name = key_name,
|
||||
client_token=id,
|
||||
min_count = count_remaining,
|
||||
max_count = count_remaining,
|
||||
monitoring_enabled = monitoring,
|
||||
security_groups = [group_name],
|
||||
placement = zone,
|
||||
instance_type = instance_type,
|
||||
kernel_id = kernel,
|
||||
ramdisk_id = ramdisk,
|
||||
subnet_id = vpc_subnet_id,
|
||||
private_ip_address = private_ip,
|
||||
user_data = user_data)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
|
||||
|
||||
instids = [ i.id for i in res.instances ]
|
||||
while True:
|
||||
try:
|
||||
res.connection.get_all_instances(instids)
|
||||
break
|
||||
except boto.exception.EC2ResponseError as e:
|
||||
if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
|
||||
# there's a race between start and get an instance
|
||||
continue
|
||||
else:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
if instance_tags:
|
||||
try:
|
||||
ec2.create_tags(instids, module.from_json(instance_tags))
|
||||
except boto.exception.EC2ResponseError as e:
|
||||
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
|
||||
|
||||
# wait here until the instances are up
|
||||
res_list = res.connection.get_all_instances(instids)
|
||||
this_res = res_list[0]
|
||||
num_running = 0
|
||||
wait_timeout = time.time() + wait_timeout
|
||||
while wait and wait_timeout > time.time() and num_running < len(instids):
|
||||
res_list = res.connection.get_all_instances(instids)
|
||||
this_res = res_list[0]
|
||||
num_running = len([ i for i in this_res.instances if i.state=='running' ])
|
||||
time.sleep(5)
|
||||
if wait and wait_timeout <= time.time():
|
||||
# waiting took too long
|
||||
module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
|
||||
|
||||
for inst in this_res.instances:
|
||||
running_instances.append(inst)
|
||||
|
||||
instance_dict_array = []
|
||||
for inst in running_instances:
|
||||
d = {
|
||||
'id': inst.id,
|
||||
'ami_launch_index': inst.ami_launch_index,
|
||||
'private_ip': inst.private_ip_address,
|
||||
'private_dns_name': inst.private_dns_name,
|
||||
'public_ip': inst.ip_address,
|
||||
'dns_name': inst.dns_name,
|
||||
'public_dns_name': inst.public_dns_name,
|
||||
'state_code': inst.state_code,
|
||||
'architecture': inst.architecture,
|
||||
'image_id': inst.image_id,
|
||||
'key_name': inst.key_name,
|
||||
'virtualization_type': inst.virtualization_type,
|
||||
'placement': inst.placement,
|
||||
'kernel': inst.kernel,
|
||||
'ramdisk': inst.ramdisk,
|
||||
'launch_time': inst.launch_time,
|
||||
'instance_type': inst.instance_type,
|
||||
'root_device_type': inst.root_device_type,
|
||||
'root_device_name': inst.root_device_name,
|
||||
'state': inst.state,
|
||||
'hypervisor': inst.hypervisor
|
||||
}
|
||||
instance_dict_array.append(d)
|
||||
|
||||
module.exit_json(changed=True, instances=instance_dict_array)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
||||
158
library/cloud/ec2_facts
Normal file
158
library/cloud/ec2_facts
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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: ec2_facts
|
||||
short_description: Gathers facts about remote hosts within ec2 (aws)
|
||||
version_added: "1.0"
|
||||
options: {}
|
||||
description:
|
||||
- This module fetches data from the metadata servers in ec2 (aws).
|
||||
Eucalyptus cloud provides a similar service and this module should
|
||||
work this cloud provider as well.
|
||||
notes:
|
||||
- Parameters to filter on ec2_facts may be added later.
|
||||
examples:
|
||||
- code: ansible all -m ec2_facts
|
||||
description: Obtain facts from ec2 metatdata servers. You will need to run an instance within ec2.
|
||||
author: "Silviu Dicu <silviudicu@gmail.com>"
|
||||
"""
|
||||
|
||||
import urllib2
|
||||
import socket
|
||||
import re
|
||||
|
||||
socket.setdefaulttimeout(5)
|
||||
|
||||
class Ec2Metadata(object):
|
||||
|
||||
ec2_metadata_uri = 'http://169.254.169.254/latest/meta-data/'
|
||||
ec2_sshdata_uri = 'http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key'
|
||||
ec2_userdata_uri = 'http://169.254.169.254/latest/user-data/'
|
||||
|
||||
AWS_REGIONS = ('ap-northeast-1',
|
||||
'ap-southeast-1',
|
||||
'ap-southeast-2',
|
||||
'eu-west-1',
|
||||
'sa-east-1',
|
||||
'us-east-1',
|
||||
'us-west-1',
|
||||
'us-west-2')
|
||||
|
||||
def __init__(self, ec2_metadata_uri=None, ec2_sshdata_uri=None, ec2_userdata_uri=None):
|
||||
self.uri_meta = ec2_metadata_uri or self.ec2_metadata_uri
|
||||
self.uri_user = ec2_userdata_uri or self.ec2_userdata_uri
|
||||
self.uri_ssh = ec2_sshdata_uri or self.ec2_sshdata_uri
|
||||
self._data = {}
|
||||
self._prefix = 'ansible_ec2_%s'
|
||||
|
||||
def _fetch(self, url):
|
||||
try:
|
||||
return urllib2.urlopen(url).read()
|
||||
except urllib2.HTTPError:
|
||||
return
|
||||
except urllib2.URLError:
|
||||
return
|
||||
|
||||
def _mangle_fields(self, fields, uri, filter_patterns=['public-keys-0']):
|
||||
new_fields = {}
|
||||
for key, value in fields.iteritems():
|
||||
split_fields = key[len(uri):].split('/')
|
||||
if len(split_fields) > 1 and split_fields[1]:
|
||||
new_key = "-".join(split_fields)
|
||||
new_fields[self._prefix % new_key] = value
|
||||
else:
|
||||
new_key = "".join(split_fields)
|
||||
new_fields[self._prefix % new_key] = value
|
||||
for pattern in filter_patterns:
|
||||
for key in new_fields.keys():
|
||||
match = re.search(pattern, key)
|
||||
if match:
|
||||
new_fields.pop(key)
|
||||
return new_fields
|
||||
|
||||
def fetch(self, uri, recurse=True):
|
||||
raw_subfields = self._fetch(uri)
|
||||
if not raw_subfields:
|
||||
return
|
||||
subfields = raw_subfields.split('\n')
|
||||
for field in subfields:
|
||||
if field.endswith('/') and recurse:
|
||||
self.fetch(uri + field)
|
||||
if uri.endswith('/'):
|
||||
new_uri = uri + field
|
||||
else:
|
||||
new_uri = uri + '/' + field
|
||||
if new_uri not in self._data and not new_uri.endswith('/'):
|
||||
content = self._fetch(new_uri)
|
||||
if field == 'security-groups':
|
||||
sg_fields = ",".join(content.split('\n'))
|
||||
self._data['%s' % (new_uri)] = sg_fields
|
||||
else:
|
||||
self._data['%s' % (new_uri)] = content
|
||||
|
||||
def fix_invalid_varnames(self, data):
|
||||
"""Change ':'' and '-' to '_' to ensure valid template variable names"""
|
||||
for (key, value) in data.items():
|
||||
if ':' in key or '-' in key:
|
||||
newkey = key.replace(':','_').replace('-','_')
|
||||
data[newkey] = value
|
||||
|
||||
def add_ec2_region(self, data):
|
||||
"""Use the 'ansible_ec2_placement_availability_zone' key/value
|
||||
pair to add 'ansible_ec2_placement_region' key/value pair with
|
||||
the EC2 region name.
|
||||
"""
|
||||
|
||||
# Only add a 'ansible_ec2_placement_region' key if the
|
||||
# 'ansible_ec2_placement_availability_zone' exists.
|
||||
zone = data.get('ansible_ec2_placement_availability_zone')
|
||||
if zone is not None:
|
||||
# Use the zone name as the region name unless the zone
|
||||
# name starts with a known AWS region name.
|
||||
region = zone
|
||||
for r in self.AWS_REGIONS:
|
||||
if zone.startswith(r):
|
||||
region = r
|
||||
break
|
||||
data['ansible_ec2_placement_region'] = region
|
||||
|
||||
def run(self):
|
||||
self.fetch(self.uri_meta) # populate _data
|
||||
data = self._mangle_fields(self._data, self.uri_meta)
|
||||
data[self._prefix % 'user-data'] = self._fetch(self.uri_user)
|
||||
data[self._prefix % 'public-key'] = self._fetch(self.uri_ssh)
|
||||
self.fix_invalid_varnames(data)
|
||||
self.add_ec2_region(data)
|
||||
return data
|
||||
|
||||
def main():
|
||||
|
||||
ec2_facts = Ec2Metadata().run()
|
||||
ec2_facts_result = dict(changed=False, ansible_facts=ec2_facts)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict()
|
||||
)
|
||||
module.exit_json(**ec2_facts_result)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
||||
215
library/cloud/ec2_vol
Normal file
215
library/cloud/ec2_vol
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ec2_vol
|
||||
short_description: create and attach a volume, return volume id and device map
|
||||
description:
|
||||
- creates an EBS volume and optionally attaches it to an instance. If both an instance ID and a device name is given and the instance has a device at the device name, then no volume is created and no attachment is made. This module has a dependency on python-boto.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
instance:
|
||||
description:
|
||||
- instance ID if you wish to attach the volume.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
volume_size:
|
||||
description:
|
||||
- size of volume (in GB) to create.
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
device_name:
|
||||
description:
|
||||
- device id to override device mapping. Assumes /dev/sdf for Linux/UNIX and /dev/xvdf for Windows.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
region:
|
||||
description:
|
||||
- region in which to create the volume
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
zone:
|
||||
description:
|
||||
- zone in which to create the volume, if unset uses the zone the instance is in (if set)
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
requirements: [ "boto" ]
|
||||
author: Lester Wade
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Simple attachment action
|
||||
local_action:
|
||||
module: ec2_vol
|
||||
instance: XXXXXX
|
||||
volume_size: 5
|
||||
device_name: sdd
|
||||
|
||||
# Playbook example combined with instance launch
|
||||
local_action:
|
||||
module: ec2
|
||||
keypair: $keypair
|
||||
image: $image
|
||||
wait: yes
|
||||
count: 3
|
||||
register: ec2
|
||||
local_action:
|
||||
module: ec2_vol
|
||||
instance: ${item.id}
|
||||
volume_size: 5
|
||||
with_items: ${ec2.instances}
|
||||
register: ec2_vol
|
||||
'''
|
||||
|
||||
# Note: this module needs to be made idempotent. Possible solution is to use resource tags with the volumes.
|
||||
# if state=present and it doesn't exist, create, tag and attach.
|
||||
# Check for state by looking for volume attachment with tag (and against block device mapping?).
|
||||
# Would personally like to revisit this in May when Eucalyptus also has tagging support (3.3).
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import boto.ec2
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
instance = dict(),
|
||||
volume_size = dict(required=True),
|
||||
device_name = dict(),
|
||||
region = dict(),
|
||||
zone = dict(),
|
||||
ec2_url = dict(aliases=['EC2_URL']),
|
||||
ec2_secret_key = dict(aliases=['EC2_SECRET_KEY']),
|
||||
ec2_access_key = dict(aliases=['EC2_ACCESS_KEY']),
|
||||
)
|
||||
)
|
||||
|
||||
instance = module.params.get('instance')
|
||||
volume_size = module.params.get('volume_size')
|
||||
device_name = module.params.get('device_name')
|
||||
region = module.params.get('region')
|
||||
zone = module.params.get('zone')
|
||||
ec2_url = module.params.get('ec2_url')
|
||||
ec2_secret_key = module.params.get('ec2_secret_key')
|
||||
ec2_access_key = module.params.get('ec2_access_key')
|
||||
|
||||
# allow eucarc environment variables to be used if ansible vars aren't set
|
||||
if not ec2_url and 'EC2_URL' in os.environ:
|
||||
ec2_url = os.environ['EC2_URL']
|
||||
if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ:
|
||||
ec2_secret_key = os.environ['EC2_SECRET_KEY']
|
||||
if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ:
|
||||
ec2_access_key = os.environ['EC2_ACCESS_KEY']
|
||||
|
||||
# If we have a region specified, connect to its endpoint.
|
||||
if region:
|
||||
try:
|
||||
ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=ec2_access_key, aws_secret_access_key=ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
# Otherwise, no region so we fallback to the old connection method
|
||||
else:
|
||||
try:
|
||||
if ec2_url: # if we have an URL set, connect to the specified endpoint
|
||||
ec2 = boto.connect_ec2_endpoint(ec2_url, ec2_access_key, ec2_secret_key)
|
||||
else: # otherwise it's Amazon.
|
||||
ec2 = boto.connect_ec2(ec2_access_key, ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
# Here we need to get the zone info for the instance. This covers situation where
|
||||
# instance is specified but zone isn't.
|
||||
# Useful for playbooks chaining instance launch with volume create + attach and where the
|
||||
# zone doesn't matter to the user.
|
||||
|
||||
if instance:
|
||||
reservation = ec2.get_all_instances(instance_ids=instance)
|
||||
inst = reservation[0].instances[0]
|
||||
zone = inst.placement
|
||||
|
||||
# Check if there is a volume already mounted there.
|
||||
if device_name:
|
||||
if device_name in inst.block_device_mapping:
|
||||
module.exit_json(msg="Volume mapping for %s already exists on instance %s" % (device_name, instance),
|
||||
changed=False)
|
||||
|
||||
# If no instance supplied, try volume creation based on module parameters.
|
||||
|
||||
try:
|
||||
volume = ec2.create_volume(volume_size, zone)
|
||||
while volume.status != 'available':
|
||||
time.sleep(3)
|
||||
volume.update()
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
|
||||
|
||||
# Attach the created volume.
|
||||
|
||||
if device_name and instance:
|
||||
try:
|
||||
attach = volume.attach(inst.id, device_name)
|
||||
while volume.attachment_state() != 'attached':
|
||||
time.sleep(3)
|
||||
volume.update()
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
|
||||
|
||||
# If device_name isn't set, make a choice based on best practices here:
|
||||
# http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
|
||||
|
||||
# In future this needs to be more dynamic but combining block device mapping best practices
|
||||
# (bounds for devices, as above) with instance.block_device_mapping data would be tricky. For me ;)
|
||||
|
||||
# Use password data attribute to tell whether the instance is Windows or Linux
|
||||
|
||||
if device_name is None and instance:
|
||||
try:
|
||||
if not ec2.get_password_data(inst.id):
|
||||
device_name = '/dev/sdf'
|
||||
attach = volume.attach(inst.id, device_name)
|
||||
while volume.attachment_state() != 'attached':
|
||||
time.sleep(3)
|
||||
volume.update()
|
||||
else:
|
||||
device_name = '/dev/xvdf'
|
||||
attach = volume.attach(inst.id, device_name)
|
||||
while volume.attachment_state() != 'attached':
|
||||
time.sleep(3)
|
||||
volume.update()
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
|
||||
|
||||
print json.dumps({
|
||||
"volume_id": volume.id,
|
||||
"device": device_name
|
||||
})
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
||||
267
library/cloud/rax
Normal file
267
library/cloud/rax
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/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 / delete an instance in Rackspace Public Cloud
|
||||
description:
|
||||
- creates / deletes Rackspace Public Cloud instances and optionally waits for it to be 'running'.
|
||||
version_added: "1.2"
|
||||
options:
|
||||
service:
|
||||
description:
|
||||
- Cloud service to interact with
|
||||
choices: ['cloudservers']
|
||||
default: cloudservers
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the resource
|
||||
choices: ['present', 'active', 'absent', 'deleted']
|
||||
default: present
|
||||
creds_file:
|
||||
description:
|
||||
- File to find the Rackspace Public Cloud credentials in
|
||||
default: null
|
||||
name:
|
||||
description:
|
||||
- Name to give the instance
|
||||
default: null
|
||||
flavor:
|
||||
description:
|
||||
- flavor to use for the instance
|
||||
default: null
|
||||
image:
|
||||
description:
|
||||
- image to use for the instance
|
||||
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
|
||||
default: null
|
||||
aliases: ['keypair']
|
||||
files:
|
||||
description:
|
||||
- Files to insert into the instance. remotefilename:localcontent
|
||||
default: null
|
||||
region:
|
||||
description:
|
||||
- Region to create an instance in
|
||||
default: null
|
||||
wait:
|
||||
description:
|
||||
- wait for the instance to be in state 'running' before returning
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
wait_timeout:
|
||||
description:
|
||||
- how long before wait gives up, in seconds
|
||||
default: 300
|
||||
examples:
|
||||
- code: |
|
||||
- name: Create a server
|
||||
local_action:
|
||||
module: rax
|
||||
creds_file: ~/.raxpub
|
||||
service: cloudservers
|
||||
name: rax-test1
|
||||
flavor: 5
|
||||
image: b11d9567-e412-4255-96b9-bd63ab23bcfe
|
||||
wait: yes
|
||||
state: present
|
||||
description: "Example 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)
|
||||
|
||||
# These are possible services, but only cloudservers is supported at this time
|
||||
#SUPPORTEDSERVICES = ['cloudservers', 'cloudfiles', 'cloud_blockstorage',
|
||||
# 'cloud_databases', 'cloud_loadbalancers']
|
||||
SUPPORTEDSERVICES = ['cloudservers']
|
||||
|
||||
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 int(flavor) != int(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
|
||||
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()
|
||||
251
library/cloud/s3
Normal file
251
library/cloud/s3
Normal file
@@ -0,0 +1,251 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: s3
|
||||
short_description: idempotent s3 module putting a file into S3.
|
||||
description:
|
||||
- This module allows the user to dictate the presence of a given file in an S3 bucket. If or once the key (file) exists in the bucket, it returns a time-expired download url. This module has a dependency on python-boto.
|
||||
version_added: "1.1"
|
||||
options:
|
||||
bucket:
|
||||
description:
|
||||
- bucket you wish to present/absent for the key (file in path).
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- desired state for both bucket and file.
|
||||
default: null
|
||||
aliases: []
|
||||
path:
|
||||
description:
|
||||
- path to the key (file) which you wish to be present/absent in the bucket.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
expiry:
|
||||
description:
|
||||
- expiry period (in seconds) for returned download URL.
|
||||
required: false
|
||||
default: 600
|
||||
aliases: []
|
||||
overwrite:
|
||||
description:
|
||||
- force overwrite if a file with the same name already exists, values true/false/yes/no. Does not support files uploaded to s3 with multipart upload.
|
||||
required: false
|
||||
default: false
|
||||
version_added: "1.2"
|
||||
requirements: [ "boto" ]
|
||||
author: Lester Wade
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Simple PUT operation
|
||||
module: s3
|
||||
bucket: mybucket
|
||||
path: /path/to/file
|
||||
state: present
|
||||
# Force and overwrite if checksums don't match
|
||||
module: s3
|
||||
bucket: mybucket
|
||||
path: /path/to/file
|
||||
state: present
|
||||
overwrite: yes
|
||||
'''
|
||||
|
||||
import sys
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
try:
|
||||
import boto
|
||||
import hashlib
|
||||
except ImportError:
|
||||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def upload_s3file(module, s3, bucket, key_name, path, expiry):
|
||||
try:
|
||||
key = bucket.new_key(key_name)
|
||||
key.set_contents_from_filename(path)
|
||||
url = key.generate_url(expiry)
|
||||
module.exit_json(msg="Put operation complete", url=url, changed=True)
|
||||
sys.exit(0)
|
||||
except s3.provider.storage_copy_error, e:
|
||||
module.fail_json(msg= str(e))
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
bucket = dict(),
|
||||
path = dict(),
|
||||
state = dict(choices=['present', 'absent']),
|
||||
expiry = dict(default=600),
|
||||
s3_url = dict(aliases=['S3_URL']),
|
||||
ec2_secret_key = dict(aliases=['EC2_SECRET_KEY']),
|
||||
ec2_access_key = dict(aliases=['EC2_ACCESS_KEY']),
|
||||
overwrite = dict(default="false", choices=BOOLEANS),
|
||||
),
|
||||
required_together=[ ['bucket', 'path', 'state'] ],
|
||||
)
|
||||
|
||||
bucket_name = module.params.get('bucket')
|
||||
path = os.path.expanduser(module.params['path'])
|
||||
state = module.params.get('state')
|
||||
expiry = int(module.params['expiry'])
|
||||
s3_url = module.params.get('s3_url')
|
||||
ec2_secret_key = module.params.get('ec2_secret_key')
|
||||
ec2_access_key = module.params.get('ec2_access_key')
|
||||
overwrite = module.boolean( module.params.get('overwrite') )
|
||||
|
||||
# allow eucarc environment variables to be used if ansible vars aren't set
|
||||
if not s3_url and 'S3_URL' in os.environ:
|
||||
s3_url = os.environ['S3_URL']
|
||||
if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ:
|
||||
ec2_secret_key = os.environ['EC2_SECRET_KEY']
|
||||
if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ:
|
||||
ec2_access_key = os.environ['EC2_ACCESS_KEY']
|
||||
|
||||
# If we have an S3_URL env var set, this is likely to be Walrus, so change connection method
|
||||
if 'S3_URL' in os.environ:
|
||||
try:
|
||||
walrus = urlparse.urlparse(s3_url).hostname
|
||||
s3 = boto.connect_walrus(walrus, ec2_access_key, ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
else:
|
||||
try:
|
||||
s3 = boto.connect_s3(ec2_access_key, ec2_secret_key)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
# README - Future features this module should have:
|
||||
# enhanced path (contents of a directory)
|
||||
# md5sum check of file vs. key in bucket
|
||||
# a user-friendly way to fetch the key (maybe a "fetch" parameter option)
|
||||
# persistent download URL if desired
|
||||
|
||||
# Lets get some information from the s3 connection, including bucket check ...
|
||||
bucket = s3.lookup(bucket_name)
|
||||
if bucket:
|
||||
bucket_exists = True
|
||||
else:
|
||||
bucket_exists = False
|
||||
|
||||
# Lets list the contents
|
||||
if bucket_exists is True:
|
||||
bucket_contents = bucket.list()
|
||||
|
||||
# Check filename is valid, if not downloading
|
||||
if path:
|
||||
if not os.path.exists(path):
|
||||
failed = True
|
||||
module.fail_json(msg="Source %s cannot be found" % (path), failed=failed)
|
||||
sys.exit(0)
|
||||
|
||||
# Default to setting the key to the same as the filename if not downloading. Adding custom key would be trivial.
|
||||
key_name = os.path.basename(path)
|
||||
|
||||
# Check to see if the key already exists
|
||||
if bucket_exists is True:
|
||||
try:
|
||||
key_check = bucket.get_key(key_name)
|
||||
if key_check:
|
||||
key_exists = True
|
||||
else:
|
||||
key_exists = False
|
||||
except s3.provider.storage_response_error, e:
|
||||
module.fail_json(msg= str(e))
|
||||
|
||||
if key_exists is True and overwrite is True:
|
||||
# Retrieve MD5 Checksums.
|
||||
md5_remote = key_check.etag[1:-1] # Strip Quotation marks from etag: https://code.google.com/p/boto/issues/detail?id=391
|
||||
etag_multipart = md5_remote.find('-')!=-1 # Find out if this is a multipart upload -> etag is not md5: https://forums.aws.amazon.com/message.jspa?messageID=222158
|
||||
if etag_multipart is True:
|
||||
module.fail_json(msg="Files uploaded with multipart to s3 are not supported with checksum. They do not contain a valid md5 checksum, use overwrite=no instead.")
|
||||
sys.exit(0)
|
||||
md5_local = hashlib.md5(open(path, 'rb').read()).hexdigest()
|
||||
md5_equal = md5_local == md5_remote
|
||||
|
||||
if state == 'present':
|
||||
if bucket_exists is True and key_exists is True:
|
||||
if overwrite is False:
|
||||
exists = True
|
||||
changed = False
|
||||
module.exit_json(msg="Bucket and key already exist", changed=changed)
|
||||
if overwrite is True:
|
||||
if md5_equal is True:
|
||||
module.exit_json(msg="Remote and local file checksums identical.", changed=False)
|
||||
if md5_equal is False:
|
||||
upload_s3file(module, s3, bucket, key_name, path, expiry)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
# If bucket exists, there cannot be a key within, lets create it ...
|
||||
if state == 'present':
|
||||
if bucket_exists is False:
|
||||
try:
|
||||
bucket = s3.create_bucket(bucket_name)
|
||||
bucket_exists = True
|
||||
key_exists = False
|
||||
changed = True
|
||||
except s3.provider.storage_create_error, e:
|
||||
module.fail_json(msg = str(e))
|
||||
|
||||
# If bucket now exists but key doesn't or overwrite is True, create the key
|
||||
if state == 'present':
|
||||
if bucket_exists is True and key_exists is False:
|
||||
upload_s3file(module, s3, bucket, key_name, path, expiry)
|
||||
|
||||
# If state is absent and the bucket exists (doesn't matter about key since the bucket is the container), delete it.
|
||||
if state == 'absent':
|
||||
if bucket_exists is True:
|
||||
try:
|
||||
for contents in bucket.list():
|
||||
bucket.delete_key(contents)
|
||||
s3.delete_bucket(bucket)
|
||||
changed = True
|
||||
module.exit_json(msg="Bucket and key removed.", changed=changed)
|
||||
sys.exit(0)
|
||||
except s3.provider.storage_response_error, e:
|
||||
module.fail_json(msg= str(e))
|
||||
else:
|
||||
changed = False
|
||||
module.exit_json(msg="Bucket and key do not exist", changed=changed)
|
||||
|
||||
# TO DO - ADD BUCKET DOWNLOAD OPTION
|
||||
# # If download is specified, fetch it
|
||||
# if download:
|
||||
# if bucket_exists is True and key_exists is True:
|
||||
# try:
|
||||
# getkey = bucket.lookup(key_name)
|
||||
# getkey.get_contents_to_filename(path)
|
||||
# url = getkey.generate_url(expiry)
|
||||
# module.exit_json(msg="GET operation complete", url=url, changed=True)
|
||||
# sys.exit(0)
|
||||
# except s3.provider.storage_copy_error, e:
|
||||
# module.fail_json(msg= str(e))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
||||
469
library/cloud/virt
Normal file
469
library/cloud/virt
Normal file
@@ -0,0 +1,469 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Virt management features
|
||||
|
||||
Copyright 2007, 2012 Red Hat, Inc
|
||||
Michael DeHaan <michael.dehaan@gmail.com>
|
||||
Seth Vidal <skvidal@fedoraproject.org>
|
||||
|
||||
This software may be freely redistributed under the terms of the GNU
|
||||
general public license.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: virt
|
||||
short_description: Manages virtual machines supported by libvirt
|
||||
description:
|
||||
- Manages virtual machines supported by I(libvirt).
|
||||
version_added: "0.2"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the guest VM being managed. Note that VM must be previously
|
||||
defined with xml.
|
||||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
state:
|
||||
description:
|
||||
- Note that there may be some lag for state requests like C(shutdown)
|
||||
since these refer only to VM states. After starting a guest, it may not
|
||||
be immediately accessible.
|
||||
required: false
|
||||
choices: [ "running", "shutdown" ]
|
||||
default: "no"
|
||||
command:
|
||||
description:
|
||||
- in addition to state management, various non-idempotent commands are available. See examples
|
||||
required: false
|
||||
choices: ["create","status", "start", "stop", "pause", "unpause",
|
||||
"shutdown", "undefine", "destroy", "get_xml", "autostart",
|
||||
"freemem", "list_vms", "info", "nodeinfo", "virttype", "define"]
|
||||
uri:
|
||||
description:
|
||||
- libvirt connection uri
|
||||
required: false
|
||||
defaults: qemu:///system
|
||||
xml:
|
||||
description:
|
||||
- XML document used with the define command
|
||||
required: false
|
||||
default: null
|
||||
requirements: [ "libvirt" ]
|
||||
author: Michael DeHaan, Seth Vidal
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# a playbook task line:
|
||||
tasks:
|
||||
- virt: name=alpha state=running
|
||||
|
||||
# /usr/bin/ansible invocations
|
||||
ansible host -m virt -a "name=alpha command=status"
|
||||
ansible host -m virt -a "name=alpha command=get_xml"
|
||||
ansible host -m virt -a "name=alpha command=create uri=lxc:///"
|
||||
|
||||
# a playbook example of defining and launching an LXC guest
|
||||
tasks:
|
||||
- name: define vm
|
||||
virt: name=foo command=define xml="{{ lookup('template', 'container-template.xml.j2') }}" uri=lxc:///
|
||||
- name: start vm
|
||||
virt: name=foo state=running uri=lxc:///
|
||||
'''
|
||||
|
||||
VIRT_FAILED = 1
|
||||
VIRT_SUCCESS = 0
|
||||
VIRT_UNAVAILABLE=2
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import libvirt
|
||||
except ImportError:
|
||||
print "failed=True msg='libvirt python module unavailable'"
|
||||
sys.exit(1)
|
||||
|
||||
ALL_COMMANDS = []
|
||||
VM_COMMANDS = ['create','status', 'start', 'stop', 'pause', 'unpause',
|
||||
'shutdown', 'undefine', 'destroy', 'get_xml', 'autostart', 'define']
|
||||
HOST_COMMANDS = ['freemem', 'list_vms', 'info', 'nodeinfo', 'virttype']
|
||||
ALL_COMMANDS.extend(VM_COMMANDS)
|
||||
ALL_COMMANDS.extend(HOST_COMMANDS)
|
||||
|
||||
VIRT_STATE_NAME_MAP = {
|
||||
0 : "running",
|
||||
1 : "running",
|
||||
2 : "running",
|
||||
3 : "paused",
|
||||
4 : "shutdown",
|
||||
5 : "shutdown",
|
||||
6 : "crashed"
|
||||
}
|
||||
|
||||
class VMNotFound(Exception): pass
|
||||
|
||||
class LibvirtConnection(object):
|
||||
|
||||
def __init__(self, uri):
|
||||
|
||||
cmd = subprocess.Popen("uname -r", shell=True, stdout=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
output = cmd.communicate()[0]
|
||||
|
||||
if output.find("xen") != -1:
|
||||
conn = libvirt.open(None)
|
||||
else:
|
||||
conn = libvirt.open(uri)
|
||||
|
||||
if not conn:
|
||||
raise Exception("hypervisor connection failure")
|
||||
|
||||
self.conn = conn
|
||||
|
||||
def find_vm(self, vmid):
|
||||
"""
|
||||
Extra bonus feature: vmid = -1 returns a list of everything
|
||||
"""
|
||||
conn = self.conn
|
||||
|
||||
vms = []
|
||||
|
||||
# this block of code borrowed from virt-manager:
|
||||
# get working domain's name
|
||||
ids = conn.listDomainsID()
|
||||
for id in ids:
|
||||
vm = conn.lookupByID(id)
|
||||
vms.append(vm)
|
||||
# get defined domain
|
||||
names = conn.listDefinedDomains()
|
||||
for name in names:
|
||||
vm = conn.lookupByName(name)
|
||||
vms.append(vm)
|
||||
|
||||
if vmid == -1:
|
||||
return vms
|
||||
|
||||
for vm in vms:
|
||||
if vm.name() == vmid:
|
||||
return vm
|
||||
|
||||
raise VMNotFound("virtual machine %s not found" % vmid)
|
||||
|
||||
def shutdown(self, vmid):
|
||||
return self.find_vm(vmid).shutdown()
|
||||
|
||||
def pause(self, vmid):
|
||||
return self.suspend(self.conn,vmid)
|
||||
|
||||
def unpause(self, vmid):
|
||||
return self.resume(self.conn,vmid)
|
||||
|
||||
def suspend(self, vmid):
|
||||
return self.find_vm(vmid).suspend()
|
||||
|
||||
def resume(self, vmid):
|
||||
return self.find_vm(vmid).resume()
|
||||
|
||||
def create(self, vmid):
|
||||
return self.find_vm(vmid).create()
|
||||
|
||||
def destroy(self, vmid):
|
||||
return self.find_vm(vmid).destroy()
|
||||
|
||||
def undefine(self, vmid):
|
||||
return self.find_vm(vmid).undefine()
|
||||
|
||||
def get_status2(self, vm):
|
||||
state = vm.info()[0]
|
||||
return VIRT_STATE_NAME_MAP.get(state,"unknown")
|
||||
|
||||
def get_status(self, vmid):
|
||||
state = self.find_vm(vmid).info()[0]
|
||||
return VIRT_STATE_NAME_MAP.get(state,"unknown")
|
||||
|
||||
def nodeinfo(self):
|
||||
return self.conn.getInfo()
|
||||
|
||||
def get_type(self):
|
||||
return self.conn.getType()
|
||||
|
||||
def get_maxVcpus(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.maxVcpus()
|
||||
|
||||
def get_maxMemory(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.maxMemory()
|
||||
|
||||
def getFreeMemory(self):
|
||||
return self.conn.getFreeMemory()
|
||||
|
||||
def get_autostart(self, vmid):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.autostart()
|
||||
|
||||
def set_autostart(self, vmid, val):
|
||||
vm = self.conn.lookupByName(vmid)
|
||||
return vm.setAutostart(val)
|
||||
|
||||
def define_from_xml(self, xml):
|
||||
return self.conn.defineXML(xml)
|
||||
|
||||
|
||||
class Virt(object):
|
||||
|
||||
def __init__(self, uri):
|
||||
self.uri = uri
|
||||
|
||||
def __get_conn(self):
|
||||
self.conn = LibvirtConnection(self.uri)
|
||||
return self.conn
|
||||
|
||||
def get_vm(self, vmid):
|
||||
self.__get_conn()
|
||||
return self.conn.find_vm(vmid)
|
||||
|
||||
def state(self):
|
||||
vms = self.list_vms()
|
||||
state = []
|
||||
for vm in vms:
|
||||
state_blurb = self.conn.get_status(vm)
|
||||
state.append("%s %s" % (vm,state_blurb))
|
||||
return state
|
||||
|
||||
def info(self):
|
||||
vms = self.list_vms()
|
||||
info = dict()
|
||||
for vm in vms:
|
||||
data = self.conn.find_vm(vm).info()
|
||||
# libvirt returns maxMem, memory, and cpuTime as long()'s, which
|
||||
# xmlrpclib tries to convert to regular int's during serialization.
|
||||
# This throws exceptions, so convert them to strings here and
|
||||
# assume the other end of the xmlrpc connection can figure things
|
||||
# out or doesn't care.
|
||||
info[vm] = {
|
||||
"state" : VIRT_STATE_NAME_MAP.get(data[0],"unknown"),
|
||||
"maxMem" : str(data[1]),
|
||||
"memory" : str(data[2]),
|
||||
"nrVirtCpu" : data[3],
|
||||
"cpuTime" : str(data[4]),
|
||||
}
|
||||
info[vm]["autostart"] = self.conn.get_autostart(vm)
|
||||
|
||||
return info
|
||||
|
||||
def nodeinfo(self):
|
||||
self.__get_conn()
|
||||
info = dict()
|
||||
data = self.conn.nodeinfo()
|
||||
info = {
|
||||
"cpumodel" : str(data[0]),
|
||||
"phymemory" : str(data[1]),
|
||||
"cpus" : str(data[2]),
|
||||
"cpumhz" : str(data[3]),
|
||||
"numanodes" : str(data[4]),
|
||||
"sockets" : str(data[5]),
|
||||
"cpucores" : str(data[6]),
|
||||
"cputhreads" : str(data[7])
|
||||
}
|
||||
return info
|
||||
|
||||
def list_vms(self):
|
||||
self.conn = self.__get_conn()
|
||||
vms = self.conn.find_vm(-1)
|
||||
results = []
|
||||
for x in vms:
|
||||
try:
|
||||
results.append(x.name())
|
||||
except:
|
||||
pass
|
||||
return results
|
||||
|
||||
def virttype(self):
|
||||
return self.__get_conn().get_type()
|
||||
|
||||
def autostart(self, vmid):
|
||||
self.conn = self.__get_conn()
|
||||
return self.conn.set_autostart(vmid, True)
|
||||
|
||||
def freemem(self):
|
||||
self.conn = self.__get_conn()
|
||||
return self.conn.getFreeMemory()
|
||||
|
||||
def shutdown(self, vmid):
|
||||
""" Make the machine with the given vmid stop running. Whatever that takes. """
|
||||
self.__get_conn()
|
||||
self.conn.shutdown(vmid)
|
||||
return 0
|
||||
|
||||
|
||||
def pause(self, vmid):
|
||||
""" Pause the machine with the given vmid. """
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.suspend(vmid)
|
||||
|
||||
def unpause(self, vmid):
|
||||
""" Unpause the machine with the given vmid. """
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.resume(vmid)
|
||||
|
||||
def create(self, vmid):
|
||||
""" Start the machine via the given vmid """
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.create(vmid)
|
||||
|
||||
def start(self, vmid):
|
||||
""" Start the machine via the given id/name """
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.create(vmid)
|
||||
|
||||
def destroy(self, vmid):
|
||||
""" Pull the virtual power from the virtual domain, giving it virtually no time to virtually shut down. """
|
||||
self.__get_conn()
|
||||
return self.conn.destroy(vmid)
|
||||
|
||||
def undefine(self, vmid):
|
||||
""" Stop a domain, and then wipe it from the face of the earth. (delete disk/config file) """
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.undefine(vmid)
|
||||
|
||||
def status(self, vmid):
|
||||
"""
|
||||
Return a state suitable for server consumption. Aka, codes.py values, not XM output.
|
||||
"""
|
||||
self.__get_conn()
|
||||
return self.conn.get_status(vmid)
|
||||
|
||||
def get_xml(self, vmid):
|
||||
"""
|
||||
Receive a Vm id as input
|
||||
Return an xml describing vm config returned by a libvirt call
|
||||
"""
|
||||
|
||||
conn = libvirt.openReadOnly(None)
|
||||
if not conn:
|
||||
return (-1,'Failed to open connection to the hypervisor')
|
||||
try:
|
||||
domV = conn.lookupByName(vmid)
|
||||
except:
|
||||
return (-1,'Failed to find the main domain')
|
||||
return domV.XMLDesc(0)
|
||||
|
||||
def get_maxVcpus(self, vmid):
|
||||
"""
|
||||
Gets the max number of VCPUs on a guest
|
||||
"""
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.get_maxVcpus(vmid)
|
||||
|
||||
def get_max_memory(self, vmid):
|
||||
"""
|
||||
Gets the max memory on a guest
|
||||
"""
|
||||
|
||||
self.__get_conn()
|
||||
return self.conn.get_MaxMemory(vmid)
|
||||
|
||||
def define(self, xml):
|
||||
"""
|
||||
Define a guest with the given xml
|
||||
"""
|
||||
self.__get_conn()
|
||||
return self.conn.define_from_xml(xml)
|
||||
|
||||
def core(module):
|
||||
|
||||
state = module.params.get('state', None)
|
||||
guest = module.params.get('name', None)
|
||||
command = module.params.get('command', None)
|
||||
uri = module.params.get('uri', None)
|
||||
xml = module.params.get('xml', None)
|
||||
|
||||
v = Virt(uri)
|
||||
res = {}
|
||||
|
||||
|
||||
if state:
|
||||
if not guest:
|
||||
module.fail_json(msg = "state change requires a guest specified")
|
||||
|
||||
res['changed'] = False
|
||||
if state == 'running':
|
||||
if v.status(guest) is not 'running':
|
||||
res['changed'] = True
|
||||
res['msg'] = v.start(guest)
|
||||
elif state == 'shutdown':
|
||||
if v.status(guest) is not 'shutdown':
|
||||
res['changed'] = True
|
||||
res['msg'] = v.shutdown(guest)
|
||||
else:
|
||||
module.fail_json(msg="unexpected state")
|
||||
|
||||
return VIRT_SUCCESS, res
|
||||
|
||||
if command:
|
||||
if command in VM_COMMANDS:
|
||||
if not guest:
|
||||
module.fail_json(msg = "%s requires 1 argument: guest" % command)
|
||||
if command == 'define':
|
||||
if not xml:
|
||||
module.fail_json(msg = "define requires xml argument")
|
||||
try:
|
||||
v.get_vm(guest)
|
||||
except VMNotFound:
|
||||
v.define(xml)
|
||||
res = {'changed': True, 'created': guest}
|
||||
return VIRT_SUCCESS, res
|
||||
res = getattr(v, command)(guest)
|
||||
if type(res) != dict:
|
||||
res = { command: res }
|
||||
return VIRT_SUCCESS, res
|
||||
|
||||
elif hasattr(v, command):
|
||||
res = getattr(v, command)()
|
||||
if type(res) != dict:
|
||||
res = { command: res }
|
||||
return VIRT_SUCCESS, res
|
||||
|
||||
else:
|
||||
module.fail_json(msg="Command %s not recognized" % basecmd)
|
||||
|
||||
module.fail_json(msg="expected state or command parameter to be specified")
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(argument_spec=dict(
|
||||
name = dict(aliases=['guest']),
|
||||
state = dict(choices=['running', 'shutdown']),
|
||||
command = dict(choices=ALL_COMMANDS),
|
||||
uri = dict(default='qemu:///system'),
|
||||
xml = dict(),
|
||||
))
|
||||
|
||||
rc = VIRT_SUCCESS
|
||||
try:
|
||||
rc, result = core(module)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
if rc != 0: # something went wrong emit the msg
|
||||
module.fail_json(rc=rc, msg=result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
||||
Reference in New Issue
Block a user