1#!/usr/bin/python 2# 3# Copyright: Ansible Project 4# 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = { 12 'metadata_version': '1.1', 13 'status': ['preview'], 14 'supported_by': 'community' 15} 16 17 18DOCUMENTATION = ''' 19--- 20module: dnsimple 21version_added: "1.6" 22short_description: Interface with dnsimple.com (a DNS hosting service) 23description: 24 - "Manages domains and records via the DNSimple API, see the docs: U(http://developer.dnsimple.com/)." 25notes: 26 - DNSimple API v1 is deprecated. Please install dnsimple-python>=1.0.0 which uses v2 API. 27options: 28 account_email: 29 description: 30 - Account email. If omitted, the environment variables C(DNSIMPLE_EMAIL) and C(DNSIMPLE_API_TOKEN) will be looked for. 31 - "If those aren't found, a C(.dnsimple) file will be looked for, see: U(https://github.com/mikemaccana/dnsimple-python#getting-started)." 32 type: str 33 account_api_token: 34 description: 35 - Account API token. See I(account_email) for more information. 36 type: str 37 domain: 38 description: 39 - Domain to work with. Can be the domain name (e.g. "mydomain.com") or the numeric ID of the domain in DNSimple. 40 - If omitted, a list of domains will be returned. 41 - If domain is present but the domain doesn't exist, it will be created. 42 type: str 43 record: 44 description: 45 - Record to add, if blank a record for the domain will be created, supports the wildcard (*). 46 type: str 47 record_ids: 48 description: 49 - List of records to ensure they either exist or do not exist. 50 type: list 51 type: 52 description: 53 - The type of DNS record to create. 54 choices: [ 'A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 'POOL' ] 55 type: str 56 ttl: 57 description: 58 - The TTL to give the new record in seconds. 59 default: 3600 60 type: int 61 value: 62 description: 63 - Record value. 64 - Must be specified when trying to ensure a record exists. 65 type: str 66 priority: 67 description: 68 - Record priority. 69 type: int 70 state: 71 description: 72 - whether the record should exist or not. 73 choices: [ 'present', 'absent' ] 74 default: present 75 type: str 76 solo: 77 description: 78 - Whether the record should be the only one for that record type and record name. 79 - Only use with C(state) is set to C(present) on a record. 80 type: 'bool' 81 default: no 82requirements: 83 - "dnsimple >= 1.0.0" 84author: "Alex Coomans (@drcapulet)" 85''' 86 87EXAMPLES = ''' 88- name: Authenticate using email and API token and fetch all domains 89 dnsimple: 90 account_email: test@example.com 91 account_api_token: dummyapitoken 92 delegate_to: localhost 93 94- name: Fetch my.com domain records 95 dnsimple: 96 domain: my.com 97 state: present 98 delegate_to: localhost 99 register: records 100 101- name: Delete a domain 102 dnsimple: 103 domain: my.com 104 state: absent 105 delegate_to: localhost 106 107- name: Create a test.my.com A record to point to 127.0.0.1 108 dnsimple: 109 domain: my.com 110 record: test 111 type: A 112 value: 127.0.0.1 113 delegate_to: localhost 114 register: record 115 116- name: Delete record using record_ids 117 dnsimple: 118 domain: my.com 119 record_ids: '{{ record["id"] }}' 120 state: absent 121 delegate_to: localhost 122 123- name: Create a my.com CNAME record to example.com 124 dnsimple: 125 domain: my.com 126 record: '' 127 type: CNAME 128 value: example.com 129 state: present 130 delegate_to: localhost 131 132- name: change TTL value for a record 133 dnsimple: 134 domain: my.com 135 record: '' 136 type: CNAME 137 value: example.com 138 ttl: 600 139 state: present 140 delegate_to: localhost 141 142- name: Delete the record 143 dnsimple: 144 domain: my.com 145 record: '' 146 type: CNAME 147 value: example.com 148 state: absent 149 delegate_to: localhost 150''' 151 152RETURN = r"""# """ 153 154import os 155import traceback 156from distutils.version import LooseVersion 157 158DNSIMPLE_IMP_ERR = None 159try: 160 from dnsimple import DNSimple 161 from dnsimple.dnsimple import __version__ as dnsimple_version 162 from dnsimple.dnsimple import DNSimpleException 163 HAS_DNSIMPLE = True 164except ImportError: 165 DNSIMPLE_IMP_ERR = traceback.format_exc() 166 HAS_DNSIMPLE = False 167 168from ansible.module_utils.basic import AnsibleModule, missing_required_lib 169 170 171def main(): 172 module = AnsibleModule( 173 argument_spec=dict( 174 account_email=dict(type='str'), 175 account_api_token=dict(type='str', no_log=True), 176 domain=dict(type='str'), 177 record=dict(type='str'), 178 record_ids=dict(type='list'), 179 type=dict(type='str', choices=['A', 'ALIAS', 'CNAME', 'MX', 'SPF', 'URL', 'TXT', 'NS', 'SRV', 'NAPTR', 'PTR', 'AAAA', 'SSHFP', 'HINFO', 180 'POOL']), 181 ttl=dict(type='int', default=3600), 182 value=dict(type='str'), 183 priority=dict(type='int'), 184 state=dict(type='str', choices=['present', 'absent'], default='present'), 185 solo=dict(type='bool', default=False), 186 ), 187 required_together=[ 188 ['record', 'value'] 189 ], 190 supports_check_mode=True, 191 ) 192 193 if not HAS_DNSIMPLE: 194 module.fail_json(msg=missing_required_lib('dnsimple'), exception=DNSIMPLE_IMP_ERR) 195 196 if LooseVersion(dnsimple_version) < LooseVersion('1.0.0'): 197 module.fail_json(msg="Current version of dnsimple Python module [%s] uses 'v1' API which is deprecated." 198 " Please upgrade to version 1.0.0 and above to use dnsimple 'v2' API." % dnsimple_version) 199 200 account_email = module.params.get('account_email') 201 account_api_token = module.params.get('account_api_token') 202 domain = module.params.get('domain') 203 record = module.params.get('record') 204 record_ids = module.params.get('record_ids') 205 record_type = module.params.get('type') 206 ttl = module.params.get('ttl') 207 value = module.params.get('value') 208 priority = module.params.get('priority') 209 state = module.params.get('state') 210 is_solo = module.params.get('solo') 211 212 if account_email and account_api_token: 213 client = DNSimple(email=account_email, api_token=account_api_token) 214 elif os.environ.get('DNSIMPLE_EMAIL') and os.environ.get('DNSIMPLE_API_TOKEN'): 215 client = DNSimple(email=os.environ.get('DNSIMPLE_EMAIL'), api_token=os.environ.get('DNSIMPLE_API_TOKEN')) 216 else: 217 client = DNSimple() 218 219 try: 220 # Let's figure out what operation we want to do 221 222 # No domain, return a list 223 if not domain: 224 domains = client.domains() 225 module.exit_json(changed=False, result=[d['domain'] for d in domains]) 226 227 # Domain & No record 228 if domain and record is None and not record_ids: 229 domains = [d['domain'] for d in client.domains()] 230 if domain.isdigit(): 231 dr = next((d for d in domains if d['id'] == int(domain)), None) 232 else: 233 dr = next((d for d in domains if d['name'] == domain), None) 234 if state == 'present': 235 if dr: 236 module.exit_json(changed=False, result=dr) 237 else: 238 if module.check_mode: 239 module.exit_json(changed=True) 240 else: 241 module.exit_json(changed=True, result=client.add_domain(domain)['domain']) 242 243 # state is absent 244 else: 245 if dr: 246 if not module.check_mode: 247 client.delete(domain) 248 module.exit_json(changed=True) 249 else: 250 module.exit_json(changed=False) 251 252 # need the not none check since record could be an empty string 253 if domain and record is not None: 254 records = [r['record'] for r in client.records(str(domain), params={'name': record})] 255 256 if not record_type: 257 module.fail_json(msg="Missing the record type") 258 259 if not value: 260 module.fail_json(msg="Missing the record value") 261 262 rr = next((r for r in records if r['name'] == record and r['type'] == record_type and r['content'] == value), None) 263 264 if state == 'present': 265 changed = False 266 if is_solo: 267 # delete any records that have the same name and record type 268 same_type = [r['id'] for r in records if r['name'] == record and r['type'] == record_type] 269 if rr: 270 same_type = [rid for rid in same_type if rid != rr['id']] 271 if same_type: 272 if not module.check_mode: 273 for rid in same_type: 274 client.delete_record(str(domain), rid) 275 changed = True 276 if rr: 277 # check if we need to update 278 if rr['ttl'] != ttl or rr['priority'] != priority: 279 data = {} 280 if ttl: 281 data['ttl'] = ttl 282 if priority: 283 data['priority'] = priority 284 if module.check_mode: 285 module.exit_json(changed=True) 286 else: 287 module.exit_json(changed=True, result=client.update_record(str(domain), str(rr['id']), data)['record']) 288 else: 289 module.exit_json(changed=changed, result=rr) 290 else: 291 # create it 292 data = { 293 'name': record, 294 'type': record_type, 295 'content': value, 296 } 297 if ttl: 298 data['ttl'] = ttl 299 if priority: 300 data['priority'] = priority 301 if module.check_mode: 302 module.exit_json(changed=True) 303 else: 304 module.exit_json(changed=True, result=client.add_record(str(domain), data)['record']) 305 306 # state is absent 307 else: 308 if rr: 309 if not module.check_mode: 310 client.delete_record(str(domain), rr['id']) 311 module.exit_json(changed=True) 312 else: 313 module.exit_json(changed=False) 314 315 # Make sure these record_ids either all exist or none 316 if domain and record_ids: 317 current_records = [str(r['record']['id']) for r in client.records(str(domain))] 318 wanted_records = [str(r) for r in record_ids] 319 if state == 'present': 320 difference = list(set(wanted_records) - set(current_records)) 321 if difference: 322 module.fail_json(msg="Missing the following records: %s" % difference) 323 else: 324 module.exit_json(changed=False) 325 326 # state is absent 327 else: 328 difference = list(set(wanted_records) & set(current_records)) 329 if difference: 330 if not module.check_mode: 331 for rid in difference: 332 client.delete_record(str(domain), rid) 333 module.exit_json(changed=True) 334 else: 335 module.exit_json(changed=False) 336 337 except DNSimpleException as e: 338 module.fail_json(msg="Unable to contact DNSimple: %s" % e.message) 339 340 module.fail_json(msg="Unknown what you wanted me to do") 341 342 343if __name__ == '__main__': 344 main() 345