commit
fa647e43ee
1 changed files with 345 additions and 0 deletions
345
library/net_infrastructure/dnsmadeeasy
Normal file
345
library/net_infrastructure/dnsmadeeasy
Normal file
|
@ -0,0 +1,345 @@
|
|||
#!/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: dnsmadeeasy
|
||||
version_added: "1.3"
|
||||
short_description: Interface with dnsmadeeasy.com (a DNS hosting service).
|
||||
description:
|
||||
- Manage DNS records via the v2 REST API of the DNS Made Easy service. Records only; no manipulation of domains or monitor/account support yet. See: http://www.dnsmadeeasy.com/services/rest-api/
|
||||
options:
|
||||
account_key:
|
||||
description:
|
||||
- Accout API Key.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
account_secret:
|
||||
description:
|
||||
- Accout Secret Key.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
domain:
|
||||
description:
|
||||
- Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNS Made Easy (e.g. "839989") for faster resolution.
|
||||
required: true
|
||||
default: null
|
||||
|
||||
record_name:
|
||||
description:
|
||||
- Record name (to get/create/delete/update).
|
||||
If record_name is not specified; all records for the domain will be returned as "result" regardless of state argument.
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_type:
|
||||
description:
|
||||
- Record type.
|
||||
required: false
|
||||
choices: [ 'A', 'AAAA', 'CNAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT' ]
|
||||
default: null
|
||||
|
||||
record_value:
|
||||
description:
|
||||
- Record value. HTTPRED: <redirection URL>, MX: <priority> <target name>, NS: <name server>, PTR: <target name>, SRV: <priority> <weight> <port> <target name>, TXT: <text value>
|
||||
If record_value is not specified; no changes will be made and the record will be returned as "result" (e.g. can be used to fetch a record's current id, type, and ttl)
|
||||
required: false
|
||||
default: null
|
||||
|
||||
record_ttl:
|
||||
description:
|
||||
- Record "Time to live". Number of seconds a record remains cached in DNS servers.
|
||||
required: false
|
||||
default: 1800
|
||||
|
||||
state:
|
||||
description:
|
||||
- If state is "present", record will be created. If state is "present" and if record exists and values have changed, it will be updated.
|
||||
If state is absent, record will be removed.
|
||||
required: true
|
||||
choices: [ 'present', 'absent' ]
|
||||
default: null
|
||||
|
||||
notes:
|
||||
- The DNS Made Easy service requires that machines interacting with it's API have the proper time + timezone set. Be sure you're within a few seconds of actual GMT by using NTP.
|
||||
- This module returns record(s) as "result" when state == 'present'. It can be be registered and used in your playbooks.
|
||||
|
||||
requirements: [ urllib, urllib2, hashlib, hmac ]
|
||||
author: Brice Burgess
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# fetch my.com domain records
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present
|
||||
register: response
|
||||
|
||||
# create / ensure the presence of a record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test" record_type="A" record_value="127.0.0.1"
|
||||
|
||||
# update the previously created record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test" record_value="192.168.0.1"
|
||||
|
||||
# fetch a specific record
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=present record_name="test"
|
||||
register: response
|
||||
|
||||
# delete a record / ensure it is absent
|
||||
- dnsmadeeasy: account_key=key account_secret=secret domain=my.com state=absent record_name="test"
|
||||
'''
|
||||
|
||||
# ============================================
|
||||
# DNSMadeEasy module specific support methods.
|
||||
#
|
||||
|
||||
IMPORT_ERROR = None
|
||||
try:
|
||||
import urllib
|
||||
import urllib2
|
||||
import json
|
||||
from time import strftime, gmtime
|
||||
import hashlib
|
||||
import hmac
|
||||
except ImportError, e:
|
||||
IMPORT_ERROR = str(e)
|
||||
|
||||
|
||||
class RequestWithMethod(urllib2.Request):
|
||||
|
||||
"""Workaround for using DELETE/PUT/etc with urllib2"""
|
||||
|
||||
def __init__(self, url, method, data=None, headers={}):
|
||||
self._method = method
|
||||
urllib2.Request.__init__(self, url, data, headers)
|
||||
|
||||
def get_method(self):
|
||||
if self._method:
|
||||
return self._method
|
||||
else:
|
||||
return urllib2.Request.get_method(self)
|
||||
|
||||
|
||||
class DME2:
|
||||
|
||||
def __init__(self, apikey, secret, domain, module):
|
||||
self.module = module
|
||||
|
||||
self.api = apikey
|
||||
self.secret = secret
|
||||
self.baseurl = 'http://api.dnsmadeeasy.com/V2.0/'
|
||||
self.domain = str(domain)
|
||||
self.domain_map = None # ["domain_name"] => ID
|
||||
self.record_map = None # ["record_name"] => ID
|
||||
self.records = None # ["record_ID"] => <record>
|
||||
|
||||
# Lookup the domain ID if passed as a domain name vs. ID
|
||||
if not self.domain.isdigit():
|
||||
self.domain = self.getDomainByName(self.domain)['id']
|
||||
|
||||
self.record_url = 'dns/managed/' + str(self.domain) + '/records'
|
||||
|
||||
def _headers(self):
|
||||
currTime = self._get_date()
|
||||
hashstring = self._create_hash(currTime)
|
||||
headers = {'x-dnsme-apiKey': self.api,
|
||||
'x-dnsme-hmac': hashstring,
|
||||
'x-dnsme-requestDate': currTime,
|
||||
'content-type': 'application/json'}
|
||||
return headers
|
||||
|
||||
def _get_date(self):
|
||||
return strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())
|
||||
|
||||
def _create_hash(self, rightnow):
|
||||
return hmac.new(self.secret.encode(), rightnow.encode(), hashlib.sha1).hexdigest()
|
||||
|
||||
def query(self, resource, method, data=None):
|
||||
url = self.baseurl + resource
|
||||
if data and not isinstance(data, basestring):
|
||||
data = urllib.urlencode(data)
|
||||
request = RequestWithMethod(url, method, data, self._headers())
|
||||
|
||||
try:
|
||||
response = urllib2.urlopen(request)
|
||||
except urllib2.HTTPError, e:
|
||||
self.module.fail_json(
|
||||
msg="%s returned %s, with body: %s" % (url, e.code, e.read()))
|
||||
except Exception, e:
|
||||
self.module.fail_json(
|
||||
msg="Failed contacting: %s : Exception %s" % (url, e.message()))
|
||||
|
||||
try:
|
||||
return json.load(response)
|
||||
except Exception, e:
|
||||
return False
|
||||
|
||||
def getDomain(self, domain_id):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.domains.get(domain_id, False)
|
||||
|
||||
def getDomainByName(self, domain_name):
|
||||
if not self.domain_map:
|
||||
self._instMap('domain')
|
||||
|
||||
return self.getDomain(self.domain_map.get(domain_name, 0))
|
||||
|
||||
def getDomains(self):
|
||||
return self.query('dns/managed', 'GET')['data']
|
||||
|
||||
def getRecord(self, record_id):
|
||||
if not self.record_map:
|
||||
self._instMap('record')
|
||||
|
||||
return self.records.get(record_id, False)
|
||||
|
||||
def getRecordByName(self, record_name):
|
||||
if not self.record_map:
|
||||
self._instMap('record')
|
||||
|
||||
return self.getRecord(self.record_map.get(record_name, 0))
|
||||
|
||||
def getRecords(self):
|
||||
return self.query(self.record_url, 'GET')['data']
|
||||
|
||||
def _instMap(self, type):
|
||||
#@TODO cache this call so it's executed only once per ansible execution
|
||||
map = {}
|
||||
results = {}
|
||||
|
||||
# iterate over e.g. self.getDomains() || self.getRecords()
|
||||
for result in getattr(self, 'get' + type.title() + 's')():
|
||||
|
||||
map[result['name']] = result['id']
|
||||
results[result['id']] = result
|
||||
|
||||
# e.g. self.domain_map || self.record_map
|
||||
setattr(self, type + '_map', map)
|
||||
setattr(self, type + 's', results) # e.g. self.domains || self.records
|
||||
|
||||
def prepareRecord(self, data):
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
def createRecord(self, data):
|
||||
#@TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url, 'POST', data)
|
||||
|
||||
def updateRecord(self, record_id, data):
|
||||
#@TODO update the cache w/ resultant record + id when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'PUT', data)
|
||||
|
||||
def deleteRecord(self, record_id):
|
||||
#@TODO remove record from the cache when impleneted
|
||||
return self.query(self.record_url + '/' + str(record_id), 'DELETE')
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_key=dict(required=True),
|
||||
account_secret=dict(required=True, no_log=True),
|
||||
domain=dict(required=True),
|
||||
state=dict(required=True, choices=['present', 'absent']),
|
||||
record_name=dict(required=False),
|
||||
record_type=dict(required=False, choices=[
|
||||
'A', 'AAAA', 'CNAME', 'HTTPRED', 'MX', 'NS', 'PTR', 'SRV', 'TXT']),
|
||||
record_value=dict(required=False),
|
||||
record_ttl=dict(required=False, default=1800, type='int'),
|
||||
#redirecttype=dict(required=False,default='Hidden Frame Masked',choices=[ 'Hidden Frame Masked', 'Standard 301', 'Standard 302' ]),
|
||||
#hardlink=dict(requred=False, default=False, type='bool', choices=BOOLEANS)
|
||||
),
|
||||
required_together=(
|
||||
['record_value', 'record_ttl', 'record_type']
|
||||
)
|
||||
)
|
||||
|
||||
if IMPORT_ERROR:
|
||||
module.fail_json(msg="Import Error: " + IMPORT_ERROR)
|
||||
|
||||
DME = DME2(module.params["account_key"], module.params[
|
||||
"account_secret"], module.params["domain"], module)
|
||||
state = module.params["state"]
|
||||
record_name = module.params["record_name"]
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if not record_name:
|
||||
domain_records = DME.getRecords()
|
||||
if not domain_records:
|
||||
module.fail_json(
|
||||
msg="The %s domain name is not accessible with this api_key; try using its ID if known." % domain)
|
||||
module.exit_json(changed=False, result=domain_records)
|
||||
|
||||
# Fetch existing record + Build new one
|
||||
current_record = DME.getRecordByName(record_name)
|
||||
new_record = {'name': record_name}
|
||||
for i in ["record_value", "record_type", "record_ttl"]:
|
||||
if module.params[i]:
|
||||
new_record[i[len("record_"):]] = module.params[i]
|
||||
|
||||
# Compare new record against existing one
|
||||
changed = False
|
||||
if current_record:
|
||||
for i in new_record:
|
||||
if str(current_record[i]) != str(new_record[i]):
|
||||
changed = True
|
||||
new_record['id'] = str(current_record['id'])
|
||||
|
||||
# Follow Keyword Controlled Behavior
|
||||
if state == 'present':
|
||||
# return the record if no value is specified
|
||||
if not "value" in new_record:
|
||||
if not current_record:
|
||||
module.fail_json(
|
||||
msg="A record with name '%s' does not exist for domain '%s.'" % (record_name, domain))
|
||||
module.exit_json(changed=False, result=current_record)
|
||||
|
||||
# create record as it does not exist
|
||||
if not current_record:
|
||||
record = DME.createRecord(DME.prepareRecord(new_record))
|
||||
module.exit_json(changed=True, result=record)
|
||||
|
||||
# update the record
|
||||
if changed:
|
||||
DME.updateRecord(
|
||||
current_record['id'], DME.prepareRecord(new_record))
|
||||
module.exit_json(changed=True, result=new_record)
|
||||
|
||||
# return the record (no changes)
|
||||
module.exit_json(changed=False, result=current_record)
|
||||
|
||||
elif state == 'absent':
|
||||
# delete the record if it exists
|
||||
if current_record:
|
||||
DME.deleteRecord(current_record['id'])
|
||||
module.exit_json(changed=True)
|
||||
|
||||
# record does not exist, return w/o change.
|
||||
module.exit_json(changed=False)
|
||||
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="'%s' is an unknown value for the state argument" % state)
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
Loading…
Reference in a new issue