Allow modules to be categorized, and also sort them when generating the documentation.

This commit is contained in:
Michael DeHaan
2013-04-28 15:03:45 -04:00
parent f46bdb6343
commit 391fb98ee2
87 changed files with 118 additions and 67 deletions

View 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
View 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
View 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
View 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
View 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
View 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
View 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()