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
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'),

View file

@ -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))
@ -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()))
def elb_healthy(asg_connection, elb_connection, module, group_name):
healthy_instances = []
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
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')
@ -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:
@ -834,5 +828,5 @@ def main():
changed = True
module.exit_json( changed = changed, **asg_properties )
if __name__ == '__main__':
main()

View file

@ -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 = []
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]
@ -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 *
if __name__ == '__main__':
main()

View file

@ -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 *
if __name__ == '__main__':
main()

View file

@ -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')

View file

@ -115,8 +115,6 @@ EXAMPLES = '''
'''
import sys
try:
import boto.ec2.cloudwatch
from boto.ec2.cloudwatch import CloudWatchConnection, MetricAlarm
@ -274,7 +272,7 @@ def main():
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 *
if __name__ == '__main__':
main()

View file

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

View file

@ -258,8 +258,6 @@ def boto_supports_volume_encryption():
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')
@ -397,8 +395,6 @@ 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')
@ -424,7 +420,7 @@ def main():
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")
@ -489,4 +485,5 @@ def main():
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
if __name__ == '__main__':
main()

View file

@ -93,9 +93,6 @@ EXAMPLES = '''
'''
import time
import sys
try:
import boto
import boto.ec2
@ -219,7 +216,7 @@ def main():
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")
@ -253,7 +250,6 @@ def main():
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
# 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.ec2 import *
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):
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
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:
if user_exists:
try:
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)
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)

View file

@ -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:

View file

@ -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)

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)
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 *
if __name__ == '__main__':
main()

View file

@ -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

View file

@ -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

View file

@ -29,12 +29,10 @@ options:
- Indicate desired state of the target.
default: present
choices: ['present', 'absent']
client_id:
api_token:
description:
- DigitalOcean manager id.
api_key:
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'),

View file

@ -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'

View file

@ -164,7 +164,7 @@ 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']
@ -175,7 +175,6 @@ def _set_tenant_id(module):
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,

View file

@ -136,18 +136,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:
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['name'],

View file

@ -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:
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'],

View file

@ -170,7 +170,7 @@ 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']

View file

@ -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)

View file

@ -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']:

View file

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

View file

@ -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']:

View file

@ -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

View file

@ -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 = '''

View file

@ -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
@ -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,37 +258,94 @@ 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
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:
# 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()
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
@ -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":

View file

@ -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 = ""

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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():

View file

@ -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)
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,6 +271,7 @@ 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']:
@ -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.

View file

@ -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

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)
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.

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."
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'),
),

View file

@ -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

View file

@ -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
)

View file

@ -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():

View file

@ -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

View file

@ -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,6 +1683,7 @@ 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.
if self.groups:
(rc, _out, _err, changed) = self._modify_group()
out += _out
err += _err
@ -1684,6 +1694,7 @@ class DarwinUser(User):
out = ''
err = ''
if self.group:
self._make_group_numerical()
for field in self.fields:
@ -1707,6 +1718,7 @@ class DarwinUser(User):
err += _err
changed = rc
if self.groups:
(rc, _out, _err, _changed) = self._modify_group()
out += _out
err += _err
@ -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'),

View file

@ -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)

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 "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

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

@ -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 = '''