mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2024-09-14 20:13:21 +02:00 
			
		
		
		
	`decoded_name` was created twice, each from `rset.name` So, the second call to `.replace(r'\100', '@')` overwrites decoded_name, discarding the result of the call to `.replace(r'\052', '*')` I had a problem with wildcard domains that was fixed by this patch.
		
			
				
	
	
		
			273 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python
 | 
						|
# This file is part of Ansible
 | 
						|
#
 | 
						|
# Ansible is free software: you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU General Public License as published by
 | 
						|
# the Free Software Foundation, either version 3 of the License, or
 | 
						|
# (at your option) any later version.
 | 
						|
#
 | 
						|
# Ansible is distributed in the hope that it will be useful,
 | 
						|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
# GNU General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
DOCUMENTATION = '''
 | 
						|
---
 | 
						|
module: route53
 | 
						|
version_added: "1.3"
 | 
						|
short_description: add or delete entries in Amazons Route53 DNS service
 | 
						|
description:
 | 
						|
     - Creates and deletes DNS records in Amazons Route53 service
 | 
						|
options:
 | 
						|
  command:
 | 
						|
    description:
 | 
						|
      - Specifies the action to take.  
 | 
						|
    required: true
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
    choices: [ 'get', 'create', 'delete' ]
 | 
						|
  zone:
 | 
						|
    description:
 | 
						|
      - The DNS zone to modify
 | 
						|
    required: true
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
  record:
 | 
						|
    description:
 | 
						|
      - The full DNS record to create or delete
 | 
						|
    required: true
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
  ttl:
 | 
						|
    description:
 | 
						|
      - The TTL to give the new record
 | 
						|
    required: false
 | 
						|
    default: 3600 (one hour)
 | 
						|
    aliases: []
 | 
						|
  type:
 | 
						|
    description:
 | 
						|
      - The type of DNS record to create
 | 
						|
    required: true
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
    choices: [ 'A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS' ]
 | 
						|
  value:
 | 
						|
    description:
 | 
						|
      - The new value when creating a DNS record.  Multiple comma-spaced values are allowed.  When deleting a record all values for the record must be specified or Route53 will not delete it.
 | 
						|
    required: false
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
  aws_secret_key:
 | 
						|
    description:
 | 
						|
      - AWS secret key. 
 | 
						|
    required: false
 | 
						|
    default: null
 | 
						|
    aliases: ['ec2_secret_key', 'secret_key']
 | 
						|
  aws_access_key:
 | 
						|
    description:
 | 
						|
      - AWS access key. 
 | 
						|
    required: false
 | 
						|
    default: null
 | 
						|
    aliases: ['ec2_access_key', 'access_key']
 | 
						|
  overwrite:
 | 
						|
    description:
 | 
						|
      - Whether an existing record should be overwritten on create if values do not match
 | 
						|
    required: false
 | 
						|
    default: null
 | 
						|
    aliases: []
 | 
						|
requirements: [ "boto" ]
 | 
						|
author: Bruce Pennypacker
 | 
						|
'''
 | 
						|
 | 
						|
