Skip to content

Instantly share code, notes, and snippets.

@Excedrin
Last active December 21, 2015 01:59
Show Gist options
  • Save Excedrin/6232221 to your computer and use it in GitHub Desktop.
Save Excedrin/6232221 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: bind
version_added: "1.3"
short_description: add, update or delete entries in BIND zone file
description:
- Creates and deletes DNS records in BIND zone file
options:
command:
description:
- Specifies the action to take.
required: true
default: null
aliases: []
choices: [ 'get', 'create', 'delete' ]
path:
description:
- Path to zone file
required: true
default: null
aliases: []
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, TTL won't be reset to the default when a record is being changed and TTL isn't specified
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.
required: false
default: null
aliases: []
requirements: [ "dnspython" ]
author: Scott Cruzen
'''
EXAMPLES = '''
# Add new.foo.com as an A record with 3 IPs
- bind: >
command=create
path=/var/named/foo.com.zone
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
- bind: >
command=get
path=/var/named/foo.com.zone
zone=foo.com
record=new.foo.com
type=A
register: rec
# Delete new.foo.com A record using the results from the get command
- bind: >
command=delete
path=/var/named/foo.com.zone
zone=foo.com
record={{ r.set.record }}
type={{ r.set.type }}
value={{ r.set.value }}
# Add an AAAA record. Note that because there are colons in the value
# that the entire parameter list must be quoted:
- bind: >
"command=create
path=/var/named/foo.com.zone
zone=foo.com
record=localhost.foo.com
type=AAAA
ttl=7200
value=::1"
'''
import sys
import time
try:
import dns.zone
except ImportError:
print "failed=True msg='dnspython required for this module'"
sys.exit(1)
def main():
argument_spec = dict(
command = dict(choices=['get', 'create', 'delete'], required=True),
zone = dict(required=True),
path = dict(required=True),
record = dict(required=True),
ttl = dict(required=False, default=-1),
type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS'], required=True),
value = dict(required=False),
)
argument_spec['class'] = dict(choices=['ANY','IN'], required=False, default='IN')
module = AnsibleModule(argument_spec)
command_in = module.params.get('command')
zone_in = module.params.get('zone')
path_in = module.params.get('path')
ttl_in = module.params.get('ttl')
record_in = module.params.get('record')
type_in = module.params.get('type')
class_in = module.params.get('class')
value_in = module.params.get('value')
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 += "."
typeval = dns.rdatatype._by_text.get(type_in, None)
if typeval is None:
raise NameError("Invalid type: %s" %type_in)
classval = dns.rdataclass._by_text.get(class_in, None)
if classval is None:
raise NameError("Invalid class: %s" %class_in)
default_ttl = False
ttl_int = long(ttl_in)
if ttl_int == -1:
default_ttl = True
ttl_int = 3600
if command_in == 'create':
if not value_in:
module.fail_json(msg = "parameter 'value' required for create/delete")
try:
zone = dns.zone.from_file(path_in, zone_in, relativize=False)
except IOError, e:
module.fail_json(msg = e.strerror)
record = {}
name = dns.name.from_text(record_in)
found_record = False
if zone.nodes.has_key(name):
node = zone.nodes[name]
rdata = node.get_rdataset(classval, typeval)
if rdata:
records = map(lambda x: x.to_text(), sorted(rdata.items))
found_record = True
record['zone'] = zone_in
record['type'] = type_in
record['record'] = record_in
record['ttl'] = rdata.ttl
record['value'] = ','.join(records)
record['values'] = records
if command_in == 'create' and value_list == records and ttl_int == rdata.ttl:
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)
if command_in == 'create':
node = zone.get_node(name, create=True)
ttl = ttl_int
if default_ttl:
currentrdataset = node.get_rdataset(classval, typeval)
if currentrdataset:
ttl = currentrdataset.ttl
newrdset = dns.rdataset.from_text_list(class_in, type_in, ttl, value_list)
node.replace_rdataset(newrdset)
if command_in == 'delete':
zone.delete_node(name)
# new serial, from easyzone
soa = zone[zone.origin].get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA).items[0]
new_serial = int(time.strftime('%Y%m%d00', time.localtime(time.time())))
if new_serial <= soa.serial:
new_serial = soa.serial + 1
soa.serial = new_serial
try:
tmp = path_in + ".tmp"
# this generally protects against coding errors in the above
# that only show up when one (new or modified) record is broken
zone.to_file(tmp, relativize=False)
os.rename(tmp, path_in)
except IOError, e:
module.fail_json(msg = e.strerror)
except TypeError, e:
# these are not particularly useful
module.fail_json(msg = e.message)
module.exit_json(changed=True)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
@srgvg
Copy link

srgvg commented Aug 14, 2013

You forgot to document the 'path' parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment