Пример #1
0
    def update_alias_record(self,
                            old_record,
                            new_identifier=None,
                            alias_dns_name=None,
                            comment=""):
        """
        Update an existing alias record in this Zone.  Returns a Status object.

        :type old_record: ResourceRecord
        :param old_record: A ResourceRecord (e.g. returned by find_records)

        See _new_alias_record for additional parameter documentation.
        """
        record = copy.copy(old_record)
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        changes.add_change_record("DELETE", record)

        if alias_dns_name:
            self._new_alias_record(changes, record.type, record.name,
                                   new_identifier, record.alias_hosted_zone_id,
                                   alias_dns_name, comment)
        else:
            self._new_alias_record(changes, record.type, record.name,
                                   new_identifier, record.alias_hosted_zone_id,
                                   record.alias_dns_name, comment)

        return Status(self.route53connection, self._commit(changes))
Пример #2
0
    def delete_record(self, record, comment=""):
        """
        Delete one or more records from this Zone.  Returns a Status object.

        :param record: A ResourceRecord (e.g. returned by
           find_records) or list, tuple, or set of ResourceRecords.

        :type comment: str
        :param comment: A comment that will be stored with the change.
        """
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        if type(record) in [list, tuple, set]:
            for r in record:
                changes.add_change_record("DELETE", r)
        else:
            changes.add_change_record("DELETE", record)
        return Status(self.route53connection, self._commit(changes))
Пример #3
0
    def update_record(self, old_record, new_value, new_ttl=None,
                      new_identifier=None, comment=""):
        """
        Update an existing record in this Zone.  Returns a Status object.

        :type old_record: ResourceRecord
        :param old_record: A ResourceRecord (e.g. returned by find_records)

        See _new_record for additional parameter documentation.
        """
        new_ttl = new_ttl or default_ttl
        record = copy.copy(old_record)
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        changes.add_change_record("DELETE", record)
        self._new_record(changes, record.type, record.name,
                         new_value, new_ttl, new_identifier, comment)
        return Status(self.route53connection, self._commit(changes))
Пример #4
0
Файл: zone.py Проект: 10sr/hue
    def delete_record(self, record, comment=""):
        """
        Delete one or more records from this Zone.  Returns a Status object.

        :param record: A ResourceRecord (e.g. returned by
           find_records) or list, tuple, or set of ResourceRecords.

        :type comment: str
        :param comment: A comment that will be stored with the change.
        """
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        if type(record) in [list, tuple, set]:
            for r in record:
                changes.add_change_record("DELETE", r)
        else:
            changes.add_change_record("DELETE", record)
        return Status(self.route53connection, self._commit(changes))
Пример #5
0
Файл: zone.py Проект: 10sr/hue
    def update_record(self, old_record, new_value, new_ttl=None,
                      new_identifier=None, comment=""):
        """
        Update an existing record in this Zone.  Returns a Status object.

        :type old_record: ResourceRecord
        :param old_record: A ResourceRecord (e.g. returned by find_records)

        See _new_record for additional parameter documentation.
        """
        new_ttl = new_ttl or default_ttl
        record = copy.copy(old_record)
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        changes.add_change_record("DELETE", record)
        self._new_record(changes, record.type, record.name,
                         new_value, new_ttl, new_identifier, comment)
        return Status(self.route53connection, self._commit(changes))
Пример #6
0
    def update_alias_record(self, old_record, new_identifier=None,
                            alias_dns_name=None, comment=""):
        """
        Update an existing alias record in this Zone.  Returns a Status object.

        :type old_record: ResourceRecord
        :param old_record: A ResourceRecord (e.g. returned by find_records)

        See _new_alias_record for additional parameter documentation.
        """
        record = copy.copy(old_record)
        changes = ResourceRecordSets(self.route53connection, self.id, comment)
        changes.add_change_record("DELETE", record)

        if alias_dns_name:
            self._new_alias_record(
                changes, record.type, record.name, new_identifier,
                record.alias_hosted_zone_id, alias_dns_name, comment)
        else:
            self._new_alias_record(
                changes, record.type, record.name, new_identifier,
                record.alias_hosted_zone_id, record.alias_dns_name, comment)

        return Status(self.route53connection, self._commit(changes))
