Skip to content

Instantly share code, notes, and snippets.

@coop
Created May 19, 2014 10:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coop/b867e47d4a4b266f6b1a to your computer and use it in GitHub Desktop.
Save coop/b867e47d4a4b266f6b1a to your computer and use it in GitHub Desktop.
#!/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