def delete_zone_domain(args=None, payload=None): ''' Deletion is pretty simple, domains are always unique so we we don't need to do any sanity checking to avoid deleting the wrong thing. ''' has_changed, has_failed = False, False msg, memset_api = None, None api_method = 'dns.zone_domain_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) domain_exists = check_zone_domain(data=response, domain=args['domain']) if domain_exists: api_method = 'dns.zone_domain_delete' payload['domain'] = args['domain'] has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True memset_api = response.json() # unset msg as we don't want to return unecessary info to the user. msg = None return (has_failed, has_changed, memset_api, msg)
def poll_reload_status(api_key=None, job_id=None, payload=None): ''' We poll the `job.status` endpoint every 5 seconds up to a maximum of 6 times. This is a relatively arbitrary choice of timeout, however requests rarely take longer than 15 seconds to complete. ''' memset_api, stderr, msg = None, None, None payload['id'] = job_id api_method = 'job.status' _has_failed, _msg, response = memset_api_call(api_key=api_key, api_method=api_method, payload=payload) while not response.json()['finished']: counter = 0 while counter < 6: sleep(5) _has_failed, msg, response = memset_api_call(api_key=api_key, api_method=api_method, payload=payload) counter += 1 if response.json()['error']: # the reload job was submitted but polling failed. Don't return this as an overall task failure. stderr = "Reload submitted successfully, but the Memset API returned a job error when attempting to poll the reload status." else: memset_api = response.json() msg = None return (memset_api, msg, stderr)
def create_zone_domain(args=None, zone_exists=None, zone_id=None, payload=None): ''' At this point we already know whether the containing zone exists, so we just need to create the domain (or exit if it already exists). ''' has_changed, has_failed = False, False msg = None api_method = 'dns.zone_domain_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) for zone_domain in response.json(): if zone_domain['domain'] == args['domain']: # zone domain already exists, nothing to change. has_changed = False break else: # we need to create the domain api_method = 'dns.zone_domain_create' payload['domain'] = args['domain'] payload['zone_id'] = zone_id has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True return (has_failed, has_changed, msg)
def delete_zone(args=None, zone_exists=None, payload=None): ''' Deletion requires extra sanity checking as the zone cannot be deleted if it contains domains or records. Setting force=true will override this behaviour. ''' has_changed, has_failed = False, False msg, memset_api = None, None if zone_exists: api_method = 'dns.zone_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) counter = 0 for zone in response.json(): if zone['nickname'] == args['name']: counter += 1 if counter == 1: for zone in response.json(): if zone['nickname'] == args['name']: zone_id = zone['id'] domain_count = len(zone['domains']) record_count = len(zone['records']) if (domain_count > 0 or record_count > 0) and args['force'] is False: # we need to fail out if force was not explicitly set. stderr = 'Zone contains domains or records and force was not used.' has_failed = True has_changed = False module.fail_json(failed=has_failed, changed=has_changed, msg=msg, stderr=stderr, rc=1) api_method = 'dns.zone_delete' payload['id'] = zone_id has_failed, msg, response = memset_api_call( api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True # return raw JSON from API in named var and then unset msg var so we aren't returning the same thing twice. memset_api = msg msg = None else: # zone names are not unique, so we cannot safely delete the requested # zone at this time. has_failed = True has_changed = False msg = 'Unable to delete zone as multiple zones with the same name exist.' else: has_failed, has_changed = False, False return (has_failed, has_changed, memset_api, msg)
def create_zone(args=None, zone_exists=None, payload=None): ''' At this point we already know whether the zone exists, so we just need to make the API reflect the desired state. ''' has_changed, has_failed = False, False msg, memset_api = None, None if not zone_exists: payload['ttl'] = args['ttl'] payload['nickname'] = args['name'] api_method = 'dns.zone_create' has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True else: api_method = 'dns.zone_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) for zone in response.json(): if zone['nickname'] == args['name']: break if zone['ttl'] != args['ttl']: # update the zone if the desired TTL is different. payload['id'] = zone['id'] payload['ttl'] = args['ttl'] api_method = 'dns.zone_update' has_failed, msg, response = memset_api_call( api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True # populate return var with zone info. api_method = 'dns.zone_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) zone_exists, msg, counter, zone_id = get_zone_id( zone_name=args['name'], current_zones=response.json()) if zone_exists: payload = dict() payload['id'] = zone_id api_method = 'dns.zone_info' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) memset_api = response.json() return (has_failed, has_changed, memset_api, msg)
def delete_zone_record(args=None, records=None, payload=None): ''' Matching records can be cleanly deleted without affecting other resource types, so this is pretty simple to achieve. ''' has_changed, has_failed = False, False msg, memset_api = None, None # if we have any matches, delete them. if records: for zone_record in records: if args['check_mode']: has_changed = True return (has_changed, has_failed, memset_api, msg) payload['id'] = zone_record['id'] api_method = 'dns.zone_record_delete' has_failed, msg, response = memset_api_call( api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True memset_api = zone_record # empty msg as we don't want to return a boatload of json to the user. msg = None return (has_changed, has_failed, memset_api, msg)
def get_facts(args=None): ''' Performs a simple API call and returns a JSON blob. ''' retvals, payload = dict(), dict() has_changed, has_failed = False, False msg, stderr, memset_api = None, None, None payload['name'] = args['name'] api_method = 'memstore.usage' has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) if has_failed: # this is the first time the API is called; incorrect credentials will # manifest themselves at this point so we need to ensure the user is # informed of the reason. retvals['failed'] = has_failed retvals['msg'] = msg retvals['stderr'] = "API returned an error: {0}" . format(response.status_code) return(retvals) # we don't want to return the same thing twice msg = None memset_api = response.json() retvals['changed'] = has_changed retvals['failed'] = has_failed for val in ['msg', 'memset_api']: if val is not None: retvals[val] = eval(val) return(retvals)
def create_or_delete_domain(args=None): ''' We need to perform some initial sanity checking and also look up required info before handing it off to create or delete. ''' retvals, payload = dict(), dict() has_changed, has_failed = False, False msg, stderr, memset_api = None, None, None # get the zones and check if the relevant zone exists. api_method = 'dns.zone_list' has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) if has_failed: # this is the first time the API is called; incorrect credentials will # manifest themselves at this point so we need to ensure the user is # informed of the reason. retvals['failed'] = has_failed retvals['msg'] = msg retvals['stderr'] = "API returned an error: {0}".format( response.status_code) return (retvals) zone_exists, msg, counter, zone_id = get_zone_id( zone_name=args['zone'], current_zones=response.json()) if not zone_exists: # the zone needs to be unique - this isn't a requirement of Memset's API but it # makes sense in the context of this module. has_failed = True if counter == 0: stderr = "DNS zone '{0}' does not exist, cannot create domain.".format( args['zone']) elif counter > 1: stderr = "{0} matches multiple zones, cannot create domain.".format( args['zone']) retvals['failed'] = has_failed retvals['msg'] = stderr return (retvals) if args['state'] == 'present': has_failed, has_changed, msg = create_zone_domain( args=args, zone_exists=zone_exists, zone_id=zone_id, payload=payload) if args['state'] == 'absent': has_failed, has_changed, memset_api, msg = delete_zone_domain( args=args, payload=payload) retvals['changed'] = has_changed retvals['failed'] = has_failed for val in ['msg', 'stderr', 'memset_api']: if val is not None: retvals[val] = eval(val) return (retvals)
def main(): global module module = AnsibleModule(argument_spec=dict(state=dict( default='present', choices=['present', 'absent'], type='str'), api_key=dict(required=True, type='str', no_log=True), domain=dict(required=True, aliases=['name'], type='str'), zone=dict(required=True, type='str')), supports_check_mode=True) # populate the dict with the user-provided vars. args = dict() for key, arg in module.params.items(): args[key] = arg args['check_mode'] = module.check_mode # validate some API-specific limitations. api_validation(args=args) if module.check_mode: retvals = check(args) else: retvals = create_or_delete_domain(args) # we would need to populate the return values with the API's response # in several places so it's easier to do it at the end instead. if not retvals['failed']: if args['state'] == 'present' and not module.check_mode: payload = dict() payload['domain'] = args['domain'] api_method = 'dns.zone_domain_info' _has_failed, _msg, response = memset_api_call( api_key=args['api_key'], api_method=api_method, payload=payload) retvals['memset_api'] = response.json() if retvals['failed']: module.fail_json(**retvals) else: module.exit_json(**retvals)
def reload_dns(args=None): ''' DNS reloads are a single API call and therefore there's not much which can go wrong outside of auth errors. ''' retvals, payload = dict(), dict() has_changed, has_failed = False, False memset_api, msg, stderr = None, None, None api_method = 'dns.reload' has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) if has_failed: # this is the first time the API is called; incorrect credentials will # manifest themselves at this point so we need to ensure the user is # informed of the reason. retvals['failed'] = has_failed retvals['memset_api'] = response.json() retvals['msg'] = msg return (retvals) # set changed to true if the reload request was accepted. has_changed = True memset_api = msg # empty msg var as we don't want to return the API's json response twice. msg = None if args['poll']: # hand off to the poll function. job_id = response.json()['id'] memset_api, msg, stderr = poll_reload_status(api_key=args['api_key'], job_id=job_id, payload=payload) # assemble return variables. retvals['failed'] = has_failed retvals['changed'] = has_changed for val in ['msg', 'stderr', 'memset_api']: if val is not None: retvals[val] = eval(val) return (retvals)
def check(args=None): ''' Support for running with check mode. ''' retvals = dict() api_method = 'dns.zone_list' has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) zone_exists, counter = check_zone(data=response, name=args['name']) # set changed to true if the operation would cause a change. has_changed = ((zone_exists and args['state'] == 'absent') or (not zone_exists and args['state'] == 'present')) retvals['changed'] = has_changed retvals['failed'] = has_failed return (retvals)
def check(args=None): ''' Support for running with check mode. ''' retvals = dict() has_changed = False api_method = 'dns.zone_domain_list' has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) domain_exists = check_zone_domain(data=response, domain=args['domain']) # set changed to true if the operation would cause a change. has_changed = ((domain_exists and args['state'] == 'absent') or (not domain_exists and args['state'] == 'present')) retvals['changed'] = has_changed retvals['failed'] = has_failed return (retvals)
def create_or_delete(args=None): ''' We need to perform some initial sanity checking and also look up required info before handing it off to create or delete. ''' retvals, payload = dict(), dict() has_failed, has_changed = False, False msg, memset_api, stderr = None, None, None # get the zones and check if the relevant zone exists. api_method = 'dns.zone_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) if _has_failed: # this is the first time the API is called; incorrect credentials will # manifest themselves at this point so we need to ensure the user is # informed of the reason. retvals['failed'] = _has_failed retvals['msg'] = _msg return (retvals) zone_exists, _msg, counter, _zone_id = get_zone_id( zone_name=args['name'], current_zones=response.json()) if args['state'] == 'present': has_failed, has_changed, memset_api, msg = create_zone( args=args, zone_exists=zone_exists, payload=payload) elif args['state'] == 'absent': has_failed, has_changed, memset_api, msg = delete_zone( args=args, zone_exists=zone_exists, payload=payload) retvals['failed'] = has_failed retvals['changed'] = has_changed for val in ['msg', 'stderr', 'memset_api']: if val is not None: retvals[val] = eval(val) return (retvals)
def create_or_delete(args=None): ''' We need to perform some initial sanity checking and also look up required info before handing it off to create or delete functions. Check mode is integrated into the create or delete functions. ''' has_failed, has_changed = False, False msg, memset_api, stderr = None, None, None retvals, payload = dict(), dict() # get the zones and check if the relevant zone exists. api_method = 'dns.zone_list' _has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) if _has_failed: # this is the first time the API is called; incorrect credentials will # manifest themselves at this point so we need to ensure the user is # informed of the reason. retvals['failed'] = _has_failed retvals['msg'] = msg retvals['stderr'] = "API returned an error: {0}".format( response.status_code) return (retvals) zone_exists, _msg, counter, zone_id = get_zone_id( zone_name=args['zone'], current_zones=response.json()) if not zone_exists: has_failed = True if counter == 0: stderr = "DNS zone {0} does not exist.".format(args['zone']) elif counter > 1: stderr = "{0} matches multiple zones.".format(args['zone']) retvals['failed'] = has_failed retvals['msg'] = stderr retvals['stderr'] = stderr return (retvals) # get a list of all records ( as we can't limit records by zone) api_method = 'dns.zone_record_list' _has_failed, _msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method) # find any matching records records = [ record for record in response.json() if record['zone_id'] == zone_id and record['record'] == args['record'] and record['type'] == args['type'] ] if args['state'] == 'present': has_changed, has_failed, memset_api, msg = create_zone_record( args=args, zone_id=zone_id, records=records, payload=payload) if args['state'] == 'absent': has_changed, has_failed, memset_api, msg = delete_zone_record( args=args, records=records, payload=payload) retvals['changed'] = has_changed retvals['failed'] = has_failed for val in ['msg', 'stderr', 'memset_api']: if val is not None: retvals[val] = eval(val) return (retvals)
def create_zone_record(args=None, zone_id=None, records=None, payload=None): ''' Sanity checking has already occurred prior to this function being called, so we can go ahead and either create or update the record. As defaults are defined for all values in the argument_spec, this may cause some changes to occur as the defaults are enforced (if the user has only configured required variables). ''' has_changed, has_failed = False, False msg, memset_api = None, None # assemble the new record. new_record = dict() new_record['zone_id'] = zone_id for arg in ['priority', 'address', 'relative', 'record', 'ttl', 'type']: new_record[arg] = args[arg] # if we have any matches, update them. if records: for zone_record in records: # record exists, add ID to payload. new_record['id'] = zone_record['id'] if zone_record == new_record: # nothing to do; record is already correct so we populate # the return var with the existing record's details. memset_api = zone_record return (has_changed, has_failed, memset_api, msg) else: # merge dicts ensuring we change any updated values payload = zone_record.copy() payload.update(new_record) api_method = 'dns.zone_record_update' if args['check_mode']: has_changed = True # return the new record to the user in the returned var. memset_api = new_record return (has_changed, has_failed, memset_api, msg) has_failed, msg, response = memset_api_call( api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True memset_api = new_record # empty msg as we don't want to return a boatload of json to the user. msg = None else: # no record found, so we need to create it api_method = 'dns.zone_record_create' payload = new_record if args['check_mode']: has_changed = True # populate the return var with the new record's details. memset_api = new_record return (has_changed, has_failed, memset_api, msg) has_failed, msg, response = memset_api_call(api_key=args['api_key'], api_method=api_method, payload=payload) if not has_failed: has_changed = True memset_api = new_record # empty msg as we don't want to return a boatload of json to the user. msg = None return (has_changed, has_failed, memset_api, msg)