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:
parent
86e679fe3c
commit
99e2557b42
1 changed files with 101 additions and 32 deletions
|
@ -93,6 +93,45 @@ options:
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
version_added: "1.9"
|
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)"
|
author: "Bruce Pennypacker (@bpennypacker)"
|
||||||
extends_documentation_fragment: aws
|
extends_documentation_fragment: aws
|
||||||
'''
|
'''
|
||||||
|
@ -156,6 +195,18 @@ EXAMPLES = '''
|
||||||
alias=True
|
alias=True
|
||||||
alias_hosted_zone_id="{{ elb_zone_id }}"
|
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
|
import boto
|
||||||
from boto import route53
|
from boto import route53
|
||||||
from boto.route53 import Route53Connection
|
from boto.route53 import Route53Connection
|
||||||
from boto.route53.record import ResourceRecordSets
|
from boto.route53.record import Record, ResourceRecordSets
|
||||||
HAS_BOTO = True
|
HAS_BOTO = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_BOTO = False
|
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):
|
def commit(changes, retry_interval):
|
||||||
"""Commit changes, but retry PriorRequestNotComplete errors."""
|
"""Commit changes, but retry PriorRequestNotComplete errors."""
|
||||||
|
@ -199,6 +260,11 @@ def main():
|
||||||
overwrite = dict(required=False, type='bool'),
|
overwrite = dict(required=False, type='bool'),
|
||||||
retry_interval = dict(required=False, default=500)
|
retry_interval = dict(required=False, default=500)
|
||||||
private_zone = dict(required=False, type='bool', default=False),
|
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)
|
module = AnsibleModule(argument_spec=argument_spec)
|
||||||
|
@ -215,6 +281,11 @@ def main():
|
||||||
alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id')
|
alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id')
|
||||||
retry_interval_in = module.params.get('retry_interval')
|
retry_interval_in = module.params.get('retry_interval')
|
||||||
private_zone_in = module.params.get('private_zone')
|
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)
|
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
|
||||||
|
|
||||||
|
@ -247,32 +318,34 @@ def main():
|
||||||
except boto.exception.BotoServerError, e:
|
except boto.exception.BotoServerError, e:
|
||||||
module.fail_json(msg = e.error_message)
|
module.fail_json(msg = e.error_message)
|
||||||
|
|
||||||
# Get all the existing hosted zones and save their ID's
|
# Find the named zone ID
|
||||||
zones = {}
|
zone = get_zone_by_name(conn, module, zone_in, private_zone_in)
|
||||||
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
|
|
||||||
|
|
||||||
# Verify that the requested zone is already defined in Route53
|
# 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
|
errmsg = "Zone %s does not exist in Route53" % zone_in
|
||||||
module.fail_json(msg = errmsg)
|
module.fail_json(msg = errmsg)
|
||||||
|
|
||||||
record = {}
|
record = {}
|
||||||
|
|
||||||
found_record = False
|
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:
|
for rset in sets:
|
||||||
# Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round
|
# Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round
|
||||||
# tripping of things like * and @.
|
# tripping of things like * and @.
|
||||||
decoded_name = rset.name.replace(r'\052', '*')
|
decoded_name = rset.name.replace(r'\052', '*')
|
||||||
decoded_name = decoded_name.replace(r'\100', '@')
|
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
|
found_record = True
|
||||||
record['zone'] = zone_in
|
record['zone'] = zone_in
|
||||||
record['type'] = rset.type
|
record['type'] = rset.type
|
||||||
|
@ -280,6 +353,11 @@ def main():
|
||||||
record['ttl'] = rset.ttl
|
record['ttl'] = rset.ttl
|
||||||
record['value'] = ','.join(sorted(rset.resource_records))
|
record['value'] = ','.join(sorted(rset.resource_records))
|
||||||
record['values'] = 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:
|
if rset.alias_dns_name:
|
||||||
record['alias'] = True
|
record['alias'] = True
|
||||||
record['value'] = rset.alias_dns_name
|
record['value'] = rset.alias_dns_name
|
||||||
|
@ -289,8 +367,9 @@ def main():
|
||||||
record['alias'] = False
|
record['alias'] = False
|
||||||
record['value'] = ','.join(sorted(rset.resource_records))
|
record['value'] = ','.join(sorted(rset.resource_records))
|
||||||
record['values'] = 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)
|
module.exit_json(changed=False)
|
||||||
|
break
|
||||||
|
|
||||||
if command_in == 'get':
|
if command_in == 'get':
|
||||||
module.exit_json(changed=False, set=record)
|
module.exit_json(changed=False, set=record)
|
||||||
|
@ -298,26 +377,16 @@ def main():
|
||||||
if command_in == 'delete' and not found_record:
|
if command_in == 'delete' and not found_record:
|
||||||
module.exit_json(changed=False)
|
module.exit_json(changed=False)
|
||||||
|
|
||||||
changes = ResourceRecordSets(conn, zones[zone_in])
|
changes = ResourceRecordSets(conn, zone.id)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if command_in == 'create' or command_in == 'delete':
|
if command_in == 'create' or command_in == 'delete':
|
||||||
change = changes.add_change(command_in.upper(), record_in, type_in, ttl_in)
|
if command_in == 'create' and found_record:
|
||||||
for v in value_list:
|
if not module.params['overwrite']:
|
||||||
if module.params['alias']:
|
module.fail_json(msg = "Record already exists with different value. Set 'overwrite' to replace it")
|
||||||
change.set_alias(alias_hosted_zone_id_in, v)
|
command = 'UPSERT'
|
||||||
else:
|
else:
|
||||||
change.add_value(v)
|
command = command_in.upper()
|
||||||
|
changes.add_change_record(command, wanted_rset)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = commit(changes, retry_interval_in)
|
result = commit(changes, retry_interval_in)
|
||||||
|
|
Loading…
Reference in a new issue