From e9a9d28b6dd9d6e90a96b285d5e17c32e3d6c8b3 Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Wed, 15 Apr 2015 13:43:00 +0300 Subject: [PATCH 01/14] Add support for specifying unique hosted zone identifier --- cloud/amazon/route53.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index f89fca448b7..75d3a66ae6b 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -35,6 +35,12 @@ options: required: true default: null aliases: [] + hosted_zone_id: + description: + - The Hosted Zone ID of the DNS zone to modify + required: false + default: null + aliases: [] record: description: - The full DNS record to create or delete @@ -156,6 +162,16 @@ EXAMPLES = ''' alias=True alias_hosted_zone_id="{{ elb_zone_id }}" +# Add an AAAA record with Hosted Zone ID. Note that because there are colons in the value +# that the entire parameter list must be quoted: +- route53: + command: "create" + zone: "foo.com" + hostes_zone_id: "Z2AABBCCDDEEFF" + record: "localhost.foo.com" + type: "AAAA" + ttl: "7200" + value: "::1" ''' @@ -191,6 +207,7 @@ def main(): argument_spec.update(dict( command = dict(choices=['get', 'create', 'delete'], required=True), zone = dict(required=True), + hosted_zone_id = dict(required=False), record = dict(required=True), ttl = dict(required=False, default=3600), type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True), @@ -209,6 +226,7 @@ def main(): command_in = module.params.get('command') zone_in = module.params.get('zone').lower() + hosted_zone_id_in = module.params.get('hosted_zone_id') ttl_in = module.params.get('ttl') record_in = module.params.get('record').lower() type_in = module.params.get('type') @@ -257,9 +275,17 @@ def main(): # the private_zone_in boolean specified in the params if module.boolean(r53zone['Config'].get('PrivateZone', False)) == private_zone_in: zone_id = r53zone['Id'].replace('/hostedzone/', '') - zones[r53zone['Name']] = zone_id + # only save when unique hosted_zone_id is given and is equal + # hosted_zone_id_in is specified in the params + if hosted_zone_id_in and zone_id == hosted_zone_id_in: + zones[r53zone['Name']] = zone_id + elif not hosted_zone_id_in: + zones[r53zone['Name']] = zone_id # Verify that the requested zone is already defined in Route53 + if not zone_in in zones and hosted_zone_id_in: + errmsg = "Hosted_zone_id %s does not exist in Route53" % hosted_zone_id_in + module.fail_json(msg = errmsg) if not zone_in in zones: errmsg = "Zone %s does not exist in Route53" % zone_in module.fail_json(msg = errmsg) @@ -282,6 +308,8 @@ def main(): record['ttl'] = rset.ttl record['value'] = ','.join(sorted(rset.resource_records)) record['values'] = sorted(rset.resource_records) + if hosted_zone_id_in: + record['hosted_zone_id'] = hosted_zone_id_in if rset.alias_dns_name: record['alias'] = True record['value'] = rset.alias_dns_name From cee4ef0fc3a6e4f21ec9787b2ce002aa4a05bd91 Mon Sep 17 00:00:00 2001 From: dagnello Date: Fri, 19 Jun 2015 10:45:12 -0700 Subject: [PATCH 02/14] Resolving secgroup.id issue in this module secgroup['id'] was not being returned in all cases where the specified security group exists. --- cloud/openstack/os_security_group.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index 51e7df772a1..86e6de0b023 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -48,6 +48,8 @@ options: - Should the resource be present or absent. choices: [present, absent] default: present + +requirements: ["shade"] ''' EXAMPLES = ''' @@ -114,24 +116,24 @@ def main(): if module.check_mode: module.exit_json(changed=_system_state_change(module, secgroup)) - changed = False if state == 'present': if not secgroup: secgroup = cloud.create_security_group(name, description) - changed = True + module.exit_json(changed=True, id=secgroup['id']) else: if _needs_update(module, secgroup): secgroup = cloud.update_security_group( secgroup['id'], description=description) - changed = True - module.exit_json( - changed=changed, id=secgroup.id, secgroup=secgroup) + module.exit_json(changed=True, id=secgroup['id']) + else: + module.exit_json(changed=False, id=secgroup['id']) if state == 'absent': - if secgroup: + if not secgroup: + module.exit_json(changed=False) + else: cloud.delete_security_group(secgroup['id']) - changed=True - module.exit_json(changed=changed) + module.exit_json(changed=True) except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) From 887b88ea73aaf1ed81fc15398b004674b15f3ec3 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 19 Jun 2015 17:06:12 -0400 Subject: [PATCH 03/14] Make sure we're always returning objects too --- cloud/openstack/os_security_group.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index 86e6de0b023..7fba28c8cb9 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -116,24 +116,24 @@ def main(): if module.check_mode: module.exit_json(changed=_system_state_change(module, secgroup)) + changed = False if state == 'present': if not secgroup: secgroup = cloud.create_security_group(name, description) - module.exit_json(changed=True, id=secgroup['id']) + changed = True else: if _needs_update(module, secgroup): secgroup = cloud.update_security_group( secgroup['id'], description=description) - module.exit_json(changed=True, id=secgroup['id']) - else: - module.exit_json(changed=False, id=secgroup['id']) + changed = True + module.exit_json( + changed=changed, id=secgroup['id'], secgroup=secgroup) if state == 'absent': - if not secgroup: - module.exit_json(changed=False) - else: + if secgroup: cloud.delete_security_group(secgroup['id']) - module.exit_json(changed=True) + changed = True + module.exit_json(changed=changed) except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) From 1ae299d00ff2fe18abd7b1d01b18f384301afccf Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 19 Jun 2015 17:39:57 -0400 Subject: [PATCH 04/14] Remove duplicate shade requirement --- cloud/openstack/os_security_group.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index 7fba28c8cb9..e42b7f938f5 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -48,8 +48,6 @@ options: - Should the resource be present or absent. choices: [present, absent] default: present - -requirements: ["shade"] ''' EXAMPLES = ''' From 84fd824f75ca30b0854634f6a99e7c0cdb90a029 Mon Sep 17 00:00:00 2001 From: murdochr Date: Sat, 20 Jun 2015 21:36:10 +0100 Subject: [PATCH 05/14] Change docs to reflect correct when syntax for matching variable strings as per MD's forum post as this fails with unhelpful error otherwise. https://groups.google.com/forum/#!topic/ansible-project/D2hQzZ_jNuM --- network/basics/uri.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/basics/uri.py b/network/basics/uri.py index b7fa8282c83..3de17c12d60 100644 --- a/network/basics/uri.py +++ b/network/basics/uri.py @@ -156,7 +156,7 @@ EXAMPLES = ''' register: webpage - action: fail - when: 'AWESOME' not in "{{ webpage.content }}" + when: "'illustrative' not in webpage.content" # Create a JIRA issue From 87404fa7987b182c1ccc05197656140174da54e2 Mon Sep 17 00:00:00 2001 From: Hagai Kariti Date: Tue, 30 Sep 2014 11:13:54 +0300 Subject: [PATCH 06/14] Hostname module should update ansible_hostname --- system/hostname.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/hostname.py b/system/hostname.py index 882402a5e21..d9193641eb2 100644 --- a/system/hostname.py +++ b/system/hostname.py @@ -509,6 +509,6 @@ def main(): hostname.set_permanent_hostname(name) changed = True - module.exit_json(changed=changed, name=name) + module.exit_json(changed=changed, name=name, ansible_facts=dict(ansible_hostname=name)) main() From e93b5c672476b34fd81327c3976c92b74d57c0d7 Mon Sep 17 00:00:00 2001 From: Tom Paine Date: Mon, 22 Jun 2015 14:52:45 +0100 Subject: [PATCH 07/14] Parse out space characters in route53 value list Fixes: https://github.com/ansible/ansible-modules-core/issues/992 --- cloud/amazon/route53.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index 67700060d9f..d25be6b99ea 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -224,7 +224,7 @@ def main(): if type(value_in) is str: if value_in: - value_list = sorted(value_in.split(',')) + value_list = sorted([s.strip() for s in value_in.split(',')]) elif type(value_in) is list: value_list = sorted(value_in) From 617d5750a6be96d68e5412832a79c0fa8229ffbc Mon Sep 17 00:00:00 2001 From: Andrea Mandolo Date: Tue, 23 Jun 2015 07:14:30 +0200 Subject: [PATCH 08/14] Added "EC2 instance" termination_protection and source_dest_check changeability at run-time --- cloud/amazon/ec2.py | 53 +++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/cloud/amazon/ec2.py b/cloud/amazon/ec2.py index 20d49ce5995..dc7d5d38dd3 100644 --- a/cloud/amazon/ec2.py +++ b/cloud/amazon/ec2.py @@ -190,6 +190,13 @@ options: required: false default: yes choices: [ "yes", "no" ] + termination_protection: + version_added: "2.0" + description: + - Enable or Disable the Termination Protection + required: false + default: no + choices: [ "yes", "no" ] state: version_added: "1.3" description: @@ -786,6 +793,7 @@ def create_instances(module, ec2, vpc, override_count=None): exact_count = module.params.get('exact_count') count_tag = module.params.get('count_tag') source_dest_check = module.boolean(module.params.get('source_dest_check')) + termination_protection = module.boolean(module.params.get('termination_protection')) # group_id and group_name are exclusive of each other if group_id and group_name: @@ -1014,11 +1022,16 @@ def create_instances(module, ec2, vpc, override_count=None): for res in res_list: running_instances.extend(res.instances) - # Enabled by default by Amazon - if not source_dest_check: + # Enabled by default by AWS + if source_dest_check is False: for inst in res.instances: inst.modify_attribute('sourceDestCheck', False) + # Disabled by default by AWS + if termination_protection is True: + for inst in res.instances: + inst.modify_attribute('disableApiTermination', True) + # Leave this as late as possible to try and avoid InvalidInstanceID.NotFound if instance_tags: try: @@ -1135,21 +1148,32 @@ def startstop_instances(module, ec2, instance_ids, state): if not isinstance(instance_ids, list) or len(instance_ids) < 1: module.fail_json(msg='instance_ids should be a list of instances, aborting') - # Check that our instances are not in the state we want to take them to - # and change them to our desired state + # Check (and eventually change) instances attributes and instances state running_instances_array = [] for res in ec2.get_all_instances(instance_ids): for inst in res.instances: - if inst.state != state: - instance_dict_array.append(get_instance_info(inst)) - try: - if state == 'running': - inst.start() - else: - inst.stop() - except EC2ResponseError, e: - module.fail_json(msg='Unable to change state for instance {0}, error: {1}'.format(inst.id, e)) - changed = True + + # Check "source_dest_check" attribute + if inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check: + inst.modify_attribute('sourceDestCheck', source_dest_check) + changed = True + + # Check "termination_protection" attribute + if inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection: + inst.modify_attribute('disableApiTermination', termination_protection) + changed = True + + # Check instance state + if inst.state != state: + instance_dict_array.append(get_instance_info(inst)) + try: + if state == 'running': + inst.start() + else: + inst.stop() + except EC2ResponseError, e: + module.fail_json(msg='Unable to change state for instance {0}, error: {1}'.format(inst.id, e)) + changed = True ## Wait for all the instances to finish starting or stopping wait_timeout = time.time() + wait_timeout @@ -1200,6 +1224,7 @@ def main(): instance_profile_name = dict(), instance_ids = dict(type='list', aliases=['instance_id']), source_dest_check = dict(type='bool', default=True), + termination_protection = dict(type='bool', default=False), state = dict(default='present', choices=['present', 'absent', 'running', 'stopped']), exact_count = dict(type='int', default=None), count_tag = dict(), From 6cdfbb72f01468192965e45ce6c019b2ea44ea65 Mon Sep 17 00:00:00 2001 From: Andrea Mandolo Date: Mon, 22 Jun 2015 17:13:42 +0200 Subject: [PATCH 09/14] Added some block_device_mapping (disks) informations to EC2 instance module ouput --- cloud/amazon/ec2.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cloud/amazon/ec2.py b/cloud/amazon/ec2.py index dc7d5d38dd3..ad2f8f8f71b 100644 --- a/cloud/amazon/ec2.py +++ b/cloud/amazon/ec2.py @@ -619,6 +619,19 @@ def get_instance_info(inst): except AttributeError: instance_info['ebs_optimized'] = False + try: + bdm_dict = {} + bdm = getattr(inst, 'block_device_mapping') + for device_name in bdm.keys(): + bdm_dict[device_name] = { + 'status': bdm[device_name].status, + 'volume_id': bdm[device_name].volume_id, + 'delete_on_termination': bdm[device_name].delete_on_termination + } + instance_info['block_device_mapping'] = bdm_dict + except AttributeError: + instance_info['block_device_mapping'] = False + try: instance_info['tenancy'] = getattr(inst, 'placement_tenancy') except AttributeError: From 110f618487de51ba41cf0ce94f2d5574c6f54d09 Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Wed, 15 Apr 2015 13:43:00 +0300 Subject: [PATCH 10/14] Add support for specifying unique hosted zone identifier --- cloud/amazon/route53.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index b1c8591b25c..30557a2212a 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -35,6 +35,12 @@ options: required: true default: null aliases: [] + hosted_zone_id: + description: + - The Hosted Zone ID of the DNS zone to modify + required: false + default: null + aliases: [] record: description: - The full DNS record to create or delete @@ -195,6 +201,17 @@ EXAMPLES = ''' alias=True alias_hosted_zone_id="{{ elb_zone_id }}" +# Add an AAAA record with Hosted Zone ID. Note that because there are colons in the value +# that the entire parameter list must be quoted: +- route53: + command: "create" + zone: "foo.com" + hostes_zone_id: "Z2AABBCCDDEEFF" + record: "localhost.foo.com" + type: "AAAA" + ttl: "7200" + value: "::1" + # Use a routing policy to distribute traffic: - route53: command: "create" @@ -252,6 +269,7 @@ def main(): argument_spec.update(dict( command = dict(choices=['get', 'create', 'delete'], required=True), zone = dict(required=True), + hosted_zone_id = dict(required=False), record = dict(required=True), ttl = dict(required=False, type='int', default=3600), type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True), @@ -275,6 +293,7 @@ def main(): command_in = module.params.get('command') zone_in = module.params.get('zone').lower() + hosted_zone_id_in = module.params.get('hosted_zone_id') ttl_in = module.params.get('ttl') record_in = module.params.get('record').lower() type_in = module.params.get('type') @@ -360,6 +379,8 @@ def main(): record['region'] = rset.region record['failover'] = rset.failover record['health_check'] = rset.health_check + if hosted_zone_id_in: + record['hosted_zone_id'] = hosted_zone_id_in if rset.alias_dns_name: record['alias'] = True record['value'] = rset.alias_dns_name From 71ebe6321b241501e40f0908ce84daf7e918ac8d Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Wed, 15 Apr 2015 13:43:00 +0300 Subject: [PATCH 11/14] Add support for specifying unique hosted zone identifier --- cloud/amazon/route53.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index 30557a2212a..8dd781ffdf2 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -212,6 +212,17 @@ EXAMPLES = ''' ttl: "7200" value: "::1" +# Add an AAAA record with Hosted Zone ID. Note that because there are colons in the value +# that the entire parameter list must be quoted: +- route53: + command: "create" + zone: "foo.com" + hostes_zone_id: "Z2AABBCCDDEEFF" + record: "localhost.foo.com" + type: "AAAA" + ttl: "7200" + value: "::1" + # Use a routing policy to distribute traffic: - route53: command: "create" @@ -374,6 +385,8 @@ def main(): record['ttl'] = rset.ttl record['value'] = ','.join(sorted(rset.resource_records)) record['values'] = sorted(rset.resource_records) + if hosted_zone_id_in: + record['hosted_zone_id'] = hosted_zone_id_in record['identifier'] = rset.identifier record['weight'] = rset.weight record['region'] = rset.region From f0ad6c5a1fd3f93d776097619231d1cd4860e520 Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Mon, 6 Jul 2015 13:02:24 +0300 Subject: [PATCH 12/14] Fix hosted_zone_id after rebase. --- cloud/amazon/route53.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index 8dd781ffdf2..a981c6ef2be 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -250,13 +250,13 @@ try: except ImportError: HAS_BOTO = False -def get_zone_by_name(conn, module, zone_name, want_private): - """Finds a zone by name""" +def get_zone_by_name(conn, module, zone_name, want_private, zone_id): + """Finds a zone by name or zone_id""" for zone in conn.get_zones(): # only save this zone id if the private status of the zone matches # the private_zone_in boolean specified in the params private_zone = module.boolean(zone.config.get('PrivateZone', False)) - if private_zone == want_private and zone.name == zone_name: + if private_zone == want_private and ((zone.name == zone_name and zone_id == None) or zone.id.replace('/hostedzone/', '') == zone_id): return zone return None @@ -280,7 +280,7 @@ def main(): argument_spec.update(dict( command = dict(choices=['get', 'create', 'delete'], required=True), zone = dict(required=True), - hosted_zone_id = dict(required=False), + hosted_zone_id = dict(required=False, default=None), record = dict(required=True), ttl = dict(required=False, type='int', default=3600), type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True), @@ -351,7 +351,7 @@ def main(): module.fail_json(msg = e.error_message) # Find the named zone ID - zone = get_zone_by_name(conn, module, zone_in, private_zone_in) + zone = get_zone_by_name(conn, module, zone_in, private_zone_in, hosted_zone_id_in) # Verify that the requested zone is already defined in Route53 if zone is None: From 228c03bd670449813c3d3d45fa4a7767ad924774 Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Mon, 6 Jul 2015 13:07:33 +0300 Subject: [PATCH 13/14] Add version number --- cloud/amazon/route53.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index a981c6ef2be..e81e5083763 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -39,6 +39,7 @@ options: description: - The Hosted Zone ID of the DNS zone to modify required: false + version_added: 2.0 default: null aliases: [] record: From 041dc8b5877eabd130c79618dedcebb40d3c138b Mon Sep 17 00:00:00 2001 From: Juho-Mikko Pellinen Date: Mon, 6 Jul 2015 13:08:46 +0300 Subject: [PATCH 14/14] Remove empty aliases --- cloud/amazon/route53.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py index e81e5083763..f9702cc38ae 100644 --- a/cloud/amazon/route53.py +++ b/cloud/amazon/route53.py @@ -41,7 +41,6 @@ options: required: false version_added: 2.0 default: null - aliases: [] record: description: - The full DNS record to create or delete