Merge branch 'devel' into mysql_anon_user

Conflicts:
	database/mysql/mysql_user.py
This commit is contained in:
Lee H 2015-12-14 11:40:20 -05:00
commit b5d7becc29
47 changed files with 683 additions and 322 deletions

View file

@ -247,6 +247,12 @@ options:
required: false required: false
default: null default: null
aliases: ['network_interface'] 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: author:
- "Tim Gerla (@tgerla)" - "Tim Gerla (@tgerla)"
@ -303,6 +309,22 @@ EXAMPLES = '''
vpc_subnet_id: subnet-29e63245 vpc_subnet_id: subnet-29e63245
assign_public_ip: yes 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 # Multiple groups example
- ec2: - ec2:
key_name: mykey key_name: mykey
@ -358,6 +380,7 @@ EXAMPLES = '''
wait: yes wait: yes
vpc_subnet_id: subnet-29e63245 vpc_subnet_id: subnet-29e63245
assign_public_ip: yes assign_public_ip: yes
spot_launch_group: report_generators
# Examples using pre-existing network interfaces # Examples using pre-existing network interfaces
- ec2: - ec2:
@ -481,7 +504,6 @@ EXAMPLES = '''
# #
- ec2: - ec2:
state: running
key_name: mykey key_name: mykey
instance_type: c1.medium instance_type: c1.medium
image: ami-40603AD1 image: ami-40603AD1
@ -499,7 +521,6 @@ EXAMPLES = '''
# #
- ec2: - ec2:
state: running
key_name: mykey key_name: mykey
instance_type: c1.medium instance_type: c1.medium
image: ami-40603AD1 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')) source_dest_check = module.boolean(module.params.get('source_dest_check'))
termination_protection = module.boolean(module.params.get('termination_protection')) termination_protection = module.boolean(module.params.get('termination_protection'))
network_interfaces = module.params.get('network_interfaces') 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 # group_id and group_name are exclusive of each other
if group_id and group_name: 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() grp_details = ec2.get_all_security_groups()
if isinstance(group_name, basestring): if isinstance(group_name, basestring):
group_name = [group_name] 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 ] 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. # Now we try to lookup the group id testing if group exists.
elif group_id: elif group_id:
@ -1042,6 +1067,9 @@ def create_instances(module, ec2, vpc, override_count=None):
module.fail_json( module.fail_json(
msg="placement_group parameter requires Boto version 2.3.0 or higher.") 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( params.update(dict(
count = count_remaining, count = count_remaining,
type = spot_type, type = spot_type,
@ -1310,6 +1338,7 @@ def main():
instance_type = dict(aliases=['type']), instance_type = dict(aliases=['type']),
spot_price = dict(), spot_price = dict(),
spot_type = dict(default='one-time', choices=["one-time", "persistent"]), spot_type = dict(default='one-time', choices=["one-time", "persistent"]),
spot_launch_group = dict(),
image = dict(), image = dict(),
kernel = dict(), kernel = dict(),
count = dict(type='int', default='1'), count = dict(type='int', default='1'),

View file

@ -281,7 +281,6 @@ def get_properties(autoscaling_group):
if getattr(autoscaling_group, "tags", None): if getattr(autoscaling_group, "tags", None):
properties['tags'] = dict((t.key, t.value) for t in autoscaling_group.tags) properties['tags'] = dict((t.key, t.value) for t in autoscaling_group.tags)
return properties return properties
def elb_dreg(asg_connection, module, group_name, instance_id): 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: else:
return return
exists = True
for lb in as_group.load_balancers: for lb in as_group.load_balancers:
elb_connection.deregister_instances(lb, instance_id) elb_connection.deregister_instances(lb, instance_id)
log.debug("De-registering {0} from ELB {1}".format(instance_id, lb)) log.debug("De-registering {0} from ELB {1}".format(instance_id, lb))
@ -319,8 +317,6 @@ def elb_dreg(asg_connection, module, group_name, instance_id):
module.fail_json(msg = "Waited too long for instance to deregister. {0}".format(time.asctime())) 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): def elb_healthy(asg_connection, elb_connection, module, group_name):
healthy_instances = [] healthy_instances = []
as_group = asg_connection.get_all_groups(names=[group_name])[0] as_group = asg_connection.get_all_groups(names=[group_name])[0]
@ -337,7 +333,7 @@ def elb_healthy(asg_connection, elb_connection, module, group_name):
# but has not yet show up in the ELB # but has not yet show up in the ELB
try: try:
lb_instances = elb_connection.describe_instance_health(lb, instances=instances) lb_instances = elb_connection.describe_instance_health(lb, instances=instances)
except boto.exception.InvalidInstance, e: except boto.exception.InvalidInstance:
pass pass
for i in lb_instances: for i in lb_instances:
if i.state == "InService": if i.state == "InService":
@ -346,7 +342,6 @@ def elb_healthy(asg_connection, elb_connection, module, group_name):
return len(healthy_instances) return len(healthy_instances)
def wait_for_elb(asg_connection, module, group_name): def wait_for_elb(asg_connection, module, group_name):
region, ec2_url, aws_connect_params = get_aws_connection_info(module) region, ec2_url, aws_connect_params = get_aws_connection_info(module)
wait_timeout = module.params.get('wait_timeout') wait_timeout = module.params.get('wait_timeout')
@ -396,7 +391,7 @@ def create_autoscaling_group(connection, module):
region, ec2_url, aws_connect_params = get_aws_connection_info(module) region, ec2_url, aws_connect_params = get_aws_connection_info(module)
try: try:
ec2_connection = connect_to_aws(boto.ec2, region, **aws_connect_params) 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)) module.fail_json(msg=str(e))
elif vpc_zone_identifier: elif vpc_zone_identifier:
vpc_zone_identifier = ','.join(vpc_zone_identifier) vpc_zone_identifier = ','.join(vpc_zone_identifier)
@ -433,7 +428,7 @@ def create_autoscaling_group(connection, module):
try: try:
connection.create_auto_scaling_group(ag) 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_new_inst(module, connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
wait_for_elb(connection, module, group_name) wait_for_elb(connection, module, group_name)
as_group = connection.get_all_groups(names=[group_name])[0] as_group = connection.get_all_groups(names=[group_name])[0]
@ -475,7 +470,7 @@ def create_autoscaling_group(connection, module):
dead_tags = [] dead_tags = []
for tag in as_group.tags: for tag in as_group.tags:
have_tags[tag.key] = [tag.value, tag.propagate_at_launch] 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 changed = True
dead_tags.append(tag) dead_tags.append(tag)
@ -492,14 +487,13 @@ def create_autoscaling_group(connection, module):
changed = True changed = True
as_group.load_balancers = module.params.get('load_balancers') as_group.load_balancers = module.params.get('load_balancers')
if changed: if changed:
try: try:
as_group.update() as_group.update()
except BotoServerError, e: except BotoServerError, e:
module.fail_json(msg=str(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_new_inst(module, connection, group_name, wait_timeout, desired_capacity, 'viable_instances')
wait_for_elb(connection, module, group_name) wait_for_elb(connection, module, group_name)
try: try:
@ -834,5 +828,5 @@ def main():
changed = True changed = True
module.exit_json( changed = changed, **asg_properties ) module.exit_json( changed = changed, **asg_properties )
if __name__ == '__main__':
main() main()

View file

@ -256,12 +256,23 @@ class ElbManager:
ec2_elbs = self._get_auto_scaling_group_lbs() ec2_elbs = self._get_auto_scaling_group_lbs()
try: try:
elb = connect_to_aws(boto.ec2.elb, self.region, elb = connect_to_aws(boto.ec2.elb, self.region, **self.aws_connect_params)
**self.aws_connect_params) except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
except (boto.exception.NoAuthHandlerFound, StandardError), e:
self.module.fail_json(msg=str(e)) self.module.fail_json(msg=str(e))
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() elbs = elb.get_all_load_balancers()
break
if ec2_elbs: if ec2_elbs:
lbs = sorted(lb for lb in elbs if lb.name in ec2_elbs) lbs = sorted(lb for lb in elbs if lb.name in ec2_elbs)
@ -279,7 +290,7 @@ class ElbManager:
try: try:
asg = connect_to_aws(boto.ec2.autoscale, self.region, **self.aws_connect_params) 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)) self.module.fail_json(msg=str(e))
asg_instances = asg.get_all_autoscaling_instances([self.instance_id]) asg_instances = asg.get_all_autoscaling_instances([self.instance_id])
@ -302,9 +313,8 @@ class ElbManager:
def _get_instance(self): def _get_instance(self):
"""Returns a boto.ec2.InstanceObject for self.instance_id""" """Returns a boto.ec2.InstanceObject for self.instance_id"""
try: try:
ec2 = connect_to_aws(boto.ec2, self.region, ec2 = connect_to_aws(boto.ec2, self.region, **self.aws_connect_params)
**self.aws_connect_params) except (boto.exception.NoAuthHandlerFound, AnsibleAWSError), e:
except (boto.exception.NoAuthHandlerFound, StandardError), e:
self.module.fail_json(msg=str(e)) self.module.fail_json(msg=str(e))
return ec2.get_only_instances(instance_ids=[self.instance_id])[0] return ec2.get_only_instances(instance_ids=[self.instance_id])[0]
@ -342,8 +352,7 @@ def main():
module.fail_json(msg="ELBs are required for registration") module.fail_json(msg="ELBs are required for registration")
instance_id = module.params['instance_id'] instance_id = module.params['instance_id']
elb_man = ElbManager(module, instance_id, ec2_elbs, elb_man = ElbManager(module, instance_id, ec2_elbs, region=region, **aws_connect_params)
region=region, **aws_connect_params)
if ec2_elbs is not None: if ec2_elbs is not None:
for elb in ec2_elbs: for elb in ec2_elbs:
@ -365,4 +374,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
main() if __name__ == '__main__':
main()

View file

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

View file

@ -311,7 +311,7 @@ def main():
try: try:
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params) 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)) module.fail_json(msg=str(e))
state = module.params.get('state') state = module.params.get('state')

View file

@ -115,8 +115,6 @@ EXAMPLES = '''
''' '''
import sys
try: try:
import boto.ec2.cloudwatch import boto.ec2.cloudwatch
from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm
@ -274,7 +272,7 @@ def main():
if region: if region:
try: try:
connection = connect_to_aws(boto.ec2.cloudwatch, region, **aws_connect_params) 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)) module.fail_json(msg=str(e))
else: else:
module.fail_json(msg="region must be specified") module.fail_json(msg="region must be specified")
@ -288,4 +286,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
main() if __name__ == '__main__':
main()

View file

@ -178,7 +178,7 @@ def main():
try: try:
connection = connect_to_aws(boto.ec2.autoscale, region, **aws_connect_params) 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)) module.fail_json(msg = str(e))
if state == 'present': if state == 'present':
@ -187,4 +187,5 @@ def main():
delete_scaling_policy(connection, module) delete_scaling_policy(connection, module)
main() if __name__ == '__main__':
main()

View file

@ -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. - 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 required: false
default: 0 default: 0
version_added: "1.9" version_added: "2.0"
author: "Will Thames (@willthames)" author: "Will Thames (@willthames)"
extends_documentation_fragment: extends_documentation_fragment:

View file

@ -258,8 +258,6 @@ def boto_supports_volume_encryption():
def create_volume(module, ec2, zone): def create_volume(module, ec2, zone):
changed = False changed = False
name = module.params.get('name') name = module.params.get('name')
id = module.params.get('id')
instance = module.params.get('instance')
iops = module.params.get('iops') iops = module.params.get('iops')
encrypted = module.params.get('encrypted') encrypted = module.params.get('encrypted')
volume_size = module.params.get('volume_size') volume_size = module.params.get('volume_size')
@ -397,8 +395,6 @@ def main():
name = module.params.get('name') name = module.params.get('name')
instance = module.params.get('instance') instance = module.params.get('instance')
volume_size = module.params.get('volume_size') volume_size = module.params.get('volume_size')
volume_type = module.params.get('volume_type')
iops = module.params.get('iops')
encrypted = module.params.get('encrypted') encrypted = module.params.get('encrypted')
device_name = module.params.get('device_name') device_name = module.params.get('device_name')
zone = module.params.get('zone') zone = module.params.get('zone')
@ -424,7 +420,7 @@ def main():
if region: if region:
try: try:
ec2 = connect_to_aws(boto.ec2, region, **aws_connect_params) 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)) module.fail_json(msg=str(e))
else: else:
module.fail_json(msg="region must be specified") module.fail_json(msg="region must be specified")
@ -489,4 +485,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
main() if __name__ == '__main__':
main()

View file

@ -93,9 +93,6 @@ EXAMPLES = '''
''' '''
import time
import sys
try: try:
import boto import boto
import boto.ec2 import boto.ec2
@ -219,7 +216,7 @@ def main():
if region: if region:
try: try:
connection = connect_to_aws(boto.vpc, region, **aws_connect_params) 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)) module.fail_json(msg=str(e))
else: else:
module.fail_json(msg="region must be specified") module.fail_json(msg="region must be specified")
@ -253,7 +250,6 @@ def main():
except BotoServerError, e: except BotoServerError, e:
module.fail_json(msg=e) module.fail_json(msg=e)
# Note: Boto currently doesn't currently provide an interface to ec2-describe-vpc-attribute # 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 # which is needed in order to detect the current status of DNS options. For now we just update
# the attribute each time and is not used as a changed-factor. # the attribute each time and is not used as a changed-factor.
@ -294,4 +290,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
main() if __name__ == '__main__':
main()

View file

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

View file

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

View file

@ -829,13 +829,17 @@ def promote_db_instance(module, conn):
instance_name = module.params.get('instance_name') instance_name = module.params.get('instance_name')
result = conn.get_db_instance(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'): if result.get_data().get('replication_source'):
changed = False
else:
try: try:
result = conn.promote_read_replica(instance_name, **params) result = conn.promote_read_replica(instance_name, **params)
changed = True
except RDSException, e: except RDSException, e:
module.fail_json(msg=e.message) module.fail_json(msg=e.message)
else:
changed = False
if module.params.get('wait'): if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module) resource = await_resource(conn, result, 'available', module)

View file

@ -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) # 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): def __init__(self, error_message, *args):
super(NotModifiableError, self).__init__(error_message, *args) super(NotModifiableError, self).__init__(error_message, *args)
self.error_message = error_message self.error_message = error_message
@ -175,7 +175,7 @@ def modify_group(group, params, immediate=False):
new_params = dict(params) new_params = dict(params)
for key in new_params.keys(): for key in new_params.keys():
if group.has_key(key): if key in group:
param = group[key] param = group[key]
new_value = new_params[key] new_value = new_params[key]
@ -281,7 +281,6 @@ def main():
else: else:
break break
except BotoServerError, e: except BotoServerError, e:
module.fail_json(msg = e.error_message) module.fail_json(msg = e.error_message)
@ -297,4 +296,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
main() if __name__ == '__main__':
main()

