Created
May 19, 2014 10:35
-
-
Save coop/b867e47d4a4b266f6b1a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# This file is part of Ansible | |
# | |
# Ansible is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# Ansible is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. | |
DOCUMENTATION = ''' | |
--- | |
module: route53 | |
version_added: "1.3" | |
short_description: add or delete entries in Amazons Route53 DNS service | |
description: | |
- Creates and deletes DNS records in Amazons Route53 service | |
options: | |
command: | |
description: | |
- Specifies the action to take. | |
required: true | |
default: null | |
aliases: [] | |
choices: [ 'get', 'create', 'delete' ] | |
zone: | |
description: | |
- The DNS zone to modify | |
required: true | |
default: null | |
aliases: [] | |
record: | |
description: | |
- The full DNS record to create or delete | |
required: true | |
default: null | |
aliases: [] | |
ttl: | |
description: | |
- The TTL to give the new record | |
required: false | |
default: 3600 (one hour) | |
aliases: [] | |
type: | |
description: | |
- The type of DNS record to create | |
required: true | |
default: null | |
aliases: [] | |
choices: [ 'A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS' ] | |
value: | |
description: | |
- The new value when creating a DNS record. Multiple comma-spaced values are allowed. When deleting a record all values for the record must be specified or Route53 will not delete it. | |
required: false | |
default: null | |
aliases: [] | |
aws_secret_key: | |
description: | |
- AWS secret key. | |
required: false | |
default: null | |
aliases: ['ec2_secret_key', 'secret_key'] | |
aws_access_key: | |
description: | |
- AWS access key. | |
required: false | |
default: null | |
aliases: ['ec2_access_key', 'access_key'] | |
overwrite: | |
description: | |
- Whether an existing record should be overwritten on create if values do not match | |
required: false | |
default: null | |
aliases: [] | |
alias: | |
description: | |
- The value of the hosted zone ID, CanonicalHostedZoneNameId, or a LoadBalancer. | |
required: false | |
default: null | |
version_added: "1.6" | |
alias_zone: | |
description: | |
- The name of the zone the alias should exist in. | |
required: false | |
default: null | |
version_added: "1.6" | |
alias_eval_health: | |
description: | |
- Wehter or not to set the Evaluate Health option for this record | |
required: false | |
default: false | |
version_added: "1.6" | |
identifier: | |
description: | |
- Used for weight or latency based routing policy. | |
required: false | |
default: null | |
version_added: "1.6" | |
weight: | |
description: | |
- Sets the weight for weight based routing. | |
default: null | |
version_added: "1.6" | |
region: | |
description: | |
- Sets the region for region based latecy routing. | |
required: false | |
default: null | |
version_added: "1.6" | |
health_check: | |
description: | |
- Sets the value of the health check for weighted routing. | |
requirements: [ "boto" ] | |
author: Bruce Pennypacker | |
''' | |
EXAMPLES = ''' | |
# Add new.foo.com as an A record with 3 IPs | |
- route53: > | |
command=create | |
zone=foo.com | |
record=new.foo.com | |
type=A | |
ttl=7200 | |
value=1.1.1.1,2.2.2.2,3.3.3.3 | |
# Retrieve the details for new.foo.com | |
- route53: > | |
command=get | |
zone=foo.com | |
record=new.foo.com | |
type=A | |
register: rec | |
# Delete new.foo.com A record using the results from the get command | |
- route53: > | |
command=delete | |
zone=foo.com | |
record={{ rec.set.record }} | |
type={{ rec.set.type }} | |
value={{ rec.set.value }} | |
# Add an AAAA record. Note that because there are colons in the value | |
# that the entire parameter list must be quoted: | |
- route53: > | |
command=create | |
zone=foo.com | |
record=localhost.foo.com | |
type=AAAA | |
ttl=7200 | |
value="::1" | |
# Add a TXT record. Note that TXT and SPF records must be surrounded | |
# by quotes when sent to Route 53: | |
- route53: > | |
command=create | |
zone=foo.com | |
record=localhost.foo.com | |
type=TXT | |
ttl=7200 | |
value="\"bar\"" | |
# Adds an alias from boo.foo.com to alias.foo.com | |
- route53: > | |
command=create | |
record=yes | |
ttl=3600 | |
type=A | |
zone=foo.com | |
alias=alias.foo.com | |
alias_zone=foo.com | |
record=alias.foo.com | |
value=boo.foo.com | |
# Add alias record with health from boo.foo.com to alias.foo.com | |
- route53: > | |
command=create | |
record=yes | |
ttl=3600 | |
type=A | |
zone=foo.com | |
alias_eval_health=yes | |
alias=alias.foo.com | |
alias_zone=foo.com | |
record=alias.foo.com | |
value=boo.foo.com | |
# Add a weighted record | |
- route53: > | |
command=create | |
record=yes | |
ttl=3600 | |
type=A | |
zone=foo.com | |
weight=100 | |
identifier=us-west-1c | |
record=alias.foo.com | |
value=boo.foo.com | |
# Add a latency record | |
- route53: > | |
command=create | |
record=yes | |
ttl=3600 | |
type=A | |
zone=foo.com | |
region=us-west-1 | |
identifier=us-west-1c | |
record=alias.foo.com | |
value=boo.foo.com | |
''' | |
import sys | |
import time | |
try: | |
import boto | |
from boto import route53 | |
from boto.route53.record import ResourceRecordSets | |
except ImportError: | |
print "failed=True msg='boto required for this module'" | |
sys.exit(1) | |
def commit(changes): | |
"""Commit changes, but retry PriorRequestNotComplete errors.""" | |
retry = 10 | |
while True: | |
try: | |
retry -= 1 | |
return changes.commit() | |
except boto.route53.exception.DNSServerError, e: | |
code = e.body.split("<Code>")[1] | |
code = code.split("</Code>")[0] | |
if code != 'PriorRequestNotComplete' or retry < 0: | |
raise e | |
time.sleep(500) | |
def main(): | |
argument_spec = ec2_argument_spec() | |
argument_spec.update(dict( | |
command = dict(choices=['get', 'create', 'delete', 'upsert'], required=True), | |
zone = dict(required=True), | |
record = dict(required=True), | |
ttl = dict(required=False, default=3600), | |
type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True), | |
value = dict(required=False), | |
overwrite = dict(required=False, type='bool'), | |
alias_zone = dict(register=False), | |
alias = dict(register=False), | |
alias_eval_health = dict(register=False, type='bool', default=False), | |
identifier = dict(register=False, default=None), | |
weight = dict(register=False, default=None), | |
region = dict(register=False, default=None), | |
health_check = dict(register=False, default=None), | |
) | |
) | |
module = AnsibleModule(argument_spec=argument_spec) | |
command_in = module.params.get('command') | |
zone_in = module.params.get('zone') | |
ttl_in = module.params.get('ttl') | |
record_in = module.params.get('record') | |
type_in = module.params.get('type') | |
value_in = module.params.get('value') | |
alias_zone_in = module.params.get('alias_zone') | |
alias_in = module.params.get('alias') | |
alias_eval_health_in = module.params.get('alias_eval_health') | |
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') | |
ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) | |
value_list = () | |
if type(value_in) is str: | |
if value_in: | |
value_list = sorted(value_in.split(',')) | |
elif type(value_in) is list: | |
value_list = sorted(value_in) | |
if zone_in[-1:] != '.': | |
zone_in += "." | |
if record_in[-1:] != '.': | |
record_in += "." | |
if not alias_zone_in: | |
alias_zone_in = zone_in | |
elif alias_zone_in[-1:] !=' .': | |
# alias_zone_in += "." | |
alias_zone_in += "" | |
# if command_in == 'create' or command_in == 'delete': | |
# if not value_in: | |
# module.fail_json(msg = "parameter 'value' required for create/delete") | |
# connect to the route53 endpoint | |
try: | |
conn = boto.route53.connection.Route53Connection(aws_access_key, aws_secret_key) | |
except boto.exception.BotoServerError, e: | |
module.fail_json(msg = e.error_message) | |
# Get all the existing hosted zones and save their ID's | |
zones = {} | |
results = conn.get_all_hosted_zones() | |
for r53zone in results['ListHostedZonesResponse']['HostedZones']: | |
zone_id = r53zone['Id'].replace('/hostedzone/', '') | |
zones[r53zone['Name']] = zone_id | |
# Verify that the requested zone is already defined in Route53 | |
if not zone_in in zones: | |
errmsg = "Zone %s does not exist in Route53" % zone_in | |
module.fail_json(msg = errmsg) | |
# Verify that the requested alisa zone is already defined in Route53 | |
# if alias_zone_in and not alias_zone_in in zones: | |
# errmsg = "Zone %s does not exist in Route53" % zone_in | |
# module.fail_json(msg = errmsg) | |
record = {} | |
found_record = False | |
sets = conn.get_all_rrsets(zones[zone_in]) | |
for rset in sets: | |
# Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round | |
# tripping of things like * and @. | |
decoded_name = rset.name.replace(r'\052', '*') | |
decoded_name = rset.name.replace(r'\100', '@') | |
if rset.type == type_in and decoded_name == record_in and value_in in rset.resource_records: | |
found_record = True | |
record['zone'] = zone_in | |
record['type'] = rset.type | |
record['record'] = decoded_name | |
record['ttl'] = rset.ttl | |
record['alias_dns_name'] = rset.alias_dns_name | |
record['alias_evaluate_target_health'] = rset.alias_evaluate_target_health | |
record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id | |
record['health_check'] = rset.health_check | |
record['identifier'] = rset.identifier | |
record['region'] = rset.region | |
record['weight'] = rset.weight | |
record['value'] = ','.join(sorted(rset.resource_records)) | |
record['values'] = sorted(rset.resource_records) | |
if value_list == sorted(rset.resource_records) and record['ttl'] == ttl_in and command_in == 'create': | |
module.exit_json(changed=False) | |
if command_in == 'get': | |
module.exit_json(changed=False, set=record) | |
if command_in == 'delete' and not found_record: | |
module.exit_json(changed=False) | |
changes = ResourceRecordSets(conn, zones[zone_in]) | |
if command_in == 'create' and found_record: | |
if not module.params['overwrite']: | |
module.fail_json(msg = "Record already exists with different value. Set 'overwrite' to replace it") | |
else: | |
change = changes.add_change("DELETE", record_in, type_in, record['ttl'], | |
alias_hosted_zone_id=zones[alias_zone_in], alias_dns_name=alias_in, identifier=identifier_in, | |
weight=weight_in, region=region_in, | |
alias_evaluate_target_health=alias_eval_health_in, health_check=health_check_in) | |
for v in record['values']: | |
change.add_value(v) | |
if command_in in ['create', 'upsert', 'delete']: | |
change = changes.add_change(command_in.upper(), record_in, type_in, ttl_in, | |
alias_hosted_zone_id=alias_zone_in, alias_dns_name=alias_in, identifier=identifier_in, | |
weight=weight_in, region=region_in, | |
alias_evaluate_target_health=alias_eval_health_in, health_check=health_check_in) | |
for v in value_list: | |
change.add_value(v) | |
try: | |
result = commit(changes) | |
except boto.route53.exception.DNSServerError, e: | |
txt = e.body.split("<Message>")[1] | |
txt = txt.split("</Message>")[0] | |
module.fail_json(msg = txt) | |
module.exit_json(changed=True) | |
# import module snippets | |
from ansible.module_utils.basic import * | |
from ansible.module_utils.ec2 import * | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment