Merge branch 'devel' into mysql_anon_user

Conflicts:
	database/mysql/mysql_user.py
reviewable/pr18780/r1
Lee H 9 years ago
commit b5d7becc29

@ -247,6 +247,12 @@ options:
required: false
default: null
aliases: ['network_interface']
spot_launch_group:
version_added: "2.1"
description:
- Launch group for spot request, see U(http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-spot-instances-work.html#spot-launch-group)
required: false
default: null
author:
- "Tim Gerla (@tgerla)"
@ -303,6 +309,22 @@ EXAMPLES = '''
vpc_subnet_id: subnet-29e63245
assign_public_ip: yes
# Single instance with ssd gp2 root volume
- ec2:
key_name: mykey
group: webserver
instance_type: c3.medium
image: ami-123456
wait: yes
wait_timeout: 500
volumes:
- device_name: /dev/xvda
volume_type: gp2
volume_size: 8
vpc_subnet_id: subnet-29e63245
assign_public_ip: yes
exact_count: 1
# Multiple groups example
- ec2:
key_name: mykey
@ -358,6 +380,7 @@ EXAMPLES = '''
wait: yes
vpc_subnet_id: subnet-29e63245
assign_public_ip: yes
spot_launch_group: report_generators
# Examples using pre-existing network interfaces
- ec2:
@ -481,7 +504,6 @@ EXAMPLES = '''
#
- ec2:
state: running
key_name: mykey
instance_type: c1.medium
image: ami-40603AD1
@ -499,7 +521,6 @@ EXAMPLES = '''
#
- ec2:
state: running
key_name: mykey
instance_type: c1.medium
image: ami-40603AD1
@ -860,6 +881,7 @@ def create_instances(module, ec2, vpc, override_count=None):
source_dest_check = module.boolean(module.params.get('source_dest_check'))
termination_protection = module.boolean(module.params.get('termination_protection'))
network_interfaces = module.params.get('network_interfaces')
spot_launch_group = module.params.get('spot_launch_group')
# group_id and group_name are exclusive of each other
if group_id and group_name:
@ -883,6 +905,9 @@ def create_instances(module, ec2, vpc, override_count=None):
grp_details = ec2.get_all_security_groups()
if isinstance(group_name, basestring):
group_name = [group_name]
unmatched = set(group_name).difference(str(grp.name) for grp in grp_details)
if len(unmatched) > 0:
module.fail_json(msg="The following group names are not valid: %s" % ', '.join(unmatched))
group_id = [ str(grp.id) for grp in grp_details if str(grp.name) in group_name ]
# Now we try to lookup the group id testing if group exists.
elif group_id:
@ -1042,6 +1067,9 @@ def create_instances(module, ec2, vpc, override_count=None):
module.fail_json(
msg="placement_group parameter requires Boto version 2.3.0 or higher.")
if spot_launch_group and isinstance(spot_launch_group, basestring):
params['launch_group'] = spot_launch_group
params.update(dict(
count = count_remaining,
type = spot_type,
@ -1310,6 +1338,7 @@ def main():
instance_type = dict(aliases=['type']),
spot_price = dict(),
spot_type = dict(default='one-time', choices=["one-time", "persistent"]),
spot_launch_group = dict(),
image = dict(),
kernel = dict(),
count = dict(type='int', default='1'),

@ -152,9 +152,9 @@ EXAMPLES = '''
# Rolling ASG Updates
Below is an example of how to assign a new launch config to an ASG and terminate old instances.
Below is an example of how to assign a new launch config to an ASG and terminate old instances.
All instances in "myasg" that do not have the launch configuration named "my_new_lc" will be terminated in
All instances in "myasg" that do not have the launch configuration named "my_new_lc" will be terminated in
a rolling fashion with instances using the current launch configuration, "my_new_lc".
This could also be considered a rolling deploy of a pre-baked AMI.
@ -281,7 +281,6 @@ def get_properties(autoscaling_group):
if getattr(autoscaling_group, "tags", None):
properties['tags'] = dict((t.key, t.value) for t in autoscaling_group.tags)
return properties
def elb_dreg(asg_connection, module, group_name, instance_id):
@ -298,7 +297,6 @@ def elb_dreg(asg_connection, module, group_name, instance_id):
else:
return
exists = True
for lb in as_group.load_balancers:
elb_connection.deregister_instances(lb, instance_id)
log.debug("De-registering {0} from ELB {1}".format(instance_id, lb))
@ -315,10 +313,8 @@ def elb_dreg(asg_connection, module, group_name, instance_id):
time.sleep(10)
if wait_timeout <= time.time():
# waiting took too long
# waiting took too long
module.fail_json(msg = "Waited too long for instance to deregister. {0}".format(time.asctime()))
def elb_healthy(asg_connection, elb_connection, module, group_name):
@ -337,7 +333,7 @@ def elb_healthy(asg_connection, elb_connection, module, group_name):
# but has not yet show up in the ELB
try:
lb_instances = elb_connection.describe_instance_health(lb, instances=instances)
except boto.exception.InvalidInstance, e:
except boto.exception.InvalidInstance:
pass
for i in lb_instances:
if i.state == "InService":
@ -346,7 +342,6 @@ def elb_healthy(asg_connection, elb_connection, module, group_name):
return len(healthy_instances)
def wait_for_elb(asg_connection, module, group_name):
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
wait_timeout = module.params.get('wait_timeout')
@ -370,7 +365,7 @@ def wait_for_elb(asg_connection, module, group_name):
log.debug("ELB thinks {0} instances are healthy.".format(healthy_instances))
time.sleep(10)
if wait_timeout <= time.time():
# waiting took too long
# waiting took too long
module.fail_json(msg = "Waited too long for ELB instances to be healthy. %s" % time.asctime())
log.debug("Waiting complete. ELB thinks {0} instances are healthy.".format(healthy_instances))
@ -396,7 +391,7 @@ def create_autoscaling_group(connection, module):
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
try:
ec2_connection = connect_to_aws(boto.ec2, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg=str(e))
elif vpc_zone_identifier:
vpc_zone_identifier = ','.join(vpc_zone_identifier)
@ -433,7 +428,7 @@ def create_autoscaling_group(connection, module):
try:
connection.create_auto_scaling_group(ag)
if wait_for_instances == True:
if wait_for_instances:
wait_for_new_inst(module, connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
wait_for_elb(connection, module, group_name)
as_group = connection.get_all_groups(names=[group_name])[0]
@ -475,7 +470,7 @@ def create_autoscaling_group(connection, module):
dead_tags = []
for tag in as_group.tags:
have_tags[tag.key] = [tag.value, tag.propagate_at_launch]
if not tag.key in want_tags:
if tag.key not in want_tags:
changed = True
dead_tags.append(tag)
@ -492,14 +487,13 @@ def create_autoscaling_group(connection, module):
changed = True
as_group.load_balancers = module.params.get('load_balancers')
if changed:
try:
as_group.update()
except BotoServerError, e:
module.fail_json(msg=str(e))
if wait_for_instances == True:
if wait_for_instances:
wait_for_new_inst(module, connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
wait_for_elb(connection, module, group_name)
try:
@ -525,7 +519,7 @@ def delete_autoscaling_group(connection, module):
if tmp_groups:
tmp_group = tmp_groups[0]
if not tmp_group.instances:
instances = False
instances = False
time.sleep(10)
group.delete()
@ -580,15 +574,15 @@ def replace(connection, module):
changed = True
return(changed, props)
# we don't want to spin up extra instances if not necessary
# we don't want to spin up extra instances if not necessary
if num_new_inst_needed < batch_size:
log.debug("Overriding batch size to {0}".format(num_new_inst_needed))
batch_size = num_new_inst_needed
log.debug("Overriding batch size to {0}".format(num_new_inst_needed))
batch_size = num_new_inst_needed
if not old_instances:
changed = False
return(changed, props)
#check if min_size/max_size/desired capacity have been specified and if not use ASG values
if min_size is None:
min_size = as_group.min_size
@ -637,7 +631,7 @@ def get_instances_by_lc(props, lc_check, initial_instances):
new_instances.append(i)
else:
old_instances.append(i)
else:
log.debug("Comparing initial instances with current: {0}".format(initial_instances))
for i in props['instances']:
@ -659,10 +653,10 @@ def list_purgeable_instances(props, lc_check, replace_instances, initial_instanc
# and they have a non-current launch config
if lc_check:
for i in instances:
if props['instance_facts'][i]['launch_config_name'] != props['launch_config_name']:
if props['instance_facts'][i]['launch_config_name'] != props['launch_config_name']:
instances_to_terminate.append(i)
else:
for i in instances:
for i in instances:
if i in initial_instances:
instances_to_terminate.append(i)
return instances_to_terminate
@ -676,7 +670,7 @@ def terminate_batch(connection, module, replace_instances, initial_instances, le
lc_check = module.params.get('lc_check')
decrement_capacity = False
break_loop = False
as_group = connection.get_all_groups(names=[group_name])[0]
props = get_properties(as_group)
desired_size = as_group.min_size
@ -720,7 +714,7 @@ def terminate_batch(connection, module, replace_instances, initial_instances, le
elb_dreg(connection, module, group_name, instance_id)
log.debug("terminating instance: {0}".format(instance_id))
connection.terminate_instance(instance_id, decrement_capacity=decrement_capacity)
# we wait to make sure the machines we marked as Unhealthy are
# no longer in the list
@ -756,7 +750,7 @@ def wait_for_term_inst(connection, module, term_instances):
# waiting took too long
module.fail_json(msg = "Waited too long for old instances to terminate. %s" % time.asctime())
def wait_for_new_inst(module, connection, group_name, wait_timeout, desired_size, prop):
# make sure we have the latest stats after that last loop.
@ -802,9 +796,9 @@ def main():
termination_policies=dict(type='list', default='Default')
),
)
module = AnsibleModule(
argument_spec=argument_spec,
argument_spec=argument_spec,
mutually_exclusive = [['replace_all_instances', 'replace_instances']]
)
@ -826,13 +820,13 @@ def main():
if state == 'present':
create_changed, asg_properties=create_autoscaling_group(connection, module)
elif state == 'absent':
changed = delete_autoscaling_group(connection, module)
module.exit_json( changed = changed )
changed = delete_autoscaling_group(connection, module)
module.exit_json( changed = changed )
if replace_all_instances or replace_instances:
replace_changed, asg_properties=replace(connection, module)
if create_changed or replace_changed:
changed = True
module.exit_json( changed = changed, **asg_properties )
main()
if __name__ == '__main__':
main()

@ -50,10 +50,10 @@ options:
choices: [ "yes", "no" ]
wait:
description:
- Wait for instance registration or deregistration to complete successfully before returning.
- Wait for instance registration or deregistration to complete successfully before returning.
required: false
default: yes
choices: [ "yes", "no" ]
choices: [ "yes", "no" ]
validate_certs:
description:
- When set to "no", SSL certificates will not be validated for boto versions >= 2.6.0.
@ -87,7 +87,7 @@ roles:
- myrole
post_tasks:
- name: Instance Register
local_action:
local_action:
module: ec2_elb
instance_id: "{{ ansible_ec2_instance_id }}"
ec2_elbs: "{{ item }}"
@ -256,12 +256,23 @@ class ElbManager:
ec2_elbs = self._get_auto_scaling_group_lbs()
try:
elb = connect_to_aws(boto.ec2.elb, self.region,
**self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
elb = connect_to_aws(boto.ec2.elb, self.region, **self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
self.module.fail_json(msg=str(e))
elbs = elb.get_all_load_balancers()
elbs = []
marker = None
while True:
try:
newelbs = elb.get_all_load_balancers(marker=marker)
marker = newelbs.next_marker
elbs.extend(newelbs)
if not marker:
break
except TypeError:
# Older version of boto do not allow for params
elbs = elb.get_all_load_balancers()
break
if ec2_elbs:
lbs = sorted(lb for lb in elbs if lb.name in ec2_elbs)
@ -279,7 +290,7 @@ class ElbManager:
try:
asg = connect_to_aws(boto.ec2.autoscale, self.region, **self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
self.module.fail_json(msg=str(e))
asg_instances = asg.get_all_autoscaling_instances([self.instance_id])
@ -302,9 +313,8 @@ class ElbManager:
def _get_instance(self):
"""Returns a boto.ec2.InstanceObject for self.instance_id"""
try:
ec2 = connect_to_aws(boto.ec2, self.region,
**self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
ec2 = connect_to_aws(boto.ec2, self.region, **self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
self.module.fail_json(msg=str(e))
return ec2.get_only_instances(instance_ids=[self.instance_id])[0]
@ -330,7 +340,7 @@ def main():
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if not region:
if not region:
module.fail_json(msg="Region must be specified as a parameter, in EC2_REGION or AWS_REGION environment variables or in boto configuration file")
ec2_elbs = module.params['ec2_elbs']
@ -342,8 +352,7 @@ def main():
module.fail_json(msg="ELBs are required for registration")
instance_id = module.params['instance_id']
elb_man = ElbManager(module, instance_id, ec2_elbs,
region=region, **aws_connect_params)
elb_man = ElbManager(module, instance_id, ec2_elbs, region=region, **aws_connect_params)
if ec2_elbs is not None:
for elb in ec2_elbs:
@ -365,4 +374,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -107,6 +107,7 @@ options:
description:
- Wait a specified timeout allowing connections to drain before terminating an instance
required: false
default: "None"
aliases: []
version_added: "1.8"
idle_timeout:
@ -491,7 +492,7 @@ class ElbManager(object):
try:
return connect_to_aws(boto.ec2.elb, self.region,
**self.aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
self.module.fail_json(msg=str(e))
def _delete_elb(self):
@ -980,4 +981,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -311,7 +311,7 @@ def main():
try:
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg=str(e))
state = module.params.get('state')

@ -115,8 +115,6 @@ EXAMPLES = '''
'''
import sys
try:
import boto.ec2.cloudwatch
from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm
@ -270,11 +268,11 @@ def main():
state = module.params.get('state')
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if region:
try:
connection = connect_to_aws(boto.ec2.cloudwatch, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg=str(e))
else:
module.fail_json(msg="region must be specified")
@ -288,4 +286,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -178,7 +178,7 @@ def main():
try:
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg = str(e))
if state == 'present':
@ -187,4 +187,5 @@ def main():
delete_scaling_policy(connection, module)
main()
if __name__ == '__main__':
main()

@ -74,7 +74,7 @@ options:
- If the volume's most recent snapshot has started less than `last_snapshot_min_age' minutes ago, a new snapshot will not be created.
required: false
default: 0
version_added: "1.9"
version_added: "2.0"
author: "Will Thames (@willthames)"
extends_documentation_fragment:

@ -47,7 +47,7 @@ options:
volume_type:
description:
- Type of EBS volume; standard (magnetic), gp2 (SSD), io1 (Provisioned IOPS). "Standard" is the old EBS default
and continues to remain the Ansible default for backwards compatibility.
and continues to remain the Ansible default for backwards compatibility.
required: false
default: standard
version_added: "1.9"
@ -69,7 +69,7 @@ options:
default: null
zone:
description:
- zone in which to create the volume, if unset uses the zone the instance is in (if set)
- zone in which to create the volume, if unset uses the zone the instance is in (if set)
required: false
default: null
aliases: ['aws_zone', 'ec2_zone']
@ -87,7 +87,7 @@ options:
choices: ["yes", "no"]
version_added: "1.5"
state:
description:
description:
- whether to ensure the volume is present or absent, or to list existing volumes (The C(list) option was added in version 1.8).
required: false
default: present
@ -101,15 +101,15 @@ extends_documentation_fragment:
EXAMPLES = '''
# Simple attachment action
- ec2_vol:
instance: XXXXXX
volume_size: 5
- ec2_vol:
instance: XXXXXX
volume_size: 5
device_name: sdd
# Example using custom iops params
# Example using custom iops params
- ec2_vol:
instance: XXXXXX
volume_size: 5
instance: XXXXXX
volume_size: 5
iops: 100
device_name: sdd
@ -118,15 +118,15 @@ EXAMPLES = '''
instance: XXXXXX
snapshot: "{{ snapshot }}"
# Playbook example combined with instance launch
# Playbook example combined with instance launch
- ec2:
keypair: "{{ keypair }}"
image: "{{ image }}"
wait: yes
wait: yes
count: 3
register: ec2
- ec2_vol:
instance: "{{ item.id }} "
instance: "{{ item.id }} "
volume_size: 5
with_items: ec2.instances
register: ec2_vol
@ -223,7 +223,7 @@ def get_volume(module, ec2):
return vols[0]
def get_volumes(module, ec2):
instance = module.params.get('instance')
try:
@ -254,12 +254,10 @@ def boto_supports_volume_encryption():
"""
return hasattr(boto, 'Version') and LooseVersion(boto.Version) >= LooseVersion('2.29.0')
def create_volume(module, ec2, zone):
changed = False
name = module.params.get('name')
id = module.params.get('id')
instance = module.params.get('instance')
iops = module.params.get('iops')
encrypted = module.params.get('encrypted')
volume_size = module.params.get('volume_size')
@ -292,16 +290,16 @@ def create_volume(module, ec2, zone):
def attach_volume(module, ec2, volume, instance):
device_name = module.params.get('device_name')
changed = False
# 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:
try:
@ -311,7 +309,7 @@ def attach_volume(module, ec2, volume, instance):
device_name = '/dev/xvdf'
except boto.exception.BotoServerError, e:
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
if volume.attachment_state() is not None:
adata = volume.attach_data
if adata.instance_id != instance.id:
@ -330,9 +328,9 @@ def attach_volume(module, ec2, volume, instance):
return volume, changed
def detach_volume(module, ec2, volume):
changed = False
if volume.attachment_state() is not None:
adata = volume.attach_data
volume.detach()
@ -340,15 +338,15 @@ def detach_volume(module, ec2, volume):
time.sleep(3)
volume.update()
changed = True
return volume, changed
def get_volume_info(volume, state):
# If we're just listing volumes then do nothing, else get the latest update for the volume
if state != 'list':
volume.update()
volume_info = {}
attachment = volume.attach_data
@ -369,7 +367,7 @@ def get_volume_info(volume, state):
},
'tags': volume.tags
}
return volume_info
def main():
@ -397,34 +395,32 @@ def main():
name = module.params.get('name')
instance = module.params.get('instance')
volume_size = module.params.get('volume_size')
volume_type = module.params.get('volume_type')
iops = module.params.get('iops')
encrypted = module.params.get('encrypted')
device_name = module.params.get('device_name')
zone = module.params.get('zone')
snapshot = module.params.get('snapshot')
state = module.params.get('state')
# Ensure we have the zone or can get the zone
if instance is None and zone is None and state == 'present':
module.fail_json(msg="You must specify either instance or zone")
# Set volume detach flag
if instance == 'None' or instance == '':
instance = None
detach_vol_flag = True
else:
detach_vol_flag = False
# Set changed flag
changed = False
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if region:
try:
ec2 = connect_to_aws(boto.ec2, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg=str(e))
else:
module.fail_json(msg="region must be specified")
@ -471,11 +467,11 @@ def main():
if volume_size and (id or snapshot):
module.fail_json(msg="Cannot specify volume_size together with id or snapshot")
if state == 'present':
volume, changed = create_volume(module, ec2, zone)
if detach_vol_flag:
volume, changed = detach_volume(module, ec2, volume)
volume, changed = detach_volume(module, ec2, volume)
elif inst is not None:
volume, changed = attach_volume(module, ec2, volume, inst)
@ -489,4 +485,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -93,9 +93,6 @@ EXAMPLES = '''
'''
import time
import sys
try:
import boto
import boto.ec2
@ -136,15 +133,15 @@ def vpc_exists(module, vpc, name, cidr_block, multi):
module.fail_json(msg='Currently there are %d VPCs that have the same name and '
'CIDR block you specified. If you would like to create '
'the VPC anyway please pass True to the multi_ok param.' % len(matching_vpcs))
return matched_vpc
def update_vpc_tags(vpc, module, vpc_obj, tags, name):
if tags is None:
tags = dict()
tags.update({'Name': name})
try:
current_tags = dict((t.name, t.value) for t in vpc.get_all_tags(filters={'resource-id': vpc_obj.id}))
@ -156,10 +153,10 @@ def update_vpc_tags(vpc, module, vpc_obj, tags, name):
except Exception, e:
e_msg=boto_exception(e)
module.fail_json(msg=e_msg)
def update_dhcp_opts(connection, module, vpc_obj, dhcp_id):
if vpc_obj.dhcp_options_id != dhcp_id:
connection.associate_dhcp_options(dhcp_id, vpc_obj.id)
return True
@ -211,48 +208,47 @@ def main():
tags=module.params.get('tags')
state=module.params.get('state')
multi=module.params.get('multi_ok')
changed=False
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if region:
try:
connection = connect_to_aws(boto.vpc, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, StandardError), e:
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
module.fail_json(msg=str(e))
else:
module.fail_json(msg="region must be specified")
if dns_hostnames and not dns_support:
module.fail_json('In order to enable DNS Hostnames you must also enable DNS support')
if state == 'present':
# Check if VPC exists
vpc_obj = vpc_exists(module, connection, name, cidr_block, multi)
if vpc_obj is None:
try:
vpc_obj = connection.create_vpc(cidr_block, instance_tenancy=tenancy)
changed = True
except BotoServerError, e:
module.fail_json(msg=e)
if dhcp_id is not None:
if dhcp_id is not None:
try:
if update_dhcp_opts(connection, module, vpc_obj, dhcp_id):
changed = True
except BotoServerError, e:
module.fail_json(msg=e)
if tags is not None or name is not None:
if tags is not None or name is not None:
try:
if update_vpc_tags(connection, module, vpc_obj, tags, name):
changed = True
except BotoServerError, e:
module.fail_json(msg=e)
# Note: Boto currently doesn't currently provide an interface to ec2-describe-vpc-attribute
# which is needed in order to detect the current status of DNS options. For now we just update
@ -263,21 +259,21 @@ def main():
except BotoServerError, e:
e_msg=boto_exception(e)
module.fail_json(msg=e_msg)
# get the vpc obj again in case it has changed
try:
vpc_obj = connection.get_all_vpcs(vpc_obj.id)[0]
except BotoServerError, e:
e_msg=boto_exception(e)
module.fail_json(msg=e_msg)
module.exit_json(changed=changed, vpc=get_vpc_values(vpc_obj))
elif state == 'absent':
# Check if VPC exists
vpc_obj = vpc_exists(module, connection, name, cidr_block, multi)
if vpc_obj is not None:
try:
connection.delete_vpc(vpc_obj.id)
@ -287,11 +283,12 @@ def main():
e_msg = boto_exception(e)
module.fail_json(msg="%s. You may want to use the ec2_vpc_subnet, ec2_vpc_igw, "
"and/or ec2_vpc_route_table modules to ensure the other components are absent." % e_msg)
module.exit_json(changed=changed, vpc=get_vpc_values(vpc_obj))
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -192,14 +192,24 @@ def create_user(module, iam, name, pwd, path, key_state, key_count):
def delete_user(module, iam, name):
del_meta = ''
try:
current_keys = [ck['access_key_id'] for ck in
iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
for key in current_keys:
iam.delete_access_key(key, name)
del_meta = iam.delete_user(name).delete_user_response
except boto.exception.BotoServerError, err:
error_msg = boto_exception(err)
try:
login_profile = iam.get_login_profiles(name).get_login_profile_response
except boto.exception.BotoServerError, err:
error_msg = boto_exception(err)
if ('Cannot find Login Profile') in error_msg:
del_meta = iam.delete_user(name).delete_user_response
else:
iam.delete_login_profile(name)
del_meta = iam.delete_user(name).delete_user_response
except Exception as ex:
module.fail_json(changed=False, msg="delete failed %s" %ex)
if ('must detach all policies first') in error_msg:
for policy in iam.get_all_user_policies(name).list_user_policies_result.policy_names:
iam.delete_user_policy(name, policy)
@ -213,7 +223,7 @@ def delete_user(module, iam, name):
"currently supported by boto. Please detach the polices "
"through the console and try again." % name)
else:
module.fail_json(changed=changed, msg=str(err))
module.fail_json(changed=changed, msg=str(error_msg))
else:
changed = True
return del_meta, name, changed
@ -650,15 +660,20 @@ def main():
else:
module.exit_json(
changed=changed, groups=user_groups, user_name=name, keys=key_list)
elif state == 'update' and not user_exists:
module.fail_json(
msg="The user %s does not exit. No update made." % name)
elif state == 'absent':
if name in orig_user_list:
set_users_groups(module, iam, name, '')
del_meta, name, changed = delete_user(module, iam, name)
module.exit_json(
deletion_meta=del_meta, deleted_user=name, changed=changed)
if user_exists:
try:
set_users_groups(module, iam, name, '')
del_meta, name, changed = delete_user(module, iam, name)
module.exit_json(deleted_user=name, changed=changed)
except Exception as ex:
module.fail_json(changed=changed, msg=str(ex))
else:
module.exit_json(
changed=False, msg="User %s is already absent from your AWS IAM users" % name)
@ -690,9 +705,11 @@ def main():
if not new_path and not new_name:
module.exit_json(
changed=changed, group_name=name, group_path=cur_path)
elif state == 'update' and not group_exists:
module.fail_json(
changed=changed, msg="Update Failed. Group %s doesn't seem to exit!" % name)
elif state == 'absent':
if name in orig_group_list:
removed_group, changed = delete_group(iam=iam, name=name)

@ -64,9 +64,9 @@ extends_documentation_fragment:
'''
EXAMPLES = '''
# Create and policy with the name of 'Admin' to the group 'administrators'
# Create a policy with the name of 'Admin' to the group 'administrators'
tasks:
- name: Create two new IAM users with API keys
- name: Assign a policy called Admin to the administrators group
iam_policy:
iam_type: group
iam_name: administrators
@ -87,7 +87,7 @@ task:
- Luigi
register: new_groups
- name:
- name: Apply READ-ONLY policy to new groups that have been recently created
iam_policy:
iam_type: group
iam_name: "{{ item.created_group.group_name }}"
@ -188,7 +188,7 @@ def role_action(module, iam, name, policy_name, skip, pdoc, state):
# Role doesn't exist so it's safe to assume the policy doesn't either
module.exit_json(changed=False)
else:
module.fail_json(e.message)
module.fail_json(msg=e.message)
try:
for pol in current_policies:

@ -829,13 +829,17 @@ def promote_db_instance(module, conn):
instance_name = module.params.get('instance_name')
result = conn.get_db_instance(instance_name)
if not result:
module.fail_json(msg="DB Instance %s does not exist" % instance_name)
if result.get_data().get('replication_source'):
changed = False
else:
try:
result = conn.promote_read_replica(instance_name, **params)
changed = True
except RDSException, e:
module.fail_json(msg=e.message)
else:
changed = False
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)

@ -112,7 +112,7 @@ except ImportError:
# returns a tuple: (whether or not a parameter was changed, the remaining parameters that weren't found in this parameter group)
class NotModifiableError(StandardError):
class NotModifiableError(Exception):
def __init__(self, error_message, *args):
super(NotModifiableError, self).__init__(error_message, *args)
self.error_message = error_message
@ -175,7 +175,7 @@ def modify_group(group, params, immediate=False):
new_params = dict(params)
for key in new_params.keys():
if group.has_key(key):
if key in group:
param = group[key]
new_value = new_params[key]
@ -281,7 +281,6 @@ def main():
else:
break
except BotoServerError, e:
module.fail_json(msg = e.error_message)
@ -297,4 +296,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
main()
if __name__ == '__main__':
main()

@ -295,7 +295,7 @@ def main():
overwrite = dict(required=False, type='bool'),
retry_interval = dict(required=False, default=500),
private_zone = dict(required=False, type='bool', default=False),
identifier = dict(required=False),
identifier = dict(required=False, default=None),
weight = dict(required=False, type='int'),
region = dict(required=False),
health_check = dict(required=False),
@ -391,7 +391,7 @@ def main():
#Need to save this changes in rset, because of comparing rset.to_xml() == wanted_rset.to_xml() in next block
rset.name = decoded_name
if rset.type == type_in and decoded_name.lower() == record_in.lower() and rset.identifier == str(identifier_in):
if rset.type == type_in and decoded_name.lower() == record_in.lower() and str(rset.identifier) == str(identifier_in):
found_record = True
record['zone'] = zone_in
record['type'] = rset.type

@ -37,6 +37,7 @@ options:
api_token:
description:
- DigitalOcean api token.
version_added: "1.9.5"
id:
description:
- Numeric, the droplet id you want to operate on.
@ -100,8 +101,9 @@ options:
notes:
- Two environment variables can be used, DO_API_KEY and DO_API_TOKEN. They both refer to the v2 token.
- As of Ansible 2.0, Version 2 of the DigitalOcean API is used.
- As of Ansible 2.0, the above parameters were changed significantly. If you are running 1.9.x or earlier, please use C(ansible-doc digital_ocean) to view the correct parameters for your version. Dedicated web docs will be available in the near future for the stable branch.
- As of Ansible 1.9.5 and 2.0, Version 2 of the DigitalOcean API is used, this removes C(client_id) and C(api_key) options in favor of C(api_token).
- If you are running Ansible 1.9.4 or earlier you might not be able to use the included version of this module as the API version used has been retired.
Upgrade Ansible or, if unable to, try downloading the latest version of this module from github and putting it into a 'library' directory.
requirements:
- "python >= 2.6"
- dopy

@ -29,12 +29,10 @@ options:
- Indicate desired state of the target.
default: present
choices: ['present', 'absent']
client_id:
description:
- DigitalOcean manager id.
api_key:
api_token:
description:
- DigitalOcean api key.
- DigitalOcean api token.
version_added: "1.9.5"
id:
description:
- Numeric, the droplet id you want to operate on.
@ -46,8 +44,9 @@ options:
- The IP address to point a domain at.
notes:
- Two environment variables can be used, DO_CLIENT_ID and DO_API_KEY.
- Version 1 of DigitalOcean API is used.
- Two environment variables can be used, DO_API_KEY and DO_API_TOKEN. They both refer to the v2 token.
- As of Ansible 1.9.5 and 2.0, Version 2 of the DigitalOcean API is used, this removes C(client_id) and C(api_key) options in favor of C(api_token).
- If you are running Ansible 1.9.4 or earlier you might not be able to use the included version of this module as the API version used has been retired.
requirements:
- "python >= 2.6"
@ -68,9 +67,9 @@ EXAMPLES = '''
- digital_ocean: >
state=present
name=test_droplet
size_id=1
region_id=2
image_id=3
size_id=1gb
region_id=sgp1
image_id=ubuntu-14-04-x64
register: test_droplet
- digital_ocean_domain: >
@ -135,8 +134,8 @@ class Domain(JsonfyMixIn):
return cls(json)
@classmethod
def setup(cls, client_id, api_key):
cls.manager = DoManager(client_id, api_key)
def setup(cls, api_token):
cls.manager = DoManager(None, api_token, api_version=2)
DomainRecord.manager = cls.manager
@classmethod
@ -171,16 +170,14 @@ def core(module):
return v
try:
# params['client_id'] will be None even if client_id is not passed in
client_id = module.params['client_id'] or os.environ['DO_CLIENT_ID']
api_key = module.params['api_key'] or os.environ['DO_API_KEY']
api_token = module.params['api_token'] or os.environ['DO_API_TOKEN'] or os.environ['DO_API_KEY']
except KeyError, e:
module.fail_json(msg='Unable to load %s' % e.message)
changed = True
state = module.params['state']
Domain.setup(client_id, api_key)
Domain.setup(api_token)
if state in ('present'):
domain = Domain.find(id=module.params["id"])
@ -223,8 +220,7 @@ def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(choices=['present', 'absent'], default='present'),
client_id = dict(aliases=['CLIENT_ID'], no_log=True),
api_key = dict(aliases=['API_KEY'], no_log=True),
api_token = dict(aliases=['API_TOKEN'], no_log=True),
name = dict(type='str'),
id = dict(aliases=['droplet_id'], type='int'),
ip = dict(type='str'),

@ -79,8 +79,11 @@ options:
version_added: "1.5"
volumes:
description:
- List of volumes to mount within the container using docker CLI-style
- 'syntax: C(/host:/container[:mode]) where "mode" may be "rw" or "ro".'
- List of volumes to mount within the container
- 'Use docker CLI-style syntax: C(/host:/container[:mode])'
- You can specify a read mode for the mount with either C(ro) or C(rw).
Starting at version 2.1, SELinux hosts can additionally use C(z) or C(Z)
mount options to use a shared or private label for the volume.
default: null
volumes_from:
description:
@ -626,14 +629,14 @@ class DockerManager(object):
# host mount (e.g. /mnt:/tmp, bind mounts host's /tmp to /mnt in the container)
elif 2 <= len(parts) <= 3:
# default to read-write
ro = False
mode = 'rw'
# with supplied bind mode
if len(parts) == 3:
if parts[2] not in ['ro', 'rw']:
self.module.fail_json(msg='bind mode needs to either be "ro" or "rw"')
if parts[2] not in ["rw", "rw,Z", "rw,z", "z,rw", "Z,rw", "Z", "z", "ro", "ro,Z", "ro,z", "z,ro", "Z,ro"]:
self.module.fail_json(msg='invalid bind mode ' + parts[2])
else:
ro = parts[2] == 'ro'
self.binds[parts[0]] = {'bind': parts[1], 'ro': ro }
mode = parts[2]
self.binds[parts[0]] = {'bind': parts[1], 'mode': mode }
else:
self.module.fail_json(msg='volumes support 1 to 3 arguments')
@ -1197,10 +1200,7 @@ class DockerManager(object):
for host_path, config in self.binds.iteritems():
if isinstance(config, dict):
container_path = config['bind']
if config['ro']:
mode = 'ro'
else:
mode = 'rw'
mode = config['mode']
else:
container_path = config
mode = 'rw'

@ -164,18 +164,17 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module):
global _os_tenant_id
if not module.params['tenant_name']:
tenant_name = module.params['login_tenant_name']
_os_tenant_id = _os_keystone.tenant_id
else:
tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
if not _os_tenant_id:
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
def _get_net_id(neutron, module):
kwargs = {
'tenant_id': _os_tenant_id,

@ -136,17 +136,16 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module):
global _os_tenant_id
if not module.params['tenant_name']:
login_tenant_name = module.params['login_tenant_name']
_os_tenant_id = _os_keystone.tenant_id
else:
login_tenant_name = module.params['tenant_name']
tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list():
if tenant.name == login_tenant_name:
_os_tenant_id = tenant.id
break
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
if not _os_tenant_id:
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
def _get_router_id(module, neutron):
kwargs = {

@ -138,18 +138,17 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module):
global _os_tenant_id
if not module.params['tenant_name']:
login_tenant_name = module.params['login_tenant_name']
_os_tenant_id = _os_keystone.tenant_id
else:
login_tenant_name = module.params['tenant_name']
tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list():
if tenant.name == login_tenant_name:
_os_tenant_id = tenant.id
break
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
if not _os_tenant_id:
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
def _get_router_id(module, neutron):
kwargs = {
'name': module.params['router_name'],

@ -170,16 +170,16 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module):
global _os_tenant_id
if not module.params['tenant_name']:
tenant_name = module.params['login_tenant_name']
_os_tenant_id = _os_keystone.tenant_id
else:
tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
for tenant in _os_keystone.tenants.list():
if tenant.name == tenant_name:
_os_tenant_id = tenant.id
break
if not _os_tenant_id:
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
module.fail_json(msg = "The tenant id cannot be found, please check the parameters")
def _get_net_id(neutron, module):
kwargs = {

@ -154,7 +154,7 @@ def main():
msg="server {0} not found".format(server_name_or_id))
if state == 'present':
cloud.add_ips_to_server(
server = cloud.add_ips_to_server(
server=server, ips=floating_ip_address, reuse=reuse,
fixed_address=fixed_address, wait=wait, timeout=timeout)
fip_address = cloud.get_server_public_ip(server)

@ -56,12 +56,12 @@ options:
default: None
min_disk:
description:
- The minimum disk space required to deploy this image
- The minimum disk space (in GB) required to boot this image
required: false
default: None
min_ram:
description:
- The minimum ram required to deploy this image
- The minimum ram (in MB) required to boot this image
required: false
default: None
is_public:
@ -125,8 +125,8 @@ def main():
disk_format = dict(default='qcow2', choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso']),
container_format = dict(default='bare', choices=['ami', 'aki', 'ari', 'bare', 'ovf', 'ova']),
owner = dict(default=None),
min_disk = dict(default=None),
min_ram = dict(default=None),
min_disk = dict(type='int', default=0),
min_ram = dict(type='int', default=0),
is_public = dict(default=False),
filename = dict(default=None),
ramdisk = dict(default=None),
@ -156,6 +156,8 @@ def main():
wait=module.params['wait'],
timeout=module.params['timeout'],
is_public=module.params['is_public'],
min_disk=module.params['min_disk'],
min_ram=module.params['min_ram']
)
changed = True
if not module.params['wait']:

@ -17,7 +17,6 @@
try:
import shade
from shade import meta
HAS_SHADE = True
except ImportError:
HAS_SHADE = False
@ -28,6 +27,7 @@ module: os_user_group
short_description: Associate OpenStack Identity users and groups
extends_documentation_fragment: openstack
version_added: "2.0"
author: "Monty Taylor (@emonty)"
description:
- Add and remove users from groups
options:
@ -51,57 +51,66 @@ requirements:
EXAMPLES = '''
# Add the demo user to the demo group
- os_user_group: user=demo group=demo
- os_user_group:
cloud: mycloud
user: demo
group: demo
'''
def main():
def _system_state_change(state, in_group):
if state == 'present' and not in_group:
return True
if state == 'absent' and in_group:
return True
return False
def main():
argument_spec = openstack_full_argument_spec(
argument_spec = dict(
user=dict(required=True),
group=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
))
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec, **module_kwargs)
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
if not HAS_SHADE:
module.fail_json(msg='shade is required for this module')
user = module.params.pop('user')
group = module.params.pop('group')
state = module.params.pop('state')
user = module.params['user']
group = module.params['group']
state = module.params['state']
try:
cloud = shade.openstack_cloud(**module.params)
cloud = shade.operator_cloud(**module.params)
in_group = cloud.is_user_in_group(user, group)
if state == 'present':
if module.check_mode:
module.exit_json(changed=_system_state_change(state, in_group))
if in_group:
changed = False
else:
cloud.add_user_to_group(
user_name_or_id=user, group_name_or_id=group)
changed = False
if state == 'present':
if not in_group:
cloud.add_user_to_group(user, group)
changed = True
elif state == 'absent':
if in_group:
cloud.remove_user_from_group(
user_name_or_id=user, group_name_or_id=group)
cloud.remove_user_from_group(user, group)
changed=True
else:
changed=False
module.exit_json(changed=changed)
except shade.OpenStackCloudException as e:
module.fail_json(msg=e.message, extra_data=e.extra_data)
from ansible.module_utils.basic import *
from ansible.module_utils.openstack import *
if __name__ == '__main__':
main()

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# This file is part of Ansible
@ -170,6 +170,7 @@ EXAMPLES = '''
vcpu.hotadd: yes
mem.hotadd: yes
notes: This is a test VM
folder: MyFolder
vm_disk:
disk1:
size_gb: 10
@ -241,6 +242,8 @@ EXAMPLES = '''
template_src: centosTemplate
cluster: MainCluster
resource_pool: "/Resources"
vm_extra_config:
folder: MyFolder
# Task to gather facts from a vSphere cluster only if the system is a VMWare guest
@ -597,7 +600,7 @@ def vmdisk_id(vm, current_datastore_name):
return id_list
def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, module, cluster_name, snapshot_to_clone, power_on_after_clone):
def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, module, cluster_name, snapshot_to_clone, power_on_after_clone, vm_extra_config):
vmTemplate = vsphere_client.get_vm_by_name(template_src)
vmTarget = None
@ -689,6 +692,10 @@ def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, mo
cloneArgs["linked"] = True
cloneArgs["snapshot"] = snapshot_to_clone
if vm_extra_config.get("folder") is not None:
# if a folder is specified, clone the VM into it
cloneArgs["folder"] = vm_extra_config.get("folder")
vmTemplate.clone(guest, **cloneArgs)
changed = True
else:
@ -701,12 +708,77 @@ def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, mo
msg="Could not clone selected machine: %s" % e
)
# example from https://github.com/kalazzerx/pysphere/blob/master/examples/pysphere_create_disk_and_add_to_vm.py
# was used.
def update_disks(vsphere_client, vm, module, vm_disk, changes):
request = VI.ReconfigVM_TaskRequestMsg()
changed = False
for cnf_disk in vm_disk:
disk_id = re.sub("disk", "", cnf_disk)
found = False
for dev_key in vm._devices:
if vm._devices[dev_key]['type'] == 'VirtualDisk':
hdd_id = vm._devices[dev_key]['label'].split()[2]
if disk_id == hdd_id:
found = True
continue
if not found:
it = VI.ReconfigVM_TaskRequestMsg()
_this = request.new__this(vm._mor)
_this.set_attribute_type(vm._mor.get_attribute_type())
request.set_element__this(_this)
spec = request.new_spec()
dc = spec.new_deviceChange()
dc.Operation = "add"
dc.FileOperation = "create"
hd = VI.ns0.VirtualDisk_Def("hd").pyclass()
hd.Key = -100
hd.UnitNumber = int(disk_id)
hd.CapacityInKB = int(vm_disk[cnf_disk]['size_gb']) * 1024 * 1024
hd.ControllerKey = 1000
# module.fail_json(msg="peos : %s" % vm_disk[cnf_disk])
backing = VI.ns0.VirtualDiskFlatVer2BackingInfo_Def("backing").pyclass()
backing.FileName = "[%s]" % vm_disk[cnf_disk]['datastore']
backing.DiskMode = "persistent"
backing.Split = False
backing.WriteThrough = False
backing.ThinProvisioned = False
backing.EagerlyScrub = False
hd.Backing = backing
dc.Device = hd
spec.DeviceChange = [dc]
request.set_element_spec(spec)
ret = vsphere_client._proxy.ReconfigVM_Task(request)._returnval
# Wait for the task to finish
task = VITask(ret, vsphere_client)
status = task.wait_for_state([task.STATE_SUCCESS,
task.STATE_ERROR])
if status == task.STATE_SUCCESS:
changed = True
changes[cnf_disk] = vm_disk[cnf_disk]
elif status == task.STATE_ERROR:
module.fail_json(
msg="Error reconfiguring vm: %s, [%s]" % (
task.get_error_message(),
vm_disk[cnf_disk]))
return changed, changes
def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, state, force):
spec = None
changed = False
changes = {}
request = VI.ReconfigVM_TaskRequestMsg()
request = None
shutdown = False
poweron = vm.is_powered_on()
@ -714,6 +786,10 @@ def reconfigure_vm(vsphere_client, vm, module, esxi, resource_pool, cluster_name
cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled)
cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled)
changed, changes = update_disks(vsphere_client, vm,
module, vm_disk, changes)
request = VI.ReconfigVM_TaskRequestMsg()
# Change Memory
if 'memory_mb' in vm_hardware:
@ -1556,7 +1632,8 @@ def main():
module=module,
cluster_name=cluster,
snapshot_to_clone=snapshot_to_clone,
power_on_after_clone=power_on_after_clone
power_on_after_clone=power_on_after_clone,
vm_extra_config=vm_extra_config
)
if state in ['restarted', 'reconfigured']:

@ -47,12 +47,12 @@ options:
default: null
creates:
description:
- a filename or glob pattern, when it already exists, this step will B(not) be run.
- a filename or (since 2.0) glob pattern, when it already exists, this step will B(not) be run.
required: no
default: null
removes:
description:
- a filename or glob pattern, when it does not exist, this step will B(not) be run.
- a filename or (since 2.0) glob pattern, when it does not exist, this step will B(not) be run.
version_added: "0.8"
required: no
default: null

@ -30,7 +30,7 @@ options:
name:
description:
- name of the database to add or remove
- name=all May only be provided if I(state) is C(dump) or C(import).
- name=all May only be provided if I(state) is C(dump) or C(import).
- if name=all Works like --all-databases option for mysqldump (Added in 2.0)
required: true
default: null
@ -85,12 +85,15 @@ notes:
- Requires the MySQLdb Python package on the remote host. For Ubuntu, this
is as easy as apt-get install python-mysqldb. (See M(apt).) For CentOS/Fedora, this
is as easy as yum install MySQL-python. (See M(yum).)
- Requires the mysql command line client. For Centos/Fedora, this is as easy as
yum install mariadb (See M(yum).). For Debian/Ubuntu this is as easy as
apt-get install mariadb-client. (See M(apt).)
- Both I(login_password) and I(login_user) are required when you are
passing credentials. If none are present, the module will attempt to read
the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
default login of C(root) with no password.
requirements: [ ConfigParser ]
author: "Mark Theunissen (@marktheunissen)"
author: "Ansible Core Team"
'''
EXAMPLES = '''
@ -367,7 +370,7 @@ def main():
except Exception, e:
module.fail_json(msg="error deleting database: " + str(e))
elif state == "dump":
rc, stdout, stderr = db_dump(module, login_host, login_user,
rc, stdout, stderr = db_dump(module, login_host, login_user,
login_password, db, target, all_databases,
port=login_port,
socket=module.params['login_unix_socket'])
@ -376,7 +379,7 @@ def main():
else:
module.exit_json(changed=True, db=db, msg=stdout)
elif state == "import":
rc, stdout, stderr = db_import(module, login_host, login_user,
rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target, all_databases,
port=login_port,
socket=module.params['login_unix_socket'])

@ -42,6 +42,13 @@ options:
- set the user's password. (Required when adding a user)
required: false
default: null
encrypted:
description:
- Indicate that the 'password' field is a `mysql_native_password` hash
required: false
choices: [ "yes", "no" ]
default: "no"
version_added: "2.0"
host:
description:
- the 'host' part of the MySQL username
@ -133,15 +140,19 @@ notes:
without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
the file."
- Currently, there is only support for the `mysql_native_password` encryted password hash module.
requirements: [ "MySQLdb" ]
author: "Mark Theunissen (@marktheunissen)"
author: "Jonathan Mainguy (@Jmainguy)"
'''
EXAMPLES = """
# Create database user with name 'bob' and password '12345' with all database privileges
- mysql_user: name=bob password=12345 priv=*.*:ALL state=present
# Create database user with name 'bob' and previously hashed mysql native password '*EE0D72C1085C46C5278932678FBE2C6A782821B4' with all database privileges
- mysql_user: name=bob password='*EE0D72C1085C46C5278932678FBE2C6A782821B4' encrypted=yes priv=*.*:ALL state=present
# Creates database user 'bob' and password '12345' with all database privileges and 'WITH GRANT OPTION'
- mysql_user: name=bob password=12345 priv=*.*:ALL,GRANT state=present
@ -157,7 +168,7 @@ EXAMPLES = """
# Specify grants composed of more than one word
- mysql_user: name=replication password=12345 priv=*.*:"REPLICATION CLIENT" state=present
# Revoke all privileges for user 'bob' and password '12345'
# Revoke all privileges for user 'bob' and password '12345'
- mysql_user: name=bob password=12345 priv=*.*:USAGE state=present
# Example privileges string format
@ -176,6 +187,7 @@ password=n<_665{vS43y
import getpass
import tempfile
import re
import string
try:
import MySQLdb
except ImportError:
@ -224,6 +236,19 @@ def connect(module, login_user=None, login_password=None, config_file=''):
db_connection = MySQLdb.connect(**config)
return db_connection.cursor()
# User Authentication Management was change in MySQL 5.7
# This is a generic check for if the server version is less than version 5.7
def server_version_check(cursor):
cursor.execute("SELECT VERSION()");
result = cursor.fetchone()
version_str = result[0]
version = version_str.split('.')
if (int(version[0]) <= 5 and int(version[1]) < 7):
return True
else:
return False
def user_exists(cursor, user, host, host_all):
if host_all:
cursor.execute("SELECT count(*) FROM user WHERE user = %s", user)
@ -233,38 +258,95 @@ def user_exists(cursor, user, host, host_all):
count = cursor.fetchone()
return count[0] > 0
def user_add(cursor, user, host, host_all, password, new_priv):
def user_add(cursor, user, host, host_all, password, encrypted, new_priv):
# we cannot create users without a proper hostname
if host_all:
return False
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
if password and encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user,host,password))
elif password and not encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
if new_priv is not None:
for db_table, priv in new_priv.iteritems():
privileges_grant(cursor, user,host,db_table,priv)
return True
def user_mod(cursor, user, host, host_all, password, new_priv, append_privs):
def is_hash(password):
ishash = False
if len(password) == 41 and password[0] == '*':
if frozenset(password[1:]).issubset(string.hexdigits):
ishash = True
return ishash
def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append_privs):
changed = False
grant_option = False
# to simplify code, if we have a specific host and no host_all, we create
# a list with just host and loop over that
if host_all:
hostnames = user_get_hostnames(cursor, user)
else:
hostnames = [host]
for host in hostnames:
# Handle passwords
if password is not None:
cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
# Handle clear text and hashed passwords.
if bool(password):
# Determine what user management method server uses
old_user_mgmt = server_version_check(cursor)
if old_user_mgmt:
cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
else:
cursor.execute("SELECT authentication_string FROM user WHERE user = %s AND host = %s", (user,host))
current_pass_hash = cursor.fetchone()
cursor.execute("SELECT PASSWORD(%s)", (password,))
new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user,host,password))
changed = True
if encrypted:
encrypted_string = (password)
if is_hash(password):
if current_pass_hash[0] != encrypted_string:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, password))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password))
changed = True
else:
module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
else:
if old_user_mgmt:
cursor.execute("SELECT PASSWORD(%s)", (password,))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user, host, password))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED BY %s", (user, host, password))
changed = True
# Handle privileges
if new_priv is not None:
curr_priv = privileges_get(cursor, user,host)
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in curr_priv.iteritems():
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
privileges_revoke(cursor, user,host,db_table,priv,grant_option)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in new_priv.iteritems():
if db_table not in curr_priv:
privileges_grant(cursor, user,host,db_table,priv)
changed = True
# Handle privileges
if new_priv is not None:
@ -440,6 +522,7 @@ def main():
user=dict(required=True, aliases=['name']),
user_anonymous=dict(type="bool", default="no"),
password=dict(default=None, no_log=True),
encrypted=dict(default=False, type='bool'),
host=dict(default="localhost"),
host_all=dict(type="bool", default="no"),
state=dict(default="present", choices=["absent", "present"]),
@ -455,6 +538,7 @@ def main():
user = module.params["user"]
user_anonymous = module.params["user_anonymous"]
password = module.params["password"]
encrypted = module.boolean(module.params["encrypted"])
host = module.params["host"].lower()
host_all = module.params["host_all"]
state = module.params["state"]
@ -494,9 +578,9 @@ def main():
if user_exists(cursor, user, host, host_all):
try:
if update_password == 'always':
changed = user_mod(cursor, user, host, host_all, password, priv, append_privs)
changed = user_mod(cursor, user, host, host_all, password, encrypted, priv, append_privs)
else:
changed = user_mod(cursor, user, host, host_all, None, priv, append_privs)
changed = user_mod(cursor, user, host, host_all, None, encrypted, priv, append_privs)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
@ -506,7 +590,7 @@ def main():
if host_all:
module.fail_json(msg="host_all parameter cannot be used when adding a user")
try:
changed = user_add(cursor, user, host, host_all, password, priv)
changed = user_add(cursor, user, host, host_all, password, encrypted, priv)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
elif state == "absent":

@ -127,10 +127,17 @@ def split_entry(entry):
''' splits entry and ensures normalized return'''
a = entry.split(':')
d = None
if entry.lower().startswith("d"):
d = True
a.pop(0)
if len(a) == 2:
a.append(None)
t, e, p = a
t = t.lower()
if t.startswith("u"):
t = "user"
@ -143,7 +150,7 @@ def split_entry(entry):
else:
t = None
return [t, e, p]
return [d, t, e, p]
def build_entry(etype, entity, permissions=None):
@ -176,9 +183,9 @@ def build_command(module, mode, path, follow, default, recursive, entry=''):
if default:
if(mode == 'rm'):
cmd.append('-k')
cmd.insert(1, '-k')
else: # mode == 'set' or mode == 'get'
cmd.append('-d')
cmd.insert(1, '-d')
cmd.append(path)
return cmd
@ -269,16 +276,18 @@ def main():
if etype or entity or permissions:
module.fail_json(msg="'entry' MUST NOT be set when 'entity', 'etype' or 'permissions' are set.")
if state == 'present' and entry.count(":") != 2:
module.fail_json(msg="'entry' MUST have 3 sections divided by ':' when 'state=present'.")
if state == 'present' and not entry.count(":") in [2, 3]:
module.fail_json(msg="'entry' MUST have 3 or 4 sections divided by ':' when 'state=present'.")
if state == 'absent' and entry.count(":") != 1:
module.fail_json(msg="'entry' MUST have 2 sections divided by ':' when 'state=absent'.")
if state == 'absent' and not entry.count(":") in [1, 2]:
module.fail_json(msg="'entry' MUST have 2 or 3 sections divided by ':' when 'state=absent'.")
if state == 'query':
module.fail_json(msg="'entry' MUST NOT be set when 'state=query'.")
etype, entity, permissions = split_entry(entry)
default_flag, etype, entity, permissions = split_entry(entry)
if default_flag != None:
default = default_flag
changed = False
msg = ""

@ -27,7 +27,7 @@ module: copy
version_added: "historical"
short_description: Copies files to remote locations.
description:
- The M(copy) module copies a file on the local box to remote locations. Use the M(fetch) module to copy files from remote locations to the local box.
- The M(copy) module copies a file on the local box to remote locations. Use the M(fetch) module to copy files from remote locations to the local box. If you need variable interpolation in copied files, use the M(template) module.
options:
src:
description:
@ -310,7 +310,7 @@ def main():
if rc != 0:
module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc,err))
if remote_src:
tmpdest = tempfile.mkstemp(dir=os.basedir(dest))
_, tmpdest = tempfile.mkstemp(dir=os.path.dirname(dest))
shutil.copy2(src, tmpdest)
module.atomic_move(tmpdest, dest)
else:

@ -157,8 +157,8 @@ def main():
original_basename = dict(required=False), # Internal use only, for recursive ops
recurse = dict(default=False, type='bool'),
force = dict(required=False, default=False, type='bool'),
diff_peek = dict(default=None),
validate = dict(required=False, default=None),
diff_peek = dict(default=None), # Internal use only, for internal checks in the action plugins
validate = dict(required=False, default=None), # Internal use only, for template and copy
src = dict(required=False, default=None),
),
add_file_common_args=True,
@ -288,7 +288,7 @@ def main():
except OSError, ex:
# Possibly something else created the dir since the os.path.exists
# check above. As long as it's a dir, we don't need to error out.
if not (ex.errno == errno.EEXISTS and os.isdir(curpath)):
if not (ex.errno == errno.EEXIST and os.isdir(curpath)):
raise
tmp_file_args = file_args.copy()
tmp_file_args['path']=curpath

@ -97,6 +97,23 @@ EXAMPLES = '''
import ConfigParser
import sys
import os
# ==============================================================
# match_opt
def match_opt(option, line):
option = re.escape(option)
return re.match('%s *=' % option, line) \
or re.match('# *%s *=' % option, line) \
or re.match('; *%s *=' % option, line)
# ==============================================================
# match_active_opt
def match_active_opt(option, line):
option = re.escape(option)
return re.match('%s *=' % option, line)
# ==============================================================
# do_ini
@ -104,6 +121,11 @@ import sys
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):
if not os.path.exists(filename):
try:
open(filename,'w').close()
except:
module.fail_json(msg="Destination file %s not writable" % filename)
ini_file = open(filename, 'r')
try:
ini_lines = ini_file.readlines()
@ -135,9 +157,7 @@ def do_ini(module, filename, section=None, option=None, value=None, state='prese
if within_section and option:
if state == 'present':
# change the existing option line
if re.match('%s *=' % option, line) \
or re.match('# *%s *=' % option, line) \
or re.match('; *%s *=' % option, line):
if match_opt(option, line):
newline = '%s = %s\n' % (option, value)
changed = ini_lines[index] != newline
ini_lines[index] = newline
@ -148,14 +168,14 @@ def do_ini(module, filename, section=None, option=None, value=None, state='prese
line = ini_lines[index]
if line.startswith('['):
break
if re.match('%s *=' % option, line):
if match_active_opt(option, line):
del ini_lines[index]
else:
index = index + 1
break
else:
# comment out the existing option line
if re.match('%s *=' % option, line):
if match_active_opt(option, line):
ini_lines[index] = '#%s' % ini_lines[index]
changed = True
break

@ -111,7 +111,7 @@ stat:
path:
description: The full path of the file/object to get the facts of
returned: success and if path exists
type: boolean
type: string
sample: '/path/to/file'
mode:
description: Unix permissions of the file in octal

@ -250,7 +250,7 @@ def pick_handler(src, dest, module):
obj = handler(src, dest, module)
if obj.can_handle_archive():
return obj
module.fail_json(msg='Failed to find handler to unarchive. Make sure the required command to extract the file is installed.')
module.fail_json(msg='Failed to find handler for "%s". Make sure the required command to extract the file is installed.' % src)
def main():

@ -55,6 +55,14 @@ options:
If C(dest) is a directory, the file will always be
downloaded (regardless of the force option), but replaced only if the contents changed.
required: true
tmp_dest:
description:
- absolute path of where temporary file is downloaded to.
- Defaults to TMPDIR, TEMP or TMP env variables or a platform specific value
- https://docs.python.org/2/library/tempfile.html#tempfile.tempdir
required: false
default: ''
version_added: '2.1'
force:
description:
- If C(yes) and C(dest) is not a directory, will download the file every
@ -175,7 +183,7 @@ def url_filename(url):
return 'index.html'
return fn
def url_get(module, url, dest, use_proxy, last_mod_time, force, timeout=10, headers=None):
def url_get(module, url, dest, use_proxy, last_mod_time, force, timeout=10, headers=None, tmp_dest=''):
"""
Download data from the url and store in a temporary file.
@ -191,7 +199,19 @@ def url_get(module, url, dest, use_proxy, last_mod_time, force, timeout=10, head
if info['status'] != 200:
module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url, dest=dest)
fd, tempname = tempfile.mkstemp()
if tmp_dest != '':
# tmp_dest should be an existing dir
tmp_dest_is_dir = os.path.isdir(tmp_dest)
if not tmp_dest_is_dir:
if os.path.exists(tmp_dest):
module.fail_json(msg="%s is a file but should be a directory." % tmp_dest)
else:
module.fail_json(msg="%s directoy does not exist." % tmp_dest)
fd, tempname = tempfile.mkstemp(dir=tmp_dest)
else:
fd, tempname = tempfile.mkstemp()
f = os.fdopen(fd, 'wb')
try:
shutil.copyfileobj(rsp, f)
@ -235,6 +255,7 @@ def main():
checksum = dict(default=''),
timeout = dict(required=False, type='int', default=10),
headers = dict(required=False, default=None),
tmp_dest = dict(required=False, default=''),
)
module = AnsibleModule(
@ -250,7 +271,8 @@ def main():
checksum = module.params['checksum']
use_proxy = module.params['use_proxy']
timeout = module.params['timeout']
tmp_dest = os.path.expanduser(module.params['tmp_dest'])
# Parse headers to dict
if module.params['headers']:
try:
@ -303,7 +325,7 @@ def main():
last_mod_time = datetime.datetime.utcfromtimestamp(mtime)
# download to tmpsrc
tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers)
tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest)
# Now the request has completed, we can finally generate the final
# destination file name from the info dict.

@ -159,7 +159,7 @@ EXAMPLES = '''
register: webpage
- action: fail
when: "'illustrative' not in webpage.content"
when: "'AWESOME' not in webpage.content"
# Create a JIRA issue

@ -32,6 +32,7 @@ options:
- A package name, like C(foo), or package specifier with version, like C(foo=1.0). Name wildcards (fnmatch) like C(apt*) and version wildcards like C(foo=1.0*) are also supported. Note that the apt-get commandline supports implicit regex matches here but we do not because it can let typos through easier (If you typo C(foo) as C(fo) apt-get would install packages that have "fo" in their name with a warning and a prompt for the user. Since we don't have warnings and prompts before installing we disallow this. Use an explicit fnmatch pattern if you want wildcarding)
required: false
default: null
aliases: [ 'pkg', 'package' ]
state:
description:
- Indicates the desired package state. C(latest) ensures that the latest version is installed. C(build-dep) ensures the package build dependencies are installed.

@ -51,7 +51,7 @@ options:
- "Package name, or package specifier with version, like C(name-1.0). When using state=latest, this can be '*' which means run: yum -y update. You can also pass a url or a local path to a rpm file. To operate on several packages this can accept a comma separated list of packages or (as of 2.0) a list of packages."
required: true
default: null
aliases: []
aliases: [ 'pkg' ]
exclude:
description:
- "Package name(s) to exclude when state=present, or latest"
@ -65,9 +65,9 @@ options:
default: null
state:
description:
- Whether to install (C(present), C(latest)), or remove (C(absent)) a package.
- Whether to install (C(present) or C(installed), C(latest)), or remove (C(absent) or C(removed)) a package.
required: false
choices: [ "present", "latest", "absent" ]
choices: [ "present", "installed", "latest", "absent", "removed" ]
default: "present"
enablerepo:
description:
@ -117,6 +117,16 @@ options:
choices: ["yes", "no"]
aliases: []
validate_certs:
description:
- This only applies if using a https url as the source of the rpm. e.g. for localinstall. If set to C(no), the SSL certificates will not be validated.
- This should only set to C(no) used on personally controlled sites using self-signed certificates as it avoids verifying the source site.
- Prior to 2.1 the code worked as if this was set to C(yes).
required: false
default: "yes"
choices: ["yes", "no"]
version_added: "2.1"
notes:
- When used with a loop of package names in a playbook, ansible optimizes
the call to the yum module. Instead of calling the module with a single
@ -185,6 +195,7 @@ def yum_base(conf_file=None):
my = yum.YumBase()
my.preconf.debuglevel=0
my.preconf.errorlevel=0
my.preconf.plugins = True
if conf_file and os.path.exists(conf_file):
my.preconf.fn = conf_file
if os.geteuid() != 0:
@ -965,6 +976,7 @@ def main():
conf_file=dict(default=None),
disable_gpg_check=dict(required=False, default="no", type='bool'),
update_cache=dict(required=False, default="no", type='bool'),
validate_certs=dict(required=False, default="yes", type='bool'),
# this should not be needed, but exists as a failsafe
install_repoquery=dict(required=False, default="yes", type='bool'),
),

@ -55,7 +55,7 @@ options:
version_added: "1.5"
description:
- if C(yes), adds the hostkey for the repo url if not already
added. If ssh_args contains "-o StrictHostKeyChecking=no",
added. If ssh_opts contains "-o StrictHostKeyChecking=no",
this parameter is ignored.
ssh_opts:
required: false

@ -80,6 +80,15 @@ options:
choices: [ "yes", "no" ]
default: "no"
version_added: "1.9"
validate_certs:
description:
- This only applies if using a https url as the source of the keys. If set to C(no), the SSL certificates will not be validated.
- This should only set to C(no) used on personally controlled sites using self-signed certificates as it avoids verifying the source site.
- Prior to 2.1 the code worked as if this was set to C(yes).
required: false
default: "yes"
choices: ["yes", "no"]
version_added: "2.1"
description:
- "Adds or removes authorized keys for particular user accounts"
author: "Ansible Core Team"
@ -93,27 +102,30 @@ EXAMPLES = '''
- authorized_key: user=charlie key=https://github.com/charlie.keys
# Using alternate directory locations:
- authorized_key: user=charlie
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
path='/etc/ssh/authorized_keys/charlie'
manage_dir=no
- authorized_key:
user: charlie
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
path: '/etc/ssh/authorized_keys/charlie'
manage_dir: no
# Using with_file
- name: Set up authorized_keys for the deploy user
authorized_key: user=deploy
key="{{ item }}"
authorized_key: user=deploy key="{{ item }}"
with_file:
- public_keys/doe-jane
- public_keys/doe-john
# Using key_options:
- authorized_key: user=charlie
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
key_options='no-port-forwarding,from="10.0.1.1"'
- authorized_key:
user: charlie
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
key_options: 'no-port-forwarding,from="10.0.1.1"'
# Using validate_certs:
- authorized_key: user=charlie key=https://github.com/user.keys validate_certs=no
# Set up authorized_keys exclusively with one key
- authorized_key: user=root key="{{ item }}" state=present
exclusive=yes
- authorized_key: user=root key="{{ item }}" state=present exclusive=yes
with_file:
- public_keys/doe-jane
'''
@ -358,6 +370,7 @@ def enforce_state(module, params):
state = params.get("state", "present")
key_options = params.get("key_options", None)
exclusive = params.get("exclusive", False)
validate_certs = params.get("validate_certs", True)
error_msg = "Error getting key from: %s"
# if the key is a url, request it and use it as key source
@ -460,6 +473,7 @@ def main():
key_options = dict(required=False, type='str'),
unique = dict(default=False, type='bool'),
exclusive = dict(default=False, type='bool'),
validate_certs = dict(default=True, type='bool'),
),
supports_check_mode=True
)

@ -260,8 +260,8 @@ class SystemdStrategy(GenericStrategy):
(rc, out, err))
def get_permanent_hostname(self):
cmd = 'hostnamectl --static status'
rc, out, err = self.module.run_command(cmd, use_unsafe_shell=True)
cmd = ['hostnamectl', '--static', 'status']
rc, out, err = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
(rc, out, err))
@ -399,6 +399,57 @@ class SolarisStrategy(GenericStrategy):
# ===========================================
class FreeBSDStrategy(GenericStrategy):
"""
This is a FreeBSD hostname manipulation strategy class - it edits
the /etc/rc.conf.d/hostname file.
"""
HOSTNAME_FILE = '/etc/rc.conf.d/hostname'
def get_permanent_hostname(self):
if not os.path.isfile(self.HOSTNAME_FILE):
try:
open(self.HOSTNAME_FILE, "a").write("hostname=temporarystub\n")
except IOError, err:
self.module.fail_json(msg="failed to write file: %s" %
str(err))
try:
try:
f = open(self.HOSTNAME_FILE, 'r')
for line in f:
line = line.strip()
if line.startswith('hostname='):
return line[10:].strip('"')
except Exception, err:
self.module.fail_json(msg="failed to read hostname: %s" % str(err))
finally:
f.close()
return None
def set_permanent_hostname(self, name):
try:
try:
f = open(self.HOSTNAME_FILE, 'r')
lines = [x.strip() for x in f]
for i, line in enumerate(lines):
if line.startswith('hostname='):
lines[i] = 'hostname="%s"' % name
break
f.close()
f = open(self.HOSTNAME_FILE, 'w')
f.write('\n'.join(lines) + '\n')
except Exception, err:
self.module.fail_json(msg="failed to update hostname: %s" % str(err))
finally:
f.close()
# ===========================================
class FedoraHostname(Hostname):
platform = 'Linux'
distribution = 'Fedora'
@ -541,6 +592,12 @@ class SolarisHostname(Hostname):
distribution = None
strategy_class = SolarisStrategy
class FreeBSDHostname(Hostname):
platform = 'FreeBSD'
distribution = None
strategy_class = FreeBSDStrategy
# ===========================================
def main():

@ -23,7 +23,7 @@ DOCUMENTATION = '''
---
module: ping
version_added: historical
short_description: Try to connect to host, veryify a usable python and return C(pong) on success.
short_description: Try to connect to host, verify a usable python and return C(pong) on success.
description:
- A trivial test module, this module always returns C(pong) on successful
contact. It does not make sense in playbooks, but it is useful from

@ -49,6 +49,11 @@ options:
- Optionally when used with the -u option, this option allows to
change the user ID to a non-unique value.
version_added: "1.1"
seuser:
required: false
description:
- Optionally sets the seuser type (user_u) on selinux enabled systems.
version_added: "2.1"
group:
required: false
description:
@ -253,6 +258,7 @@ class User(object):
self.name = module.params['name']
self.uid = module.params['uid']
self.non_unique = module.params['non_unique']
self.seuser = module.params['seuser']
self.group = module.params['group']
self.groups = module.params['groups']
self.comment = module.params['comment']
@ -313,6 +319,9 @@ class User(object):
if self.non_unique:
cmd.append('-o')
if self.seuser is not None:
cmd.append('-Z')
cmd.append(self.seuser)
if self.group is not None:
if not self.group_exists(self.group):
self.module.fail_json(msg="Group %s does not exist" % self.group)
@ -1674,9 +1683,10 @@ class DarwinUser(User):
self._update_system_user()
# here we don't care about change status since it is a creation,
# thus changed is always true.
(rc, _out, _err, changed) = self._modify_group()
out += _out
err += _err
if self.groups:
(rc, _out, _err, changed) = self._modify_group()
out += _out
err += _err
return (rc, err, out)
def modify_user(self):
@ -1684,7 +1694,8 @@ class DarwinUser(User):
out = ''
err = ''
self._make_group_numerical()
if self.group:
self._make_group_numerical()
for field in self.fields:
if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]:
@ -1707,12 +1718,13 @@ class DarwinUser(User):
err += _err
changed = rc
(rc, _out, _err, _changed) = self._modify_group()
out += _out
err += _err
if self.groups:
(rc, _out, _err, _changed) = self._modify_group()
out += _out
err += _err
if _changed is True:
changed = rc
if _changed is True:
changed = rc
rc = self._update_system_user()
if rc == 0:
@ -2047,6 +2059,8 @@ def main():
shell=dict(default=None, type='str'),
password=dict(default=None, type='str', no_log=True),
login_class=dict(default=None, type='str'),
# following options are specific to selinux
seuser=dict(default=None, type='str'),
# following options are specific to userdel
force=dict(default='no', type='bool'),
remove=dict(default='no', type='bool'),

@ -97,6 +97,7 @@ else:
apache_hashes = ["apr_md5_crypt", "des_crypt", "ldap_sha1", "plaintext"]
def create_missing_directories(dest):
destpath = os.path.dirname(dest)
if not os.path.exists(destpath):
@ -155,9 +156,6 @@ def absent(dest, username, check_mode):
""" Ensures user is absent
Returns (msg, changed) """
if not os.path.exists(dest):
raise ValueError("%s does not exists" % dest)
if StrictVersion(passlib.__version__) >= StrictVersion('1.6'):
ht = HtpasswdFile(dest, new=False)
else:
@ -244,6 +242,9 @@ def main():
if state == 'present':
(msg, changed) = present(path, username, password, crypt_scheme, create, check_mode)
elif state == 'absent':
if not os.path.exists(path):
module.exit_json(msg="%s not present" % username,
warnings="%s does not exist" % path, changed=False)
(msg, changed) = absent(path, username, check_mode)
else:
module.fail_json(msg="Invalid state: %s" % state)

@ -68,6 +68,7 @@ Set-Attr $date "year" (Get-Date -format yyyy)
Set-Attr $date "month" (Get-Date -format MM)
Set-Attr $date "day" (Get-Date -format dd)
Set-Attr $date "hour" (Get-Date -format HH)
Set-Attr $date "minute" (Get-Date -format mm)
Set-Attr $date "iso8601" (Get-Date -format s)
Set-Attr $result.ansible_facts "ansible_date_time" $date

@ -44,16 +44,6 @@ options:
required: true
default: null
author: "Jon Hawkesworth (@jhawkesworth)"
notes:
- The "win_copy" module is best used for small files only.
This module should **not** be used for files bigger than 3Mb as
this will result in a 500 response from the winrm host
and it will not be possible to connect via winrm again until the
windows remote management service has been restarted on the
windows host.
Files larger than 1Mb will take minutes to transfer.
The recommended way to transfer large files is using win_get_url
or collecting from a windows file share folder.
'''
EXAMPLES = '''

Loading…
Cancel
Save