route53: add support for routing policies

It is now possible to pass various routing policies if an identity is
provided.

This commit also introduces multiple optimisations:
* Only fetch records for the given domain
* Use UPSERT instead of DELETE+CREATE to update existing records
This commit is contained in:
zimbatm 2015-05-05 16:07:18 +01:00 committed by Matt Clay
parent 86e679fe3c
commit 99e2557b42

View file

@ -93,6 +93,45 @@ options:
required: false
default: false
version_added: "1.9"
identifier:
description:
- Weighted and latency-based resource record sets only. An identifier
that differentiates among multiple resource record sets that have the
same combination of DNS name and type.
required: false
default: null
version_added: "2.0"
weight:
description:
- Weighted resource record sets only. Among resource record sets that
have the same combination of DNS name and type, a value that
determines what portion of traffic for the current resource record set
is routed to the associated location.
required: false
default: null
version_added: "2.0"
region:
description:
- Latency-based resource record sets only Among resource record sets
that have the same combination of DNS name and type, a value that
determines which region this should be associated with for the
latency-based routing
required: false
default: null
version_added: "2.0"
health_check:
description:
- Health check to associate with this record
required: false
default: null
version_added: "2.0"
failover:
description:
- Failover resource record sets only. Whether this is the primary or
secondary resource record set.
required: false
default: null
version_added: "2.0"
author: "Bruce Pennypacker (@bpennypacker)"
extends_documentation_fragment: aws
'''
@ -156,6 +195,18 @@ EXAMPLES = '''
alias=True
alias_hosted_zone_id="{{ elb_zone_id }}"
# Use a routing policy to distribute traffic:
- route53:
command: "create"
zone: "foo.com"
record: "www.foo.com"
type: "CNAME"
value: "host1.foo.com"
ttl: 30
# Routing policy
identifier: "host1@www"
weight: 100
health_check: "d994b780-3150-49fd-9205-356abdd42e75"
'''
@ -165,11 +216,21 @@ try:
import boto
from boto import route53
from boto.route53 import Route53Connection
from boto.route53.record import ResourceRecordSets
from boto.route53.record import Record, ResourceRecordSets
HAS_BOTO = True
except ImportError:
HAS_BOTO = False
def get_zone_by_name(conn, module, zone_name, want_private):
"""Finds a zone by name"""
for zone in conn.get_zones():
# only save this zone id if the private status of the zone matches
# the private_zone_in boolean specified in the params
private_zone = module.boolean(zone.config.get('PrivateZone', False))
if private_zone == want_private and zone.name == zone_name:
return zone
return None
def commit(changes, retry_interval):
"""Commit changes, but retry PriorRequestNotComplete errors."""
@ -199,6 +260,11 @@ def main():
overwrite = dict(required=False, type='bool'),
retry_interval = dict(required=False, default=500)
private_zone = dict(required=False, type='bool', default=False),
identifier = dict(required=False),
weight = dict(required=False, type='int'),
region = dict(required=False),
health_check = dict(required=False),
failover = dict(required=False),
)
)
module = AnsibleModule(argument_spec=argument_spec)
@ -215,6 +281,11 @@ def main():
alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id')
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')
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
@ -247,32 +318,34 @@ def main():
except boto.exception.BotoServerError, e:
module.fail_json(msg = e.error_message)
# Get all the existing hosted zones and save their ID's
zones = {}
results = conn.get_all_hosted_zones()
for r53zone in results['ListHostedZonesResponse']['HostedZones']:
# only save this zone id if the private status of the zone matches
# the private_zone_in boolean specified in the params
if module.boolean(r53zone['Config'].get('PrivateZone', False)) == private_zone_in:
zone_id = r53zone['Id'].replace('/hostedzone/', '')
zones[r53zone['Name']] = zone_id
# Find the named zone ID
zone = get_zone_by_name(conn, module, zone_in, private_zone_in)
# Verify that the requested zone is already defined in Route53
if not zone_in in zones:
if zone is None:
errmsg = "Zone %s does not exist in Route53" % zone_in
module.fail_json(msg = errmsg)
record = {}
found_record = False
sets = conn.get_all_rrsets(zones[zone_in])
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():
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
@ -280,6 +353,11 @@ def main():
record['ttl'] = rset.ttl
record['value'] = ','.join(sorted(rset.resource_records))
record['values'] = sorted(rset.resource_records)
record['identifier'] = rset.identifier
record['weight'] = rset.weight
record['region'] = rset.region
record['failover'] = rset.failover
record['health_check'] = rset.health_check
if rset.alias_dns_name:
record['alias'] = True
record['value'] = rset.alias_dns_name
@ -289,8 +367,9 @@ def main():
record['alias'] = False
record['value'] = ','.join(sorted(rset.resource_records))
record['values'] = sorted(rset.resource_records)
if value_list == sorted(rset.resource_records) and int(record['ttl']) == ttl_in and command_in == 'create':
if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml():
module.exit_json(changed=False)
break
if command_in == 'get':
module.exit_json(changed=False, set=record)
@ -298,26 +377,16 @@ def main():
if command_in == 'delete' and not found_record:
module.exit_json(changed=False)
changes = ResourceRecordSets(conn, zones[zone_in])
if command_in == 'create' and found_record:
if not module.params['overwrite']:
module.fail_json(msg = "Record already exists with different value. Set 'overwrite' to replace it")
else:
change = changes.add_change("DELETE", record_in, type_in, record['ttl'])
for v in record['values']:
if record['alias']:
change.set_alias(record['alias_hosted_zone_id'], v)
else:
change.add_value(v)
changes = ResourceRecordSets(conn, zone.id)
if command_in == 'create' or command_in == 'delete':
change = changes.add_change(command_in.upper(), record_in, type_in, ttl_in)
for v in value_list:
if module.params['alias']:
change.set_alias(alias_hosted_zone_id_in, v)
else:
change.add_value(v)
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)