EXAMPLES = '''
 | 
						|
# Add new.foo.com as an A record with 3 IPs
 | 
						|
- route53: >
 | 
						|
      command=create
 | 
						|
      zone=foo.com
 | 
						|
      record=new.foo.com
 | 
						|
      type=A
 | 
						|
      ttl=7200
 | 
						|
      value=1.1.1.1,2.2.2.2,3.3.3.3
 | 
						|
 | 
						|
# Retrieve the details for new.foo.com
 | 
						|
- route53: >
 | 
						|
      command=get
 | 
						|
      zone=foo.com
 | 
						|
      record=new.foo.com
 | 
						|
      type=A
 | 
						|
  register: rec
 | 
						|
 | 
						|
# Delete new.foo.com A record using the results from the get command
 | 
						|
- route53: >
 | 
						|
      command=delete
 | 
						|
      zone=foo.com
 | 
						|
      record={{ rec.set.record }}
 | 
						|
      type={{ rec.set.type }}
 | 
						|
      value={{ rec.set.value }}
 | 
						|
 | 
						|
# Add an AAAA record.  Note that because there are colons in the value
 | 
						|
# that the entire parameter list must be quoted:
 | 
						|
- route53: >
 | 
						|
      command=create
 | 
						|
      zone=foo.com
 | 
						|
      record=localhost.foo.com
 | 
						|
      type=AAAA
 | 
						|
      ttl=7200
 | 
						|
      value="::1"
 | 
						|
 | 
						|
# Add a TXT record. Note that TXT and SPF records must be surrounded
 | 
						|
# by quotes when sent to Route 53:
 | 
						|
- route53: >
 | 
						|
      command=create
 | 
						|
      zone=foo.com
 | 
						|
      record=localhost.foo.com
 | 
						|
      type=TXT
 | 
						|
      ttl=7200
 | 
						|
      value="\"bar\""
 | 
						|
 | 
						|
 | 
						|
'''
 | 
						|
 | 
						|
import sys
 | 
						|
import time
 | 
						|
 | 
						|
try:
 | 
						|
    import boto
 | 
						|
    from boto import route53
 | 
						|
    from boto.route53.record import ResourceRecordSets
 | 
						|
except ImportError:
 | 
						|
    print "failed=True msg='boto required for this module'"
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
def commit(changes):
 | 
						|
    """Commit changes, but retry PriorRequestNotComplete errors."""
 | 
						|
    retry = 10
 | 
						|
    while True:
 | 
						|
        try:
 | 
						|
            retry -= 1
 | 
						|
            return changes.commit()
 | 
						|
        except boto.route53.exception.DNSServerError, e:
 | 
						|
            code = e.body.split("<Code>")[1]
 | 
						|
            code = code.split("</Code>")[0]
 | 
						|
            if code != 'PriorRequestNotComplete' or retry < 0:
 | 
						|
                raise e
 | 
						|
            time.sleep(500)
 | 
						|
 | 
						|
def main():
 | 
						|
    argument_spec = ec2_argument_spec()
 | 
						|
    argument_spec.update(dict(
 | 
						|
            command         = dict(choices=['get', 'create', 'delete'], required=True),
 | 
						|
            zone            = dict(required=True),
 | 
						|
            record          = dict(required=True),
 | 
						|
            ttl             = dict(required=False, default=3600),
 | 
						|
            type            = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True),
 | 
						|
            value           = dict(required=False), 
 | 
						|
            overwrite       = dict(required=False, type='bool')
 | 
						|
        )
 | 
						|
    )
 | 
						|
    module = AnsibleModule(argument_spec=argument_spec)
 | 
						|
 | 
						|
    command_in            = module.params.get('command')
 | 
						|
    zone_in               = module.params.get('zone')
 | 
						|
    ttl_in                = module.params.get('ttl')
 | 
						|
    record_in             = module.params.get('record')
 | 
						|
    type_in               = module.params.get('type')
 | 
						|
    value_in              = module.params.get('value')
 | 
						|
 | 
						|
    ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module)
 | 
						|
 | 
						|
    value_list = ()
 | 
						|
 | 
						|
    if type(value_in) is str:
 | 
						|
        if value_in:
 | 
						|
            value_list = sorted(value_in.split(','))
 | 
						|
    elif type(value_in)  is list:
 | 
						|
        value_list = sorted(value_in)
 | 
						|
 | 
						|
    if zone_in[-1:] != '.':
 | 
						|
        zone_in += "."
 | 
						|
 | 
						|
    if record_in[-1:] != '.':
 | 
						|
        record_in += "."
 | 
						|
 | 
						|
    if command_in == 'create' or command_in == 'delete':
 | 
						|
        if not value_in:
 | 
						|
            module.fail_json(msg = "parameter 'value' required for create/delete")
 | 
						|
 | 
						|
    # connect to the route53 endpoint 
 | 
						|
    try:
 | 
						|
        conn = boto.route53.connection.Route53Connection(aws_access_key, aws_secret_key)
 | 
						|
    except boto.exception.BotoServerError, e:
 | 
						|
        module.fail_json(msg = e.error_message)
 | 
						|
 | 
						|
    # Get all the existing hosted zones and save their ID's
 | 
						|
    zones = {}
 | 
						|
    results = conn.get_all_hosted_zones()
 | 
						|
    for r53zone in results['ListHostedZonesResponse']['HostedZones']:
 | 
						|
        zone_id = r53zone['Id'].replace('/hostedzone/', '')
 | 
						|
        zones[r53zone['Name']] = zone_id
 | 
						|
 | 
						|
    # Verify that the requested zone is already defined in Route53
 | 
						|
    if not zone_in in zones:
 | 
						|
        errmsg = "Zone %s does not exist in Route53" % zone_in
 | 
						|
        module.fail_json(msg = errmsg)
 | 
						|
 | 
						|
    record = {}
 | 
						|
    
 | 
						|
    found_record = False
 | 
						|
    sets = conn.get_all_rrsets(zones[zone_in])
 | 
						|
    for rset in sets:
 | 
						|
        # Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round
 | 
						|
        # tripping of things like * and @.
 | 
						|
        decoded_name = rset.name.replace(r'\052', '*')
 | 
						|
        decoded_name = decoded_name.replace(r'\100', '@')
 | 
						|
 | 
						|
        if rset.type == type_in and decoded_name == record_in:
 | 
						|
            found_record = True
 | 
						|
            record['zone'] = zone_in
 | 
						|
            record['type'] = rset.type
 | 
						|
            record['record'] = decoded_name
 | 
						|
            record['ttl'] = rset.ttl
 | 
						|
            record['value'] = ','.join(sorted(rset.resource_records))
 | 
						|
            record['values'] = sorted(rset.resource_records)
 | 
						|
            if value_list == sorted(rset.resource_records) and record['ttl'] == ttl_in and command_in == 'create':
 | 
						|
                module.exit_json(changed=False)
 | 
						|
 | 
						|
    if command_in == 'get':
 | 
						|
        module.exit_json(changed=False, set=record)
 | 
						|
 | 
						|
    if command_in == 'delete' and not found_record:
 | 
						|
        module.exit_json(changed=False)
 | 
						|
 | 
						|
    changes = ResourceRecordSets(conn, zones[zone_in])
 | 
						|
 | 
						|
    if command_in == 'create' and found_record:
 | 
						|
        if not module.params['overwrite']:
 | 
						|
            module.fail_json(msg = "Record already exists with different value. Set 'overwrite' to replace it")
 | 
						|
        else:
 | 
						|
            change = changes.add_change("DELETE", record_in, type_in, record['ttl'])
 | 
						|
        for v in record['values']:
 | 
						|
            change.add_value(v)
 | 
						|
 | 
						|
    if command_in == 'create' or command_in == 'delete':
 | 
						|
        change = changes.add_change(command_in.upper(), record_in, type_in, ttl_in)
 | 
						|
        for v in value_list:
 | 
						|
            change.add_value(v)
 | 
						|
 | 
						|
    try:
 | 
						|
        result = commit(changes)
 | 
						|
    except boto.route53.exception.DNSServerError, e:
 | 
						|
        txt = e.body.split("<Message>")[1]
 | 
						|
        txt = txt.split("</Message>")[0]
 | 
						|
        module.fail_json(msg = txt)
 | 
						|
 | 
						|
    module.exit_json(changed=True)
 | 
						|
 | 
						|
# import module snippets
 | 
						|
from ansible.module_utils.basic import *
 | 
						|
from ansible.module_utils.ec2 import *
 | 
						|
 | 
						|
main()
 |