def import_zone_data(filename, api_key, **kwargs): nsone_obj = NSONE(apiKey=api_key) extension = os.path.splitext(filename)[1] with open(filename, 'rb') as f: if extension == '.csv': reader = csv.DictReader(f) data = transform_csv(reader) elif extension == '.json': data = transform_json(json.loads(f)) if kwargs.get('delete'): delete_zone_data(nsone_obj, data) return for k, v in data.iteritems(): try: zone = nsone_obj.createZone(k) print 'Added Zone {}'.format(zone) except AuthException as e: # Invalid api key passed in print e.message return except ResourceException as e: # zone already exists print '{} {}'.format(k, e.message) zone = nsone_obj.loadZone(k) for rec in v: answers = rec['Data'].split() try: # determine which record type to add using types provided in the file method_name = 'add_{}'.format(rec['Type']) add_method = getattr(zone, method_name) # data corresponds to the answer and it might have priority values record = add_method(k, [answers], ttl=rec['TTL']) print 'Successfully Added record {}'.format(record) except ResourceException as e: # record already exists, so add answers to it # Or Invalid add method print '{} {}'.format(rec, e.message) try: record = nsone_obj.loadRecord(k, rec['Type'], k) print 'Successfully loaded record {}'.format(k, rec['Type']) recordAnswers = {answer['answer'][0] for answer in record.data['answers']} if recordAnswers.intersection(answers[0]): record.addAnswers(answers) print 'Added answer: {}'.format(answers[0]) record.addAnswers([answers]) print 'Successfully Processed Answers {}'.format(answers) except ResourceException as e: # Invalid format for answer, so ignore this answer, or invalid record type print e.message continue
class Ns1Provider(BaseProvider): ''' Ns1 provider nsone: class: octodns.provider.ns1.Ns1Provider api_key: env/NS1_API_KEY ''' SUPPORTS_GEO = True SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SPF', 'SRV', 'TXT')) ZONE_NOT_FOUND_MESSAGE = 'server error: zone not found' def __init__(self, id, api_key, *args, **kwargs): self.log = getLogger('Ns1Provider[{}]'.format(id)) self.log.debug('__init__: id=%s, api_key=***', id) super(Ns1Provider, self).__init__(id, *args, **kwargs) self._client = NSONE(apiKey=api_key) def _data_for_A(self, _type, record): # record meta (which would include geo information is only # returned when getting a record's detail, not from zone detail geo = defaultdict(list) data = { 'ttl': record['ttl'], 'type': _type, } values, codes = [], [] if 'answers' not in record: values = record['short_answers'] for answer in record.get('answers', []): meta = answer.get('meta', {}) if meta: # country + state and country + province are allowed # in that case though, supplying a state/province would # be redundant since the country would supercede in when # resolving the record. it is syntactically valid, however. country = meta.get('country', []) us_state = meta.get('us_state', []) ca_province = meta.get('ca_province', []) for cntry in country: cn = transformations.cc_to_cn(cntry) con = transformations.cn_to_ctca2(cn) key = '{}-{}'.format(con, cntry) geo[key].extend(answer['answer']) for state in us_state: key = 'NA-US-{}'.format(state) geo[key].extend(answer['answer']) for province in ca_province: key = 'NA-CA-{}'.format(province) geo[key].extend(answer['answer']) for code in meta.get('iso_region_code', []): key = code geo[key].extend(answer['answer']) else: values.extend(answer['answer']) codes.append([]) values = [unicode(x) for x in values] geo = OrderedDict( {unicode(k): [unicode(x) for x in v] for k, v in geo.items()}) data['values'] = values data['geo'] = geo return data _data_for_AAAA = _data_for_A def _data_for_SPF(self, _type, record): values = [v.replace(';', '\\;') for v in record['short_answers']] return {'ttl': record['ttl'], 'type': _type, 'values': values} _data_for_TXT = _data_for_SPF def _data_for_CAA(self, _type, record): values = [] for answer in record['short_answers']: flags, tag, value = answer.split(' ', 2) values.append({ 'flags': flags, 'tag': tag, 'value': value, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_CNAME(self, _type, record): try: value = record['short_answers'][0] except IndexError: value = None return { 'ttl': record['ttl'], 'type': _type, 'value': value, } _data_for_ALIAS = _data_for_CNAME _data_for_PTR = _data_for_CNAME def _data_for_MX(self, _type, record): values = [] for answer in record['short_answers']: preference, exchange = answer.split(' ', 1) values.append({ 'preference': preference, 'exchange': exchange, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NAPTR(self, _type, record): values = [] for answer in record['short_answers']: order, preference, flags, service, regexp, replacement = \ answer.split(' ', 5) values.append({ 'flags': flags, 'order': order, 'preference': preference, 'regexp': regexp, 'replacement': replacement, 'service': service, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NS(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'values': [ a if a.endswith('.') else '{}.'.format(a) for a in record['short_answers'] ], } def _data_for_SRV(self, _type, record): values = [] for answer in record['short_answers']: priority, weight, port, target = answer.split(' ', 3) values.append({ 'priority': priority, 'weight': weight, 'port': port, 'target': target, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def populate(self, zone, target=False, lenient=False): self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, target, lenient) try: nsone_zone = self._client.loadZone(zone.name[:-1]) records = nsone_zone.data['records'] geo_records = nsone_zone.search(has_geo=True) exists = True except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise records = [] geo_records = [] exists = False before = len(zone.records) # geo information isn't returned from the main endpoint, so we need # to query for all records with geo information zone_hash = {} for record in chain(records, geo_records): _type = record['type'] if _type not in self.SUPPORTS: continue data_for = getattr(self, '_data_for_{}'.format(_type)) name = zone.hostname_from_fqdn(record['domain']) record = Record.new(zone, name, data_for(_type, record), source=self, lenient=lenient) zone_hash[(_type, name)] = record [zone.add_record(r) for r in zone_hash.values()] self.log.info('populate: found %s records, exists=%s', len(zone.records) - before, exists) return exists def _params_for_A(self, record): params = {'answers': record.values, 'ttl': record.ttl} if hasattr(record, 'geo'): # purposefully set non-geo answers to have an empty meta, # so that we know we did this on purpose if/when troubleshooting params['answers'] = [{ "answer": [x], "meta": {} } for x in record.values] has_country = False for iso_region, target in record.geo.items(): key = 'iso_region_code' value = iso_region if not has_country and \ len(value.split('-')) > 1: # pragma: nocover has_country = True for answer in target.values: params['answers'].append( { 'answer': [answer], 'meta': { key: [value] }, }, ) params['filters'] = [] if has_country: params['filters'].append({"filter": "shuffle", "config": {}}) params['filters'].append({ "filter": "geotarget_country", "config": {} }) params['filters'].append({ "filter": "select_first_n", "config": { "N": 1 } }) self.log.debug("params for A: %s", params) return params _params_for_AAAA = _params_for_A _params_for_NS = _params_for_A def _params_for_SPF(self, record): # NS1 seems to be the only provider that doesn't want things # escaped in values so we have to strip them here and add # them when going the other way values = [v.replace('\\;', ';') for v in record.values] return {'answers': values, 'ttl': record.ttl} _params_for_TXT = _params_for_SPF def _params_for_CAA(self, record): values = [(v.flags, v.tag, v.value) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_CNAME(self, record): return {'answers': [record.value], 'ttl': record.ttl} _params_for_ALIAS = _params_for_CNAME _params_for_PTR = _params_for_CNAME def _params_for_MX(self, record): values = [(v.preference, v.exchange) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_NAPTR(self, record): values = [(v.order, v.preference, v.flags, v.service, v.regexp, v.replacement) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_SRV(self, record): values = [(v.priority, v.weight, v.port, v.target) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _get_name(self, record): return record.fqdn[:-1] if record.name == '' else record.name def _apply_Create(self, nsone_zone, change): new = change.new name = self._get_name(new) _type = new._type params = getattr(self, '_params_for_{}'.format(_type))(new) meth = getattr(nsone_zone, 'add_{}'.format(_type)) try: meth(name, **params) except RateLimitException as e: period = float(e.period) self.log.warn( '_apply_Create: rate limit encountered, pausing ' 'for %ds and trying again', period) sleep(period) meth(name, **params) def _apply_Update(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) new = change.new params = getattr(self, '_params_for_{}'.format(_type))(new) try: record.update(**params) except RateLimitException as e: period = float(e.period) self.log.warn( '_apply_Update: rate limit encountered, pausing ' 'for %ds and trying again', period) sleep(period) record.update(**params) def _apply_Delete(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) try: record.delete() except RateLimitException as e: period = float(e.period) self.log.warn( '_apply_Delete: rate limit encountered, pausing ' 'for %ds and trying again', period) sleep(period) record.delete() def _apply(self, plan): desired = plan.desired changes = plan.changes self.log.debug('_apply: zone=%s, len(changes)=%d', desired.name, len(changes)) domain_name = desired.name[:-1] try: nsone_zone = self._client.loadZone(domain_name) except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise self.log.debug('_apply: no matching zone, creating') nsone_zone = self._client.createZone(domain_name) for change in changes: class_name = change.__class__.__name__ getattr(self, '_apply_{}'.format(class_name))(nsone_zone, change)
# # License under The MIT License (MIT). See LICENSE in project root. # from nsone import NSONE # NSONE will use config in ~/.nsone by default nsone = NSONE() # to specify an apikey here instead, use: # nsone = NSONE(apiKey='qACMD09OJXBxT7XOuRs8') # to load an alternate configuration file: # nsone = NSONE(configFile='/etc/nsone/api.json') zone = nsone.loadZone("test.com") qps = zone.qps() print("current QPS for test.com: %s" % qps["qps"]) usage = zone.usage() print("test.com usage: %s" % usage) usage = zone.usage(period="30d") print("test.com 30 d usage: %s" % usage) rec = zone.loadRecord("foo", "A") rqps = rec.qps() print("current QPS for foo.test.com: %s" % rqps["qps"]) usage = rec.usage() print("foo.test.com usage: %s" % usage) usage = rec.usage(period="30d") print("foo.test.com 30 d usage: %s" % usage)
# NSONE will use config in ~/.nsone by default nsone = NSONE() # to specify an apikey here instead, use: # nsone = NSONE(apiKey='qACMD09OJXBxT7XOuRs8') # to load an alternate configuration file: # nsone = NSONE(configFile='/etc/nsone/api.json') ###################### # LOAD / CREATE ZONE # ###################### # to load an existing zone, get a Zone object back test_zone = nsone.loadZone('test.com') # or create a new zone, get a Zone object back # you can specify options like retry, refresh, expiry, nx_ttl, etc zone = nsone.createZone('example.com', nx_ttl=3600) print(zone) # once you have a Zone, you can access all the zone information via the # data property print(zone.data['dns_servers']) ############### # ADD RECORDS # ############### # in general, use add_XXXX to add a new record to a zone, where XXXX represents
class Ns1Provider(BaseProvider): ''' Ns1 provider nsone: class: octodns.provider.ns1.Ns1Provider api_key: env/NS1_API_KEY ''' SUPPORTS_GEO = False ZONE_NOT_FOUND_MESSAGE = 'server error: zone not found' def __init__(self, id, api_key, *args, **kwargs): self.log = getLogger('Ns1Provider[{}]'.format(id)) self.log.debug('__init__: id=%s, api_key=***', id) super(Ns1Provider, self).__init__(id, *args, **kwargs) self._client = NSONE(apiKey=api_key) def _data_for_A(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'values': record['short_answers'], } _data_for_AAAA = _data_for_A _data_for_SPF = _data_for_A _data_for_TXT = _data_for_A def _data_for_CNAME(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'value': record['short_answers'][0], } _data_for_PTR = _data_for_CNAME def _data_for_MX(self, _type, record): values = [] for answer in record['short_answers']: priority, value = answer.split(' ', 1) values.append({ 'priority': priority, 'value': value, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NAPTR(self, _type, record): values = [] for answer in record['short_answers']: order, preference, flags, service, regexp, replacement = \ answer.split(' ', 5) values.append({ 'flags': flags, 'order': order, 'preference': preference, 'regexp': regexp, 'replacement': replacement, 'service': service, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NS(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'values': [ a if a.endswith('.') else '{}.'.format(a) for a in record['short_answers'] ], } def _data_for_SRV(self, _type, record): values = [] for answer in record['short_answers']: priority, weight, port, target = answer.split(' ', 3) values.append({ 'priority': priority, 'weight': weight, 'port': port, 'target': target, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def populate(self, zone, target=False): self.log.debug('populate: name=%s', zone.name) try: nsone_zone = self._client.loadZone(zone.name[:-1]) records = nsone_zone.data['records'] except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise records = [] before = len(zone.records) for record in records: _type = record['type'] data_for = getattr(self, '_data_for_{}'.format(_type)) name = zone.hostname_from_fqdn(record['domain']) record = Record.new(zone, name, data_for(_type, record)) zone.add_record(record) self.log.info('populate: found %s records', len(zone.records) - before) def _params_for_A(self, record): return {'answers': record.values, 'ttl': record.ttl} _params_for_AAAA = _params_for_A _params_for_NS = _params_for_A _params_for_SPF = _params_for_A _params_for_TXT = _params_for_A def _params_for_CNAME(self, record): return {'answers': [record.value], 'ttl': record.ttl} _params_for_PTR = _params_for_CNAME def _params_for_MX(self, record): values = [(v.priority, v.value) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_NAPTR(self, record): values = [(v.order, v.preference, v.flags, v.service, v.regexp, v.replacement) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_SRV(self, record): values = [(v.priority, v.weight, v.port, v.target) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _get_name(self, record): return record.fqdn[:-1] if record.name == '' else record.name def _apply_Create(self, nsone_zone, change): new = change.new name = self._get_name(new) _type = new._type params = getattr(self, '_params_for_{}'.format(_type))(new) getattr(nsone_zone, 'add_{}'.format(_type))(name, **params) def _apply_Update(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) new = change.new params = getattr(self, '_params_for_{}'.format(_type))(new) record.update(**params) def _apply_Delete(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) record.delete() def _apply(self, plan): desired = plan.desired changes = plan.changes self.log.debug('_apply: zone=%s, len(changes)=%d', desired.name, len(changes)) domain_name = desired.name[:-1] try: nsone_zone = self._client.loadZone(domain_name) except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise self.log.debug('_apply: no matching zone, creating') nsone_zone = self._client.createZone(domain_name) for change in changes: class_name = change.__class__.__name__ getattr(self, '_apply_{}'.format(class_name))(nsone_zone, change)
class Ns1Provider(BaseProvider): ''' Ns1 provider nsone: class: octodns.provider.ns1.Ns1Provider api_key: env/NS1_API_KEY ''' SUPPORTS_GEO = False SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SPF', 'SRV', 'TXT')) ZONE_NOT_FOUND_MESSAGE = 'server error: zone not found' def __init__(self, id, api_key, *args, **kwargs): self.log = getLogger('Ns1Provider[{}]'.format(id)) self.log.debug('__init__: id=%s, api_key=***', id) super(Ns1Provider, self).__init__(id, *args, **kwargs) self._client = NSONE(apiKey=api_key) def _data_for_A(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'values': record['short_answers'], } _data_for_AAAA = _data_for_A def _data_for_SPF(self, _type, record): values = [v.replace(';', '\;') for v in record['short_answers']] return {'ttl': record['ttl'], 'type': _type, 'values': values} _data_for_TXT = _data_for_SPF def _data_for_CAA(self, _type, record): values = [] for answer in record['short_answers']: flags, tag, value = answer.split(' ', 2) values.append({ 'flags': flags, 'tag': tag, 'value': value, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_CNAME(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'value': record['short_answers'][0], } _data_for_ALIAS = _data_for_CNAME _data_for_PTR = _data_for_CNAME def _data_for_MX(self, _type, record): values = [] for answer in record['short_answers']: preference, exchange = answer.split(' ', 1) values.append({ 'preference': preference, 'exchange': exchange, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NAPTR(self, _type, record): values = [] for answer in record['short_answers']: order, preference, flags, service, regexp, replacement = \ answer.split(' ', 5) values.append({ 'flags': flags, 'order': order, 'preference': preference, 'regexp': regexp, 'replacement': replacement, 'service': service, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def _data_for_NS(self, _type, record): return { 'ttl': record['ttl'], 'type': _type, 'values': [ a if a.endswith('.') else '{}.'.format(a) for a in record['short_answers'] ], } def _data_for_SRV(self, _type, record): values = [] for answer in record['short_answers']: priority, weight, port, target = answer.split(' ', 3) values.append({ 'priority': priority, 'weight': weight, 'port': port, 'target': target, }) return { 'ttl': record['ttl'], 'type': _type, 'values': values, } def populate(self, zone, target=False, lenient=False): self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name, target, lenient) try: nsone_zone = self._client.loadZone(zone.name[:-1]) records = nsone_zone.data['records'] except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise records = [] before = len(zone.records) for record in records: _type = record['type'] data_for = getattr(self, '_data_for_{}'.format(_type)) name = zone.hostname_from_fqdn(record['domain']) record = Record.new(zone, name, data_for(_type, record), source=self, lenient=lenient) zone.add_record(record) self.log.info('populate: found %s records', len(zone.records) - before) def _params_for_A(self, record): return {'answers': record.values, 'ttl': record.ttl} _params_for_AAAA = _params_for_A _params_for_NS = _params_for_A def _params_for_SPF(self, record): # NS1 seems to be the only provider that doesn't want things escaped in # values so we have to strip them here and add them when going the # other way values = [v.replace('\;', ';') for v in record.values] return {'answers': values, 'ttl': record.ttl} _params_for_TXT = _params_for_SPF def _params_for_CAA(self, record): values = [(v.flags, v.tag, v.value) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_CNAME(self, record): return {'answers': [record.value], 'ttl': record.ttl} _params_for_ALIAS = _params_for_CNAME _params_for_PTR = _params_for_CNAME def _params_for_MX(self, record): values = [(v.preference, v.exchange) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_NAPTR(self, record): values = [(v.order, v.preference, v.flags, v.service, v.regexp, v.replacement) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _params_for_SRV(self, record): values = [(v.priority, v.weight, v.port, v.target) for v in record.values] return {'answers': values, 'ttl': record.ttl} def _get_name(self, record): return record.fqdn[:-1] if record.name == '' else record.name def _apply_Create(self, nsone_zone, change): new = change.new name = self._get_name(new) _type = new._type params = getattr(self, '_params_for_{}'.format(_type))(new) meth = getattr(nsone_zone, 'add_{}'.format(_type)) try: meth(name, **params) except RateLimitException as e: self.log.warn( '_apply_Create: rate limit encountered, pausing ' 'for %ds and trying again', e.period) sleep(e.period) meth(name, **params) def _apply_Update(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) new = change.new params = getattr(self, '_params_for_{}'.format(_type))(new) try: record.update(**params) except RateLimitException as e: self.log.warn( '_apply_Update: rate limit encountered, pausing ' 'for %ds and trying again', e.period) sleep(e.period) record.update(**params) def _apply_Delete(self, nsone_zone, change): existing = change.existing name = self._get_name(existing) _type = existing._type record = nsone_zone.loadRecord(name, _type) try: record.delete() except RateLimitException as e: self.log.warn( '_apply_Delete: rate limit encountered, pausing ' 'for %ds and trying again', e.period) sleep(e.period) record.delete() def _apply(self, plan): desired = plan.desired changes = plan.changes self.log.debug('_apply: zone=%s, len(changes)=%d', desired.name, len(changes)) domain_name = desired.name[:-1] try: nsone_zone = self._client.loadZone(domain_name) except ResourceException as e: if e.message != self.ZONE_NOT_FOUND_MESSAGE: raise self.log.debug('_apply: no matching zone, creating') nsone_zone = self._client.createZone(domain_name) for change in changes: class_name = change.__class__.__name__ getattr(self, '_apply_{}'.format(class_name))(nsone_zone, change)