Пример #7
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
        command                      = dict(choices=['get', 'create', 'delete'], required=True),
        zone                         = dict(required=True),
        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', 'SOA'], required=True),
        alias                        = dict(required=False, type='bool'),
        alias_hosted_zone_id         = dict(required=False),
        alias_evaluate_target_health = dict(required=False, type='bool', default=False),
        value                        = dict(required=False),
        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, default=None),
        weight                       = dict(required=False, type='int'),
        region                       = dict(required=False),
        health_check                 = dict(required=False),
        failover                     = dict(required=False,choices=['PRIMARY','SECONDARY']),
        vpc_id                       = dict(required=False),
        wait                         = dict(required=False, type='bool', default=False),
        wait_timeout                 = dict(required=False, type='int', default=300),
    )
    )
    module = AnsibleModule(argument_spec=argument_spec)

    if not HAS_BOTO:
        module.fail_json(msg='boto required for this module')

    if distutils.version.StrictVersion(boto.__version__) < distutils.version.StrictVersion(MINIMUM_BOTO_VERSION):
        module.fail_json(msg='Found boto in version %s, but >= %s is required' % (boto.__version__, MINIMUM_BOTO_VERSION))

    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')
    value_in                        = module.params.get('value')
    alias_in                        = module.params.get('alias')
    alias_hosted_zone_id_in         = module.params.get('alias_hosted_zone_id')
    alias_evaluate_target_health_in = module.params.get('alias_evaluate_target_health')
    retry_interval_in               = module.params.get('retry_interval')
    private_zone_in                 = module.params.get('private_zone')
    identifier_in                   = module.params.get('identifier')
    weight_in                       = module.params.get('weight')
    region_in                       = module.params.get('region')
    health_check_in                 = module.params.get('health_check')
    failover_in                     = module.params.get('failover')
    vpc_id_in                       = module.params.get('vpc_id')
    wait_in                         = module.params.get('wait')
    wait_timeout_in                 = module.params.get('wait_timeout')

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    value_list = ()

    if isinstance(value_in, str):
        if value_in:
            value_list = sorted([s.strip() for s in value_in.split(',')])
    elif isinstance(value_in, 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")
        elif alias_in:
            if len(value_list) != 1:
                module.fail_json(msg = "parameter 'value' must contain a single dns name for alias create/delete")
            elif not alias_hosted_zone_id_in:
                module.fail_json(msg = "parameter 'alias_hosted_zone_id' required for alias create/delete")
        elif ( weight_in is not None or region_in is not None or failover_in is not None ) and identifier_in is None:
            module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier")

    if command_in == 'create':
        if ( weight_in is not None or region_in is not None or failover_in is not None ) and identifier_in is None:
          module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier")
        elif ( weight_in is None and region_in is None and failover_in is None ) and identifier_in is not None:
          module.fail_json(msg= "You have specified identifier which makes sense only if you specify one of: weight, region or failover.")



    if vpc_id_in and not private_zone_in:
        module.fail_json(msg="parameter 'private_zone' must be true when specifying parameter"
            " 'vpc_id'")


    # connect to the route53 endpoint
    try:
        conn = Route53Connection(**aws_connect_kwargs)
    except boto.exception.BotoServerError as e:
        module.fail_json(msg = e.error_message)

    # Find the named zone ID
    zone = get_zone_by_name(conn, module, zone_in, private_zone_in, hosted_zone_id_in, vpc_id_in)

    # Verify that the requested zone is already defined in Route53
    if zone is None:
        errmsg = "Zone %s does not exist in Route53" % zone_in
        module.fail_json(msg = errmsg)

    record = {}

    found_record = False
    wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in,
        identifier=identifier_in, weight=weight_in, region=region_in,
        health_check=health_check_in, failover=failover_in)
    for v in value_list:
        if alias_in:
            wanted_rset.set_alias(alias_hosted_zone_id_in, v, alias_evaluate_target_health_in)
        else:
            wanted_rset.add_value(v)

    sets = conn.get_all_rrsets(zone.id, name=record_in, type=type_in, identifier=identifier_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', '@')
        #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 identifier_in is not None:
            identifier_in = str(identifier_in)

        if rset.type == type_in and decoded_name.lower() == record_in.lower() and rset.identifier == identifier_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 hosted_zone_id_in:
                record['hosted_zone_id'] = hosted_zone_id_in
            record['identifier'] = rset.identifier
            record['weight'] = rset.weight
            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
              record['values'] = [rset.alias_dns_name]
              record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id
              record['alias_evaluate_target_health'] = rset.alias_evaluate_target_health
            else:
              record['alias'] = False
              record['value'] = ','.join(sorted(rset.resource_records))
              record['values'] = sorted(rset.resource_records)
            if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml():
                module.exit_json(changed=False)
            break

    if command_in == 'get':
        if type_in == 'NS':
            ns = record['values']
        else:
            # Retrieve name servers associated to the zone.
            ns = conn.get_zone(zone_in).get_nameservers()

        module.exit_json(changed=False, set=record, nameservers=ns)

    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone.id)

    if command_in == 'create' or command_in == 'delete':
        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")
            command = 'UPSERT'
        else:
            command = command_in.upper()
        changes.add_change_record(command, wanted_rset)

    try:
        result = invoke_with_throttling_retries(commit, changes, retry_interval_in, wait_in, wait_timeout_in)
    except boto.route53.exception.DNSServerError as e:
        txt = e.body.split("<Message>")[1]
        txt = txt.split("</Message>")[0]
        if "but it already exists" in txt:
                module.exit_json(changed=False)
        else:
                module.fail_json(msg = txt)
    except TimeoutError:
        module.fail_json(msg='Timeout waiting for changes to replicate')

    module.exit_json(changed=True)
Пример #8
0
    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone.id)

    if command_in == 'create' or command_in == 'delete':
        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"
                )
            command = 'UPSERT'
        else:
            command = command_in.upper()
        changes.add_change_record(command, wanted_rset)

    try:
        result = commit(changes, retry_interval_in)
    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 *
Пример #9
0
    def test_record_commit(self):
        rrsets = ResourceRecordSets(self.service_connection)
        rrsets.add_change_record(
            'CREATE', Record('vanilla.example.com', 'A', 60, ['1.2.3.4']))
        rrsets.add_change_record(
            'CREATE',
            Record('alias.example.com',
                   'AAAA',
                   alias_hosted_zone_id='Z123OTHER',
                   alias_dns_name='target.other',
                   alias_evaluate_target_health=True))
        rrsets.add_change_record(
            'CREATE',
            Record('wrr.example.com',
                   'CNAME',
                   60, ['cname.target'],
                   weight=10,
                   identifier='weight-1'))
        rrsets.add_change_record(
            'CREATE',
            Record('lbr.example.com',
                   'TXT',
                   60, ['text record'],
                   region='us-west-2',
                   identifier='region-1'))
        rrsets.add_change_record(
            'CREATE',
            Record('failover.example.com',
                   'A',
                   60, ['2.2.2.2'],
                   health_check='hc-1234',
                   failover='PRIMARY',
                   identifier='primary'))

        changes_xml = rrsets.to_xml()

        # the whitespacing doesn't match exactly, so we'll pretty print and drop all new lines
        # not the best, but
        actual_xml = re.sub(
            r"\s*[\r\n]+", "\n",
            xml.dom.minidom.parseString(changes_xml).toprettyxml())
        expected_xml = re.sub(
            r"\s*[\r\n]+", "\n",
            xml.dom.minidom.parseString(b"""
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
    <ChangeBatch>
        <Comment>None</Comment>
        <Changes>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>vanilla.example.com</Name>
                    <Type>A</Type>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>1.2.3.4</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>alias.example.com</Name>
                    <Type>AAAA</Type>
                    <AliasTarget>
                        <HostedZoneId>Z123OTHER</HostedZoneId>
                        <DNSName>target.other</DNSName>
                        <EvaluateTargetHealth>true</EvaluateTargetHealth>
                    </AliasTarget>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>wrr.example.com</Name>
                    <Type>CNAME</Type>
                    <SetIdentifier>weight-1</SetIdentifier>
                    <Weight>10</Weight>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>cname.target</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>lbr.example.com</Name>
                    <Type>TXT</Type>
                    <SetIdentifier>region-1</SetIdentifier>
                    <Region>us-west-2</Region>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>text record</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>failover.example.com</Name>
                    <Type>A</Type>
                    <SetIdentifier>primary</SetIdentifier>
                    <Failover>PRIMARY</Failover>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>2.2.2.2</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                    <HealthCheckId>hc-1234</HealthCheckId>
                </ResourceRecordSet>
            </Change>
        </Changes>
    </ChangeBatch>
</ChangeResourceRecordSetsRequest>
        """).toprettyxml())

        # Note: the alias XML should not include the TTL, even if it's specified in the object model
        self.assertEqual(actual_xml, expected_xml)
Пример #10
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(
        dict(
            state=dict(
                type='str',
                required=True,
                choices=['absent', 'create', 'delete', 'get', 'present'],
                aliases=['command']),
            zone=dict(type='str'),
            hosted_zone_id=dict(type='str'),
            record=dict(type='str', required=True),
            ttl=dict(type='int', default=3600),
            type=dict(type='str',
                      required=True,
                      choices=[
                          'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR',
                          'SOA', 'SPF', 'SRV', 'TXT'
                      ]),
            alias=dict(type='bool'),
            alias_hosted_zone_id=dict(type='str'),
            alias_evaluate_target_health=dict(type='bool', default=False),
            value=dict(type='list'),
            overwrite=dict(type='bool'),
            retry_interval=dict(type='int', default=500),
            private_zone=dict(type='bool', default=False),
            identifier=dict(type='str'),
            weight=dict(type='int'),
            region=dict(type='str'),
            health_check=dict(type='str'),
            failover=dict(type='str', choices=['PRIMARY', 'SECONDARY']),
            vpc_id=dict(type='str'),
            wait=dict(type='bool', default=False),
            wait_timeout=dict(type='int', default=300),
        ))

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
        required_one_of=[['zone', 'hosted_zone_id']],
        # If alias is True then you must specify alias_hosted_zone as well
        required_together=[['alias', 'alias_hosted_zone_id']],
        # state=present, absent, create, delete THEN value is required
        required_if=(
            ('state', 'present', ['value']),
            ('state', 'create', ['value']),
            ('state', 'absent', ['value']),
            ('state', 'delete', ['value']),
        ),
        # failover, region and weight are mutually exclusive
        mutually_exclusive=[('failover', 'region', 'weight')],
        # failover, region and weight require identifier
        required_by=dict(
            failover=('identifier', ),
            region=('identifier', ),
            weight=('identifier', ),
        ),
    )

    if not HAS_BOTO:
        module.fail_json(msg='boto required for this module')

    if distutils.version.StrictVersion(
            boto.__version__) < distutils.version.StrictVersion(
                MINIMUM_BOTO_VERSION):
        module.fail_json(
            msg='Found boto in version %s, but >= %s is required' %
            (boto.__version__, MINIMUM_BOTO_VERSION))

    if module.params['state'] in ('present', 'create'):
        command_in = 'create'
    elif module.params['state'] in ('absent', 'delete'):
        command_in = 'delete'
    elif module.params['state'] == 'get':
        command_in = 'get'

    zone_in = (module.params.get('zone') or '').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')
    value_in = module.params.get('value') or []
    alias_in = module.params.get('alias')
    alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id')
    alias_evaluate_target_health_in = module.params.get(
        'alias_evaluate_target_health')
    retry_interval_in = module.params.get('retry_interval')

    if module.params['vpc_id'] is not None:
        private_zone_in = True
    else:
        private_zone_in = module.params.get('private_zone')

    identifier_in = module.params.get('identifier')
    weight_in = module.params.get('weight')
    region_in = module.params.get('region')
    health_check_in = module.params.get('health_check')
    failover_in = module.params.get('failover')
    vpc_id_in = module.params.get('vpc_id')
    wait_in = module.params.get('wait')
    wait_timeout_in = module.params.get('wait_timeout')

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    if zone_in[-1:] != '.':
        zone_in += "."

    if record_in[-1:] != '.':
        record_in += "."

    if command_in == 'create' or command_in == 'delete':
        if alias_in and len(value_in) != 1:
            module.fail_json(
                msg=
                "parameter 'value' must contain a single dns name for alias records"
            )
        if (weight_in is None and region_in is None
                and failover_in is None) and identifier_in is not None:
            module.fail_json(
                msg=
                "You have specified identifier which makes sense only if you specify one of: weight, region or failover."
            )

    # connect to the route53 endpoint
    try:
        conn = Route53Connection(**aws_connect_kwargs)
    except boto.exception.BotoServerError as e:
        module.fail_json(msg=e.error_message)

    # Find the named zone ID
    zone_id = hosted_zone_id_in or get_zone_id_by_name(
        conn, module, zone_in, private_zone_in, vpc_id_in)

    # Verify that the requested zone is already defined in Route53
    if zone_id is None:
        errmsg = "Zone %s does not exist in Route53" % (zone_in
                                                        or hosted_zone_id_in)
        module.fail_json(msg=errmsg)

    record = {}

    found_record = False
    wanted_rset = Record(name=record_in,
                         type=type_in,
                         ttl=ttl_in,
                         identifier=identifier_in,
                         weight=weight_in,
                         region=region_in,
                         health_check=health_check_in,
                         failover=failover_in)
    for v in value_in:
        if alias_in:
            wanted_rset.set_alias(alias_hosted_zone_id_in, v,
                                  alias_evaluate_target_health_in)
        else:
            wanted_rset.add_value(v)

    need_to_sort_records = (type_in == 'CAA')

    # Sort records for wanted_rset if necessary (keep original list)
    unsorted_records = wanted_rset.resource_records
    if need_to_sort_records:
        wanted_rset.resource_records = sorted(unsorted_records)

    sets = invoke_with_throttling_retries(conn.get_all_rrsets,
                                          zone_id,
                                          name=record_in,
                                          type=type_in,
                                          identifier=identifier_in)
    sets_iter = iter(sets)
    while True:
        try:
            rset = invoke_with_throttling_retries(next, sets_iter)
        except StopIteration:
            break
        # 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', '@')
        # 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 identifier_in is not None:
            identifier_in = str(identifier_in)

        if rset.type == type_in and decoded_name.lower() == record_in.lower(
        ) and rset.identifier == identifier_in:
            if need_to_sort_records:
                # Sort records
                rset.resource_records = sorted(rset.resource_records)
            found_record = True
            record['zone'] = zone_in
            record['type'] = rset.type
            record['record'] = decoded_name
            record['ttl'] = rset.ttl
            record['identifier'] = rset.identifier
            record['weight'] = rset.weight
            record['region'] = rset.region
            record['failover'] = rset.failover
            record['health_check'] = rset.health_check
            record['hosted_zone_id'] = zone_id
            if rset.alias_dns_name:
                record['alias'] = True
                record['value'] = rset.alias_dns_name
                record['values'] = [rset.alias_dns_name]
                record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id
                record[
                    'alias_evaluate_target_health'] = rset.alias_evaluate_target_health
            else:
                record['alias'] = False
                record['value'] = ','.join(sorted(rset.resource_records))
                record['values'] = sorted(rset.resource_records)
            if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml(
            ):
                module.exit_json(changed=False)

        # We need to look only at the first rrset returned by the above call,
        # so break here. The returned elements begin with the one matching our
        # requested name, type, and identifier, if such an element exists,
        # followed by all others that come after it in alphabetical order.
        # Therefore, if the first set does not match, no subsequent set will
        # match either.
        break

    if command_in == 'get':
        if type_in == 'NS':
            ns = record.get('values', [])
        else:
            # Retrieve name servers associated to the zone.
            z = invoke_with_throttling_retries(conn.get_zone, zone_in)
            ns = invoke_with_throttling_retries(z.get_nameservers)

        module.exit_json(changed=False, set=record, nameservers=ns)

    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone_id)

    if command_in == 'create' or command_in == 'delete':
        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"
                )
            command = 'UPSERT'
        else:
            command = command_in.upper()
        # Restore original order of records
        wanted_rset.resource_records = unsorted_records
        changes.add_change_record(command, wanted_rset)

    if not module.check_mode:
        try:
            invoke_with_throttling_retries(commit, changes, retry_interval_in,
                                           wait_in, wait_timeout_in)
        except boto.route53.exception.DNSServerError as e:
            txt = e.body.split("<Message>")[1]
            txt = txt.split("</Message>")[0]
            if "but it already exists" in txt:
                module.exit_json(changed=False)
            else:
                module.fail_json(msg=txt)
        except TimeoutError:
            module.fail_json(msg='Timeout waiting for changes to replicate')

    module.exit_json(changed=True)