View file

@ -295,7 +295,7 @@ def main():
overwrite = dict(required=False, type='bool'), overwrite = dict(required=False, type='bool'),
retry_interval = dict(required=False, default=500), retry_interval = dict(required=False, default=500),
private_zone = dict(required=False, type='bool', default=False), 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'), weight = dict(required=False, type='int'),
region = dict(required=False), region = dict(required=False),
health_check = 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 #Need to save this changes in rset, because of comparing rset.to_xml() == wanted_rset.to_xml() in next block
rset.name = decoded_name 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 found_record = True
record['zone'] = zone_in record['zone'] = zone_in
record['type'] = rset.type record['type'] = rset.type

View file

@ -37,6 +37,7 @@ options:
api_token: api_token:
description: description:
- DigitalOcean api token. - DigitalOcean api token.
version_added: "1.9.5"
id: id:
description: description:
- Numeric, the droplet id you want to operate on. - Numeric, the droplet id you want to operate on.
@ -100,8 +101,9 @@ options:
notes: notes:
- Two environment variables can be used, DO_API_KEY and DO_API_TOKEN. They both refer to the v2 token. - 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 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).
- 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. - 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: requirements:
- "python >= 2.6" - "python >= 2.6"
- dopy - dopy

View file

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

View file

@ -79,8 +79,11 @@ options:
version_added: "1.5" version_added: "1.5"
volumes: volumes:
description: description:
- List of volumes to mount within the container using docker CLI-style - List of volumes to mount within the container
- 'syntax: C(/host:/container[:mode]) where "mode" may be "rw" or "ro".' - '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 default: null
volumes_from: volumes_from:
description: description:
@ -626,14 +629,14 @@ class DockerManager(object):
# host mount (e.g. /mnt:/tmp, bind mounts host's /tmp to /mnt in the container) # host mount (e.g. /mnt:/tmp, bind mounts host's /tmp to /mnt in the container)
elif 2 <= len(parts) <= 3: elif 2 <= len(parts) <= 3:
# default to read-write # default to read-write
ro = False mode = 'rw'
# with supplied bind mode # with supplied bind mode
if len(parts) == 3: if len(parts) == 3:
if parts[2] not in ['ro', '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='bind mode needs to either be "ro" or "rw"') self.module.fail_json(msg='invalid bind mode ' + parts[2])
else: else:
ro = parts[2] == 'ro' mode = parts[2]
self.binds[parts[0]] = {'bind': parts[1], 'ro': ro } self.binds[parts[0]] = {'bind': parts[1], 'mode': mode }
else: else:
self.module.fail_json(msg='volumes support 1 to 3 arguments') 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(): for host_path, config in self.binds.iteritems():
if isinstance(config, dict): if isinstance(config, dict):
container_path = config['bind'] container_path = config['bind']
if config['ro']: mode = config['mode']
mode = 'ro'
else:
mode = 'rw'
else: else:
container_path = config container_path = config
mode = 'rw' mode = 'rw'

View file

@ -164,7 +164,7 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module): def _set_tenant_id(module):
global _os_tenant_id global _os_tenant_id
if not module.params['tenant_name']: if not module.params['tenant_name']:
tenant_name = module.params['login_tenant_name'] _os_tenant_id = _os_keystone.tenant_id
else: else:
tenant_name = module.params['tenant_name'] tenant_name = module.params['tenant_name']
@ -175,7 +175,6 @@ def _set_tenant_id(module):
if not _os_tenant_id: 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): def _get_net_id(neutron, module):
kwargs = { kwargs = {
'tenant_id': _os_tenant_id, 'tenant_id': _os_tenant_id,

View file

@ -136,18 +136,17 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module): def _set_tenant_id(module):
global _os_tenant_id global _os_tenant_id
if not module.params['tenant_name']: if not module.params['tenant_name']:
login_tenant_name = module.params['login_tenant_name'] _os_tenant_id = _os_keystone.tenant_id
else: else:
login_tenant_name = module.params['tenant_name'] tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list(): for tenant in _os_keystone.tenants.list():
if tenant.name == login_tenant_name: if tenant.name == tenant_name:
_os_tenant_id = tenant.id _os_tenant_id = tenant.id
break break
if not _os_tenant_id: 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): def _get_router_id(module, neutron):
kwargs = { kwargs = {
'name': module.params['name'], 'name': module.params['name'],

View file

@ -138,18 +138,17 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module): def _set_tenant_id(module):
global _os_tenant_id global _os_tenant_id
if not module.params['tenant_name']: if not module.params['tenant_name']:
login_tenant_name = module.params['login_tenant_name'] _os_tenant_id = _os_keystone.tenant_id
else: else:
login_tenant_name = module.params['tenant_name'] tenant_name = module.params['tenant_name']
for tenant in _os_keystone.tenants.list(): for tenant in _os_keystone.tenants.list():
if tenant.name == login_tenant_name: if tenant.name == tenant_name:
_os_tenant_id = tenant.id _os_tenant_id = tenant.id
break break
if not _os_tenant_id: 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): def _get_router_id(module, neutron):
kwargs = { kwargs = {
'name': module.params['router_name'], 'name': module.params['router_name'],

View file

@ -170,7 +170,7 @@ def _get_neutron_client(module, kwargs):
def _set_tenant_id(module): def _set_tenant_id(module):
global _os_tenant_id global _os_tenant_id
if not module.params['tenant_name']: if not module.params['tenant_name']:
tenant_name = module.params['login_tenant_name'] _os_tenant_id = _os_keystone.tenant_id
else: else:
tenant_name = module.params['tenant_name'] tenant_name = module.params['tenant_name']

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env python2
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of Ansible # This file is part of Ansible
@ -170,6 +170,7 @@ EXAMPLES = '''
vcpu.hotadd: yes vcpu.hotadd: yes
mem.hotadd: yes mem.hotadd: yes
notes: This is a test VM notes: This is a test VM
folder: MyFolder
vm_disk: vm_disk:
disk1: disk1:
size_gb: 10 size_gb: 10
@ -241,6 +242,8 @@ EXAMPLES = '''
template_src: centosTemplate template_src: centosTemplate
cluster: MainCluster cluster: MainCluster
resource_pool: "/Resources" resource_pool: "/Resources"
vm_extra_config:
folder: MyFolder
# Task to gather facts from a vSphere cluster only if the system is a VMWare guest # 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 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) vmTemplate = vsphere_client.get_vm_by_name(template_src)
vmTarget = None vmTarget = None
@ -689,6 +692,10 @@ def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, mo
cloneArgs["linked"] = True cloneArgs["linked"] = True
cloneArgs["snapshot"] = snapshot_to_clone 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) vmTemplate.clone(guest, **cloneArgs)
changed = True changed = True
else: 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 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): 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 spec = None
changed = False changed = False
changes = {} changes = {}
request = VI.ReconfigVM_TaskRequestMsg() request = None
shutdown = False shutdown = False
poweron = vm.is_powered_on() 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) cpuHotAddEnabled = bool(vm.properties.config.cpuHotAddEnabled)
cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled) cpuHotRemoveEnabled = bool(vm.properties.config.cpuHotRemoveEnabled)
changed, changes = update_disks(vsphere_client, vm,
module, vm_disk, changes)
request = VI.ReconfigVM_TaskRequestMsg()
# Change Memory # Change Memory
if 'memory_mb' in vm_hardware: if 'memory_mb' in vm_hardware:
@ -1556,7 +1632,8 @@ def main():
module=module, module=module,
cluster_name=cluster, cluster_name=cluster,
snapshot_to_clone=snapshot_to_clone, 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']: if state in ['restarted', 'reconfigured']:

View file

@ -47,12 +47,12 @@ options:
default: null default: null
creates: creates:
description: 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 required: no
default: null default: null
removes: removes:
description: 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" version_added: "0.8"
required: no required: no
default: null default: null

View file

@ -85,12 +85,15 @@ notes:
- Requires the MySQLdb Python package on the remote host. For Ubuntu, this - 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 apt-get install python-mysqldb. (See M(apt).) For CentOS/Fedora, this
is as easy as yum install MySQL-python. (See M(yum).) 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 - 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 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 the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
default login of C(root) with no password. default login of C(root) with no password.
requirements: [ ConfigParser ] requirements: [ ConfigParser ]
author: "Mark Theunissen (@marktheunissen)" author: "Ansible Core Team"
''' '''
EXAMPLES = ''' EXAMPLES = '''

View file

@ -42,6 +42,13 @@ options:
- set the user's password. (Required when adding a user) - set the user's password. (Required when adding a user)
required: false required: false
default: null 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: host:
description: description:
- the 'host' part of the MySQL username - 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 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 new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
the file." the file."
- Currently, there is only support for the `mysql_native_password` encryted password hash module.
requirements: [ "MySQLdb" ] requirements: [ "MySQLdb" ]
author: "Mark Theunissen (@marktheunissen)" author: "Jonathan Mainguy (@Jmainguy)"
''' '''
EXAMPLES = """ EXAMPLES = """
# Create database user with name 'bob' and password '12345' with all database privileges # Create database user with name 'bob' and password '12345' with all database privileges
- mysql_user: name=bob password=12345 priv=*.*:ALL state=present - 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' # 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 - mysql_user: name=bob password=12345 priv=*.*:ALL,GRANT state=present
@ -176,6 +187,7 @@ password=n<_665{vS43y
import getpass import getpass
import tempfile import tempfile
import re import re
import string
try: try:
import MySQLdb import MySQLdb
except ImportError: except ImportError:
@ -224,6 +236,19 @@ def connect(module, login_user=None, login_password=None, config_file=''):
db_connection = MySQLdb.connect(**config) db_connection = MySQLdb.connect(**config)
return db_connection.cursor() 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): def user_exists(cursor, user, host, host_all):
if host_all: if host_all:
cursor.execute("SELECT count(*) FROM user WHERE user = %s", user) cursor.execute("SELECT count(*) FROM user WHERE user = %s", user)
@ -233,37 +258,94 @@ def user_exists(cursor, user, host, host_all):
count = cursor.fetchone() count = cursor.fetchone()
return count[0] > 0 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 # we cannot create users without a proper hostname
if host_all: if host_all:
return False return False
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)) cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
if new_priv is not None: if new_priv is not None:
for db_table, priv in new_priv.iteritems(): for db_table, priv in new_priv.iteritems():
privileges_grant(cursor, user,host,db_table,priv) privileges_grant(cursor, user,host,db_table,priv)
return True 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 changed = False
grant_option = 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: if host_all:
hostnames = user_get_hostnames(cursor, user) hostnames = user_get_hostnames(cursor, user)
else: else:
hostnames = [host] hostnames = [host]
for host in hostnames: for host in hostnames:
# Handle passwords # Handle clear text and hashed passwords.
if password is not None: 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)) 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() current_pass_hash = cursor.fetchone()
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,)) cursor.execute("SELECT PASSWORD(%s)", (password,))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
new_pass_hash = cursor.fetchone() new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]: if current_pass_hash[0] != new_pass_hash[0]:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user,host,password)) 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 changed = True
# Handle privileges # Handle privileges
@ -440,6 +522,7 @@ def main():
user=dict(required=True, aliases=['name']), user=dict(required=True, aliases=['name']),
user_anonymous=dict(type="bool", default="no"), user_anonymous=dict(type="bool", default="no"),
password=dict(default=None, no_log=True), password=dict(default=None, no_log=True),
encrypted=dict(default=False, type='bool'),
host=dict(default="localhost"), host=dict(default="localhost"),
host_all=dict(type="bool", default="no"), host_all=dict(type="bool", default="no"),
state=dict(default="present", choices=["absent", "present"]), state=dict(default="present", choices=["absent", "present"]),
@ -455,6 +538,7 @@ def main():
user = module.params["user"] user = module.params["user"]
user_anonymous = module.params["user_anonymous"] user_anonymous = module.params["user_anonymous"]
password = module.params["password"] password = module.params["password"]
encrypted = module.boolean(module.params["encrypted"])
host = module.params["host"].lower() host = module.params["host"].lower()
host_all = module.params["host_all"] host_all = module.params["host_all"]
state = module.params["state"] state = module.params["state"]
@ -494,9 +578,9 @@ def main():
if user_exists(cursor, user, host, host_all): if user_exists(cursor, user, host, host_all):
try: try:
if update_password == 'always': 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: 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: except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e)) module.fail_json(msg=str(e))
@ -506,7 +590,7 @@ def main():
if host_all: if host_all:
module.fail_json(msg="host_all parameter cannot be used when adding a user") module.fail_json(msg="host_all parameter cannot be used when adding a user")
try: 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: except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e)) module.fail_json(msg=str(e))
elif state == "absent": elif state == "absent":

View file

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

View file

@ -27,7 +27,7 @@ module: copy
version_added: "historical" version_added: "historical"
short_description: Copies files to remote locations. short_description: Copies files to remote locations.
description: 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: options:
src: src:
description: description:
@ -310,7 +310,7 @@ def main():
if rc != 0: if rc != 0:
module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc,err)) module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc,err))
if remote_src: if remote_src:
tmpdest = tempfile.mkstemp(dir=os.basedir(dest)) _, tmpdest = tempfile.mkstemp(dir=os.path.dirname(dest))
shutil.copy2(src, tmpdest) shutil.copy2(src, tmpdest)
module.atomic_move(tmpdest, dest) module.atomic_move(tmpdest, dest)
else: else:

View file

@ -157,8 +157,8 @@ def main():
original_basename = dict(required=False), # Internal use only, for recursive ops original_basename = dict(required=False), # Internal use only, for recursive ops
recurse = dict(default=False, type='bool'), recurse = dict(default=False, type='bool'),
force = dict(required=False, default=False, type='bool'), force = dict(required=False, default=False, type='bool'),
diff_peek = dict(default=None), diff_peek = dict(default=None), # Internal use only, for internal checks in the action plugins
validate = dict(required=False, default=None), validate = dict(required=False, default=None), # Internal use only, for template and copy
src = dict(required=False, default=None), src = dict(required=False, default=None),
), ),
add_file_common_args=True, add_file_common_args=True,
@ -288,7 +288,7 @@ def main():
except OSError, ex: except OSError, ex:
# Possibly something else created the dir since the os.path.exists # 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. # 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 raise
tmp_file_args = file_args.copy() tmp_file_args = file_args.copy()
tmp_file_args['path']=curpath tmp_file_args['path']=curpath

View file

@ -97,6 +97,23 @@ EXAMPLES = '''
import ConfigParser import ConfigParser
import sys 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 # do_ini
@ -104,6 +121,11 @@ import sys
def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False): 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') ini_file = open(filename, 'r')
try: try:
ini_lines = ini_file.readlines() 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 within_section and option:
if state == 'present': if state == 'present':
# change the existing option line # change the existing option line
if re.match('%s *=' % option, line) \ if match_opt(option, line):
or re.match('# *%s *=' % option, line) \
or re.match('; *%s *=' % option, line):
newline = '%s = %s\n' % (option, value) newline = '%s = %s\n' % (option, value)
changed = ini_lines[index] != newline changed = ini_lines[index] != newline
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] line = ini_lines[index]
if line.startswith('['): if line.startswith('['):
break break
if re.match('%s *=' % option, line): if match_active_opt(option, line):
del ini_lines[index] del ini_lines[index]
else: else:
index = index + 1 index = index + 1
break break
else: else:
# comment out the existing option line # 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] ini_lines[index] = '#%s' % ini_lines[index]
changed = True changed = True
break break

View file

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

View file

@ -250,7 +250,7 @@ def pick_handler(src, dest, module):
obj = handler(src, dest, module) obj = handler(src, dest, module)
if obj.can_handle_archive(): if obj.can_handle_archive():
return obj 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(): def main():

View file

@ -55,6 +55,14 @@ options:
If C(dest) is a directory, the file will always be If C(dest) is a directory, the file will always be
downloaded (regardless of the force option), but replaced only if the contents changed. downloaded (regardless of the force option), but replaced only if the contents changed.
required: true 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: force:
description: description:
- If C(yes) and C(dest) is not a directory, will download the file every - 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 'index.html'
return fn 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. 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: if info['status'] != 200:
module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url, dest=dest) module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url, dest=dest)
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() fd, tempname = tempfile.mkstemp()
f = os.fdopen(fd, 'wb') f = os.fdopen(fd, 'wb')
try: try:
shutil.copyfileobj(rsp, f) shutil.copyfileobj(rsp, f)
@ -235,6 +255,7 @@ def main():
checksum = dict(default=''), checksum = dict(default=''),
timeout = dict(required=False, type='int', default=10), timeout = dict(required=False, type='int', default=10),
headers = dict(required=False, default=None), headers = dict(required=False, default=None),
tmp_dest = dict(required=False, default=''),
) )
module = AnsibleModule( module = AnsibleModule(
@ -250,6 +271,7 @@ def main():
checksum = module.params['checksum'] checksum = module.params['checksum']
use_proxy = module.params['use_proxy'] use_proxy = module.params['use_proxy']
timeout = module.params['timeout'] timeout = module.params['timeout']
tmp_dest = os.path.expanduser(module.params['tmp_dest'])
# Parse headers to dict # Parse headers to dict
if module.params['headers']: if module.params['headers']:
@ -303,7 +325,7 @@ def main():
last_mod_time = datetime.datetime.utcfromtimestamp(mtime) last_mod_time = datetime.datetime.utcfromtimestamp(mtime)
# download to tmpsrc # 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 # Now the request has completed, we can finally generate the final
# destination file name from the info dict. # destination file name from the info dict.

View file

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

View file

@ -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) - 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 required: false
default: null default: null
aliases: [ 'pkg', 'package' ]
state: state:
description: 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. - Indicates the desired package state. C(latest) ensures that the latest version is installed. C(build-dep) ensures the package build dependencies are installed.

View file

@ -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." - "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 required: true
default: null default: null
aliases: [] aliases: [ 'pkg' ]
exclude: exclude:
description: description:
- "Package name(s) to exclude when state=present, or latest" - "Package name(s) to exclude when state=present, or latest"
@ -65,9 +65,9 @@ options:
default: null default: null
state: state:
description: 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 required: false
choices: [ "present", "latest", "absent" ] choices: [ "present", "installed", "latest", "absent", "removed" ]
default: "present" default: "present"
enablerepo: enablerepo:
description: description:
@ -117,6 +117,16 @@ options:
choices: ["yes", "no"] choices: ["yes", "no"]
aliases: [] 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: notes:
- When used with a loop of package names in a playbook, ansible optimizes - 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 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 = yum.YumBase()
my.preconf.debuglevel=0 my.preconf.debuglevel=0
my.preconf.errorlevel=0 my.preconf.errorlevel=0
my.preconf.plugins = True
if conf_file and os.path.exists(conf_file): if conf_file and os.path.exists(conf_file):
my.preconf.fn = conf_file my.preconf.fn = conf_file
if os.geteuid() != 0: if os.geteuid() != 0:
@ -965,6 +976,7 @@ def main():
conf_file=dict(default=None), conf_file=dict(default=None),
disable_gpg_check=dict(required=False, default="no", type='bool'), disable_gpg_check=dict(required=False, default="no", type='bool'),
update_cache=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 # this should not be needed, but exists as a failsafe
install_repoquery=dict(required=False, default="yes", type='bool'), install_repoquery=dict(required=False, default="yes", type='bool'),
), ),

View file

@ -55,7 +55,7 @@ options:
version_added: "1.5" version_added: "1.5"
description: description:
- if C(yes), adds the hostkey for the repo url if not already - 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. this parameter is ignored.
ssh_opts: ssh_opts:
required: false required: false

View file

@ -80,6 +80,15 @@ options:
choices: [ "yes", "no" ] choices: [ "yes", "no" ]
default: "no" default: "no"
version_added: "1.9" 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: description:
- "Adds or removes authorized keys for particular user accounts" - "Adds or removes authorized keys for particular user accounts"
author: "Ansible Core Team" author: "Ansible Core Team"
@ -93,27 +102,30 @@ EXAMPLES = '''
- authorized_key: user=charlie key=https://github.com/charlie.keys - authorized_key: user=charlie key=https://github.com/charlie.keys
# Using alternate directory locations: # Using alternate directory locations:
- authorized_key: user=charlie - authorized_key:
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}" user: charlie
path='/etc/ssh/authorized_keys/charlie' key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
manage_dir=no path: '/etc/ssh/authorized_keys/charlie'
manage_dir: no
# Using with_file # Using with_file
- name: Set up authorized_keys for the deploy user - name: Set up authorized_keys for the deploy user
authorized_key: user=deploy authorized_key: user=deploy key="{{ item }}"
key="{{ item }}"
with_file: with_file:
- public_keys/doe-jane - public_keys/doe-jane
- public_keys/doe-john - public_keys/doe-john
# Using key_options: # Using key_options:
- authorized_key: user=charlie - authorized_key:
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}" user: charlie
key_options='no-port-forwarding,from="10.0.1.1"' 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 # Set up authorized_keys exclusively with one key
- authorized_key: user=root key="{{ item }}" state=present - authorized_key: user=root key="{{ item }}" state=present exclusive=yes
exclusive=yes
with_file: with_file:
- public_keys/doe-jane - public_keys/doe-jane
''' '''
@ -358,6 +370,7 @@ def enforce_state(module, params):
state = params.get("state", "present") state = params.get("state", "present")
key_options = params.get("key_options", None) key_options = params.get("key_options", None)
exclusive = params.get("exclusive", False) exclusive = params.get("exclusive", False)
validate_certs = params.get("validate_certs", True)
error_msg = "Error getting key from: %s" error_msg = "Error getting key from: %s"
# if the key is a url, request it and use it as key source # 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'), key_options = dict(required=False, type='str'),
unique = dict(default=False, type='bool'), unique = dict(default=False, type='bool'),
exclusive = dict(default=False, type='bool'), exclusive = dict(default=False, type='bool'),
validate_certs = dict(default=True, type='bool'),
), ),
supports_check_mode=True supports_check_mode=True
) )

View file

@ -260,8 +260,8 @@ class SystemdStrategy(GenericStrategy):
(rc, out, err)) (rc, out, err))
def get_permanent_hostname(self): def get_permanent_hostname(self):
cmd = 'hostnamectl --static status' cmd = ['hostnamectl', '--static', 'status']
rc, out, err = self.module.run_command(cmd, use_unsafe_shell=True) rc, out, err = self.module.run_command(cmd)
if rc != 0: if rc != 0:
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
(rc, out, err)) (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): class FedoraHostname(Hostname):
platform = 'Linux' platform = 'Linux'
distribution = 'Fedora' distribution = 'Fedora'
@ -541,6 +592,12 @@ class SolarisHostname(Hostname):
distribution = None distribution = None
strategy_class = SolarisStrategy strategy_class = SolarisStrategy
class FreeBSDHostname(Hostname):
platform = 'FreeBSD'
distribution = None
strategy_class = FreeBSDStrategy
# =========================================== # ===========================================
def main(): def main():

View file

@ -23,7 +23,7 @@ DOCUMENTATION = '''
--- ---
module: ping module: ping
version_added: historical 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: description:
- A trivial test module, this module always returns C(pong) on successful - 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 contact. It does not make sense in playbooks, but it is useful from

View file

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

View file

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

View file

@ -68,6 +68,7 @@ Set-Attr $date "year" (Get-Date -format yyyy)
Set-Attr $date "month" (Get-Date -format MM) Set-Attr $date "month" (Get-Date -format MM)
Set-Attr $date "day" (Get-Date -format dd) Set-Attr $date "day" (Get-Date -format dd)
Set-Attr $date "hour" (Get-Date -format HH) 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 $date "iso8601" (Get-Date -format s)
Set-Attr $result.ansible_facts "ansible_date_time" $date Set-Attr $result.ansible_facts "ansible_date_time" $date

10
windows/win_copy.py Normal file → Executable file
View file

@ -44,16 +44,6 @@ options:
required: true required: true
default: null default: null
author: "Jon Hawkesworth (@jhawkesworth)" 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 = ''' EXAMPLES = '''