Ejemplo n.º 1
0
    def create_record(self, hosted_zone_name, record_name, record_type, value):
        """
        Create a DNS record. Update an existing record if one already exists.
        Args:
            hosted_zone_name (str): the domain of the Hosted Zone
            record_name (str): the name of the record
            record_type (str): the type of record (A, AAAA, CNAME, etc)
            value (str): a single value to insert into the record
        """
        zone = throttled_call(self.route53.get_zone, hosted_zone_name)

        record = Record(record_name, record_type, ttl=5)
        record.add_value(value)

        logger.info("Setting Record %s of type %s to %s", record_name, record_type, value)

        changes = throttled_call(self.route53.get_all_rrsets, zone.id)
        changes.add_change_record('UPSERT', record)

        throttled_call(changes.commit)
Ejemplo n.º 2
0
def _create_mock_zone_and_records(route53):
    route53.create_hosted_zone(TEST_DOMAIN)
    route53.create_hosted_zone(TEST_DOMAIN2)
    example_com = route53.get_zone(TEST_DOMAIN)
    test_com = route53.get_zone(TEST_DOMAIN2)

    record_sets = route53.get_all_rrsets(example_com.id)

    record = Record(TEST_RECORD_NAME, TEST_RECORD_TYPE)
    record.add_value(TEST_RECORD_VALUE)

    record_sets.add_change_record('CREATE', record)
    record_sets.commit()

    record_sets = route53.get_all_rrsets(test_com.id)

    record = Record(TEST_RECORD_NAME2, TEST_RECORD_TYPE)
    record.add_value(TEST_RECORD_VALUE)

    record_sets.add_change_record('CREATE', record)
    record_sets.commit()
Ejemplo n.º 3
0
    def create_record(self, hosted_zone_name, record_name, record_type, value):
        """
        Create a DNS record. Update an existing record if one already exists.
        Args:
            hosted_zone_name (str): the domain of the Hosted Zone
            record_name (str): the name of the record
            record_type (str): the type of record (A, AAAA, CNAME, etc)
            value (str): a single value to insert into the record
        """
        zone = self.route53.get_zone(hosted_zone_name)

        record = Record(record_name, record_type, ttl=60)
        record.add_value(value)

        logging.info("Setting Record %s of type %s to %s", record_name,
                     record_type, value)

        changes = self.route53.get_all_rrsets(zone.id)
        changes.add_change_record('UPSERT', record)

        changes.commit()
Ejemplo n.º 4
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)
Ejemplo n.º 5
0
    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)
        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', '@')

        if rset.type == type_in and decoded_name.lower() == record_in.lower(
        ) and rset.identifier == identifier_in:
            found_record = True
            record['zone'] = zone_in
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
    # 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)
        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 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
            record['record'] = decoded_name
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)