Пример #11
0
    def test_record_commit(self):
        rrsets = ResourceRecordSets(self.service_connection)
        rrsets.add_change_record('CREATE', Record('vanilla.example.com', 'A', 60, ['1.2.3.4']))
        rrsets.add_change_record('CREATE', Record('alias.example.com', 'AAAA', alias_hosted_zone_id='Z123OTHER', alias_dns_name='target.other', alias_evaluate_target_health=True))
        rrsets.add_change_record('CREATE', Record('wrr.example.com', 'CNAME', 60, ['cname.target'], weight=10, identifier='weight-1'))
        rrsets.add_change_record('CREATE', Record('lbr.example.com', 'TXT', 60, ['text record'], region='us-west-2', identifier='region-1'))
        rrsets.add_change_record('CREATE', Record('failover.example.com', 'A', 60, ['2.2.2.2'], health_check='hc-1234', failover='PRIMARY', identifier='primary'))

        changes_xml = rrsets.to_xml()

        # the whitespacing doesn't match exactly, so we'll pretty print and drop all new lines
        # not the best, but
        actual_xml = re.sub(r"\s*[\r\n]+", "\n", xml.dom.minidom.parseString(changes_xml).toprettyxml())
        expected_xml = re.sub(r"\s*[\r\n]+", "\n", xml.dom.minidom.parseString(b"""
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
    <ChangeBatch>
        <Comment>None</Comment>
        <Changes>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>vanilla.example.com</Name>
                    <Type>A</Type>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>1.2.3.4</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>alias.example.com</Name>
                    <Type>AAAA</Type>
                    <AliasTarget>
                        <HostedZoneId>Z123OTHER</HostedZoneId>
                        <DNSName>target.other</DNSName>
                        <EvaluateTargetHealth>true</EvaluateTargetHealth>
                    </AliasTarget>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>wrr.example.com</Name>
                    <Type>CNAME</Type>
                    <SetIdentifier>weight-1</SetIdentifier>
                    <Weight>10</Weight>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>cname.target</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>lbr.example.com</Name>
                    <Type>TXT</Type>
                    <SetIdentifier>region-1</SetIdentifier>
                    <Region>us-west-2</Region>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>text record</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                </ResourceRecordSet>
            </Change>
            <Change>
                <Action>CREATE</Action>
                <ResourceRecordSet>
                    <Name>failover.example.com</Name>
                    <Type>A</Type>
                    <SetIdentifier>primary</SetIdentifier>
                    <Failover>PRIMARY</Failover>
                    <TTL>60</TTL>
                    <ResourceRecords>
                        <ResourceRecord>
                            <Value>2.2.2.2</Value>
                        </ResourceRecord>
                    </ResourceRecords>
                    <HealthCheckId>hc-1234</HealthCheckId>
                </ResourceRecordSet>
            </Change>
        </Changes>
    </ChangeBatch>
</ChangeResourceRecordSetsRequest>
        """).toprettyxml())

        # Note: the alias XML should not include the TTL, even if it's specified in the object model
        self.assertEqual(actual_xml, expected_xml)
