diff --git a/library/net_infrastructure/dnsmadeeasy b/library/net_infrastructure/dnsmadeeasy
new file mode 100644
index 00000000000..542b6f8e285
--- /dev/null
+++ b/library/net_infrastructure/dnsmadeeasy
@@ -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 .
+
+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: , MX: , NS: , PTR: , SRV: , TXT:
+ 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"] =>
+
+ # 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
+#<>
+main()