Пример #12
0
from boto.route53.exception import DNSServerError

PUBLIC_ZONE_ID = "{{publicZone}}"
PRIVATE_ZONE_ID = "{{privateZone}}"

LOG = open('/home/ubuntu/cloudera/terminate.log', 'a', 0)

# Delete DNS Records
print("Deleting DNS Records", file=LOG)
conn = boto.connect_route53()
try:
    hosts = []
    changes = ResourceRecordSets(conn, PRIVATE_ZONE_ID)
    records = [r for r in conn.get_all_rrsets(PRIVATE_ZONE_ID) if r.type == "CNAME"]
    for record in records:
        changes.add_change_record("DELETE", record)
        hosts.append(record.name)
    if records:
        changes.commit()

        changes = ResourceRecordSets(conn, PUBLIC_ZONE_ID)
        records  = [r for r in conn.get_all_rrsets(PUBLIC_ZONE_ID) if r.type == "CNAME" and r.name in hosts]
        for record in records:
            changes.add_change_record("DELETE", record)
        if records:
            changes.commit()

except DNSServerError:
    print("Error Deleting DNS Records", file=LOG)

# Terminate Cluster
Пример #13
0
        module.exit_json(changed=False, set=record, nameservers=ns)

    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone.id)

    if command_in == 'create' or command_in == 'delete':
        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")
            command = 'UPSERT'
        else:
            command = command_in.upper()
        changes.add_change_record(command, wanted_rset)

    try:
        result = commit(changes, retry_interval_in)
    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()
Пример #14
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
            command                      = dict(choices=['get', 'create', 'delete'], required=True),
            zone                         = dict(required=True),
            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', 'SOA'], required=True),
            alias                        = dict(required=False, type='bool'),
            alias_hosted_zone_id         = dict(required=False),
            alias_evaluate_target_health = dict(required=False, type='bool', default=False),
            value                        = dict(required=False),
            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, default=None),
            weight                       = dict(required=False, type='int'),
            region                       = dict(required=False),
            health_check                 = dict(required=False),
            failover                     = dict(required=False,choices=['PRIMARY','SECONDARY']),
            vpc_id                       = dict(required=False),
            wait                         = dict(required=False, type='bool', default=False),
            wait_timeout                 = dict(required=False, type='int', default=300),
        )
    )
    module = AnsibleModule(argument_spec=argument_spec)

    if not HAS_BOTO:
        module.fail_json(msg='boto required for this module')

    if distutils.version.StrictVersion(boto.__version__) < distutils.version.StrictVersion(MINIMUM_BOTO_VERSION):
        module.fail_json(msg='Found boto in version %s, but >= %s is required' % (boto.__version__, MINIMUM_BOTO_VERSION))

    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')
    value_in                        = module.params.get('value')
    alias_in                        = module.params.get('alias')
    alias_hosted_zone_id_in         = module.params.get('alias_hosted_zone_id')
    alias_evaluate_target_health_in = module.params.get('alias_evaluate_target_health')
    retry_interval_in               = module.params.get('retry_interval')
    private_zone_in                 = module.params.get('private_zone')
    identifier_in                   = module.params.get('identifier')
    weight_in                       = module.params.get('weight')
    region_in                       = module.params.get('region')
    health_check_in                 = module.params.get('health_check')
    failover_in                     = module.params.get('failover')
    vpc_id_in                       = module.params.get('vpc_id')
    wait_in                         = module.params.get('wait')
    wait_timeout_in                 = module.params.get('wait_timeout')

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    value_list = ()

    if isinstance(value_in, str):
        if value_in:
            value_list = sorted([s.strip() for s in value_in.split(',')])
    elif isinstance(value_in, 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")
        elif alias_in:
            if len(value_list) != 1:
                module.fail_json(msg = "parameter 'value' must contain a single dns name for alias create/delete")
            elif not alias_hosted_zone_id_in:
                module.fail_json(msg = "parameter 'alias_hosted_zone_id' required for alias create/delete")
        elif ( weight_in!=None or region_in!=None or failover_in!=None ) and identifier_in==None:
            module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier")

    if command_in == 'create':
        if ( weight_in!=None or region_in!=None or failover_in!=None ) and identifier_in==None:
          module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier")
        elif  ( weight_in==None and region_in==None and failover_in==None ) and identifier_in!=None:
          module.fail_json(msg= "You have specified identifier which makes sense only if you specify one of: weight, region or failover.")



    if vpc_id_in and not private_zone_in:
        module.fail_json(msg="parameter 'private_zone' must be true when specifying parameter"
            " 'vpc_id'")


    # connect to the route53 endpoint
    try:
        conn = Route53Connection(**aws_connect_kwargs)
    except boto.exception.BotoServerError as e:
        module.fail_json(msg = e.error_message)

    # Find the named zone ID
    zone = get_zone_by_name(conn, module, zone_in, private_zone_in, hosted_zone_id_in, vpc_id_in)

    # Verify that the requested zone is already defined in Route53
    if zone is None:
        errmsg = "Zone %s does not exist in Route53" % zone_in
        module.fail_json(msg = errmsg)

    record = {}

    found_record = False
    wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in,
        identifier=identifier_in, weight=weight_in, region=region_in,
        health_check=health_check_in, failover=failover_in)
    for v in value_list:
        if alias_in:
            wanted_rset.set_alias(alias_hosted_zone_id_in, v, alias_evaluate_target_health_in)
        else:
            wanted_rset.add_value(v)

    sets = conn.get_all_rrsets(zone.id, name=record_in, type=type_in, identifier=identifier_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', '@')
        #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 identifier_in is not None:
            identifier_in = str(identifier_in)

        if rset.type == type_in and decoded_name.lower() == record_in.lower() and rset.identifier == identifier_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 hosted_zone_id_in:
                record['hosted_zone_id'] = hosted_zone_id_in
            record['identifier'] = rset.identifier
            record['weight'] = rset.weight
            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
              record['values'] = [rset.alias_dns_name]
              record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id
              record['alias_evaluate_target_health'] = rset.alias_evaluate_target_health
            else:
              record['alias'] = False
              record['value'] = ','.join(sorted(rset.resource_records))
              record['values'] = sorted(rset.resource_records)
            if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml():
                module.exit_json(changed=False)
            break

    if command_in == 'get':
        if type_in == 'NS':
            ns = record['values']
        else:
            # Retrieve name servers associated to the zone.
            ns = conn.get_zone(zone_in).get_nameservers()

        module.exit_json(changed=False, set=record, nameservers=ns)

    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone.id)

    if command_in == 'create' or command_in == 'delete':
        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")
            command = 'UPSERT'
        else:
            command = command_in.upper()
        changes.add_change_record(command, wanted_rset)

    try:
        result = invoke_with_throttling_retries(commit, changes, retry_interval_in, wait_in, wait_timeout_in)
    except boto.route53.exception.DNSServerError as e:
        txt = e.body.split("<Message>")[1]
        txt = txt.split("</Message>")[0]
        if "but it already exists" in txt:
                module.exit_json(changed=False)
        else:
                module.fail_json(msg = txt)
    except TimeoutError:
        module.fail_json(msg='Timeout waiting for changes to replicate')

    module.exit_json(changed=True)
Пример #15
0
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
        state=dict(aliases=['command'], choices=['present', 'absent', 'get', 'create', 'delete'], required=True),
        zone=dict(required=True),
        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', 'SOA'], required=True),
        alias=dict(required=False, type='bool'),
        alias_hosted_zone_id=dict(required=False),
        alias_evaluate_target_health=dict(required=False, type='bool', default=False),
        value=dict(required=False, type='list', default=[]),
        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, default=None),
        weight=dict(required=False, type='int'),
        region=dict(required=False),
        health_check=dict(required=False),
        failover=dict(required=False, choices=['PRIMARY', 'SECONDARY']),
        vpc_id=dict(required=False),
        wait=dict(required=False, type='bool', default=False),
        wait_timeout=dict(required=False, type='int', default=300),
    ))

    # state=present, absent, create, delete THEN value is required
    required_if = [('state', 'present', ['value']), ('state', 'create', ['value'])]
    required_if.extend([('state', 'absent', ['value']), ('state', 'delete', ['value'])])

    # If alias is True then you must specify alias_hosted_zone as well
    required_together = [['alias', 'alias_hosted_zone_id']]

    # failover, region, and weight are mutually exclusive
    mutually_exclusive = [('failover', 'region', 'weight')]

    module = AnsibleModule(argument_spec=argument_spec, required_together=required_together, required_if=required_if,
                           mutually_exclusive=mutually_exclusive)

    if not HAS_BOTO:
        module.fail_json(msg='boto required for this module')

    if distutils.version.StrictVersion(boto.__version__) < distutils.version.StrictVersion(MINIMUM_BOTO_VERSION):
        module.fail_json(msg='Found boto in version %s, but >= %s is required' % (boto.__version__, MINIMUM_BOTO_VERSION))

    if module.params['state'] in ('present', 'create'):
        command_in = 'create'
    elif module.params['state'] in ('absent', 'delete'):
        command_in = 'delete'
    elif module.params['state'] == 'get':
        command_in = 'get'

    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')
    value_in = module.params.get('value')
    alias_in = module.params.get('alias')
    alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id')
    alias_evaluate_target_health_in = module.params.get('alias_evaluate_target_health')
    retry_interval_in = module.params.get('retry_interval')

    if module.params['vpc_id'] is not None:
        private_zone_in = True
    else:
        private_zone_in = module.params.get('private_zone')

    identifier_in = module.params.get('identifier')
    weight_in = module.params.get('weight')
    region_in = module.params.get('region')
    health_check_in = module.params.get('health_check')
    failover_in = module.params.get('failover')
    vpc_id_in = module.params.get('vpc_id')
    wait_in = module.params.get('wait')
    wait_timeout_in = module.params.get('wait_timeout')

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    if zone_in[-1:] != '.':
        zone_in += "."

    if record_in[-1:] != '.':
        record_in += "."

    if command_in == 'create' or command_in == 'delete':
        if alias_in and len(value_in) != 1:
            module.fail_json(msg="parameter 'value' must contain a single dns name for alias records")
        if (weight_in is not None or region_in is not None or failover_in is not None) and identifier_in is None:
            module.fail_json(msg="If you specify failover, region or weight you must also specify identifier")
        if (weight_in is None and region_in is None and failover_in is None) and identifier_in is not None:
            module.fail_json(msg="You have specified identifier which makes sense only if you specify one of: weight, region or failover.")

    # connect to the route53 endpoint
    try:
        conn = Route53Connection(**aws_connect_kwargs)
    except boto.exception.BotoServerError as e:
        module.fail_json(msg=e.error_message)

    # Find the named zone ID
    zone = get_zone_by_name(conn, module, zone_in, private_zone_in, hosted_zone_id_in, vpc_id_in)

    # Verify that the requested zone is already defined in Route53
    if zone is None:
        errmsg = "Zone %s does not exist in Route53" % zone_in
        module.fail_json(msg=errmsg)

    record = {}

    found_record = False
    wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in,
                         identifier=identifier_in, weight=weight_in,
                         region=region_in, health_check=health_check_in,
                         failover=failover_in)
    for v in value_in:
        if alias_in:
            wanted_rset.set_alias(alias_hosted_zone_id_in, v, alias_evaluate_target_health_in)
        else:
            wanted_rset.add_value(v)

    sets = invoke_with_throttling_retries(conn.get_all_rrsets, zone.id, name=record_in,
                                          type=type_in, identifier=identifier_in)
    sets_iter = iter(sets)
    while True:
        try:
            rset = invoke_with_throttling_retries(next, sets_iter)
        except StopIteration:
            break
        # 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', '@')
        # 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 identifier_in is not None:
            identifier_in = str(identifier_in)

        if rset.type == type_in and decoded_name.lower() == record_in.lower() and rset.identifier == identifier_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 hosted_zone_id_in:
                record['hosted_zone_id'] = hosted_zone_id_in
            record['identifier'] = rset.identifier
            record['weight'] = rset.weight
            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
                record['values'] = [rset.alias_dns_name]
                record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id
                record['alias_evaluate_target_health'] = rset.alias_evaluate_target_health
            else:
                record['alias'] = False
                record['value'] = ','.join(sorted(rset.resource_records))
                record['values'] = sorted(rset.resource_records)
            if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml():
                module.exit_json(changed=False)

        # We need to look only at the first rrset returned by the above call,
        # so break here. The returned elements begin with the one matching our
        # requested name, type, and identifier, if such an element exists,
        # followed by all others that come after it in alphabetical order.
        # Therefore, if the first set does not match, no subsequent set will
        # match either.
        break

    if command_in == 'get':
        if type_in == 'NS':
            ns = record.get('values', [])
        else:
            # Retrieve name servers associated to the zone.
            z = invoke_with_throttling_retries(conn.get_zone, zone_in)
            ns = invoke_with_throttling_retries(z.get_nameservers)

        module.exit_json(changed=False, set=record, nameservers=ns)

    if command_in == 'delete' and not found_record:
        module.exit_json(changed=False)

    changes = ResourceRecordSets(conn, zone.id)

    if command_in == 'create' or command_in == 'delete':
        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")
            command = 'UPSERT'
        else:
            command = command_in.upper()
        changes.add_change_record(command, wanted_rset)

    try:
        result = invoke_with_throttling_retries(commit, changes, retry_interval_in, wait_in, wait_timeout_in)
    except boto.route53.exception.DNSServerError as e:
        txt = e.body.split("<Message>")[1]
        txt = txt.split("</Message>")[0]
        if "but it already exists" in txt:
            module.exit_json(changed=False)
        else:
            module.fail_json(msg=txt)
    except TimeoutError:
        module.fail_json(msg='Timeout waiting for changes to replicate')

    module.exit_json(changed=True)