def test_explicit_rollback_and_commit(zone): with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text('bar.foo', None)) txn.rollback() assert zone.get_node('bar.foo') is not None with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text('bar.foo', None)) txn.commit() assert zone.get_node('bar.foo') is None with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete(dns.name.from_text('bar.foo', None)) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.add('bar.foo', 300, dns.rdata.from_text('in', 'txt', 'hi')) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.replace('bar.foo', 300, dns.rdata.from_text('in', 'txt', 'hi')) with pytest.raises(dns.transaction.AlreadyEnded): with zone.reader() as txn: txn.rollback() txn.get('bar.foo', 'in', 'mx') with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete_exact('bar.foo') with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.name_exists('bar.foo') with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.update_serial() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.changed() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.rollback() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.commit() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() for rdataset in txn: print(rdataset)
def test_explicit_rollback_and_commit(zone): with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None)) txn.rollback() assert zone.get_node("bar.foo") is not None with zone.writer() as txn: assert not txn.changed() txn.delete(dns.name.from_text("bar.foo", None)) txn.commit() assert zone.get_node("bar.foo") is None with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete(dns.name.from_text("bar.foo", None)) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.add("bar.foo", 300, dns.rdata.from_text("in", "txt", "hi")) with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.replace("bar.foo", 300, dns.rdata.from_text("in", "txt", "hi")) with pytest.raises(dns.transaction.AlreadyEnded): with zone.reader() as txn: txn.rollback() txn.get("bar.foo", "in", "mx") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.delete_exact("bar.foo") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.name_exists("bar.foo") with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.update_serial() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.changed() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.rollback() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() txn.commit() with pytest.raises(dns.transaction.AlreadyEnded): with zone.writer() as txn: txn.rollback() for rdataset in txn: pass
def convert_zone(domain, zone): new_zone = dns.zone.Zone(origin=(domain.rstrip('.') + '.')) for name in zone: new_name = adjust_node_name(zone.origin, domain, str(name)) node = zone.get_node(name) for rdataset in node.rdatasets: new_rdataset = new_zone.find_rdataset(new_name, rdtype=rdataset.rdtype, create=True) for rdata in rdataset: new_rdata = dns.rdata.from_text(1, rdata.rdtype, rdata.to_text()) if rdataset.rdtype == dns.rdatatype.CNAME: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text(adjust_node_name(zone.origin, domain, str(new_rdata.target.derelativize(zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.MX: if not new_rdata.exchange.is_absolute(): new_rdata.exchange = dns.name.from_text(adjust_node_name(zone.origin, domain, str(new_rdata.exchange.derelativize(zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.NS: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text(adjust_node_name(zone.origin, domain, str(new_rdata.target.derelativize(zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.SRV: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text(adjust_node_name(zone.origin, domain, str(new_rdata.target.derelativize(zone.origin))), origin=new_zone.origin) new_rdataset.add(new_rdata, ttl=rdataset.ttl) return new_zone
def cmd_rrcreate(args): zone = _get_records(args) name = dns.name.from_text(args.rr, zone.origin) rdataset = _create_rdataset(args.type, args.ttl, args.values) rdataset_old = None node = zone.get_node(args.rr) if node: for rds in node.rdatasets: if args.type == dns.rdatatype.to_text(rds.rdtype): rdataset_old = rds break f = BindToR53Formatter() if args.replace and rdataset_old: parts = f.replace_record(zone, name, rdataset_old, rdataset) else: parts = f.create_record(zone, name, rdataset) for xml in parts: ret = r53.change_rrsets(args.zone, xml) if args.wait: wait_for_sync(ret) else: print 'Success' pprint(ret.ChangeResourceRecordSetsResponse)
def cmd_rrdelete(args): zone = _get_records(args) name = dns.name.from_text(args.rr, zone.origin) node = zone.get_node(args.rr) if node: if len(node.rdatasets) > 1 and not args.type: rtypes = [ dns.rdatatype.to_text(rds.rdtype) for rds in node.rdatasets ] print 'Ambigious record - several resource types for record %r found: %s' % (args.rr, ', '.join(rtypes)) else: rdataset = None for rds in node.rdatasets: if args.type == dns.rdatatype.to_text(rds.rdtype) or not args.type: rdataset = rds break if not rdataset: print 'Record not found: %s, type: %s' % (args.rr, args.type) return print 'Deleting %s %s...' % (args.rr, dns.rdatatype.to_text(rds.rdtype)) f = BindToR53Formatter() for xml in f.delete_record(zone, name, rdataset): ret = r53.change_rrsets(args.zone, xml) if args.wait: wait_for_sync(ret) else: print 'Success' pprint(ret.ChangeResourceRecordSetsResponse) else: print 'Record not found: %s' % args.rr
def cmd_rrdelete(args, r53): zone = _get_records(args, r53) name = dns.name.from_text(args.rr, zone.origin) node = zone.get_node(args.rr) if node: if len(node.rdatasets) > 1 and not args.type: rtypes = [dns.rdatatype.to_text(rds.rdtype) for rds in node.rdatasets] logging.warning( 'Ambigious record - several resource types for record %r found: %s' % ( args.rr, ', '.join(rtypes))) else: rdataset = None for rds in node.rdatasets: if args.type == dns.rdatatype.to_text(rds.rdtype) or not args.type: rdataset = rds break if not rdataset: logging.warning('Record not found: %s, type: %s' % (args.rr, args.type)) return logging.info('Deleting %s %s...' % (args.rr, dns.rdatatype.to_text(rds.rdtype))) f = BindToR53Formatter() for xml in f.delete_record(zone, name, rdataset): ret = r53.change_rrsets(args.zone, xml) if args.wait: wait_for_sync(ret, r53) else: logging.info('Success') pprint(ret.ChangeResourceRecordSetsResponse) else: logging.warning('Record not found: %s' % args.rr)
def cmd_rrcreate(args, r53): zone = _get_records(args, r53) name = dns.name.from_text(args.rr, zone.origin) rdataset = _create_rdataset(args.type, args.ttl, args.values, args.weight, args.identifier, args.region, args.failover) rdataset_old = None node = zone.get_node(args.rr) if node: for rds in node.rdatasets: if args.type == dns.rdatatype.to_text(rds.rdtype): # find the rds in the requested region only if args.region is not None: for rdtype in rds.items: if hasattr(rdtype, 'region') and rdtype.region == args.region: rdataset_old = rds break else: rdataset_old = rds break f = BindToR53Formatter() if args.replace and rdataset_old: parts = f.replace_record(zone, name, rdataset_old, rdataset) else: parts = f.create_record(zone, name, rdataset) for xml in parts: if args.dump: logging.debug(xml) ret = retry(r53.change_rrsets, args.zone, xml) if args.wait: wait_for_sync(ret, r53) else: logging.info('Success') pprint(ret.ChangeResourceRecordSetsResponse)
def addRecord(self, domain, host, ip, klass, type, ttl): host = "%s." % host records = [x for x in self.zones if x.domain == domain] if not records: raise RuntimeError("Invalid domain") record = records[0] file = record.file zone = dns.zone.from_file(file, os.path.basename(file), relativize=False) node = zone.get_node(host, create=True) if type == "A": t = dns.rdatatype.A if klass == "IN": k = dns.rdataclass.IN ds = node.get_rdataset(t, k, covers=dns.rdatatype.NONE, create=True) ds.ttl = ttl if type == "A" and klass == "IN": item = A(k, t, ip) ds.items.append(item) # update version for k, v in zone.nodes.iteritems(): for ds in v.rdatasets: if ds.rdtype == dns.rdatatype.SOA: for item in ds.items: item.serial += 1 zone.to_file(file, relativize=False) self.restart()
def cmd_rrdelete(args): zone = _get_records(args) name = dns.name.from_text(args.rr, zone.origin) node = zone.get_node(args.rr) if node: if len(node.rdatasets) > 1 and not args.type: rtypes = [ dns.rdatatype.to_text(rds.rdtype) for rds in node.rdatasets ] print 'Ambigious record - several resource types for record %s found: %s' % (args.rr, ', '.join(rtypes)) else: rdataset = None for rds in node.rdatasets: if args.type == dns.rdatatype.to_text(rds.rdtype) or not args.type: rdataset = rds break if not rdataset: print 'Record not found: %s, type: %s' % (args.rr, args.type) return print 'Deleting %s %s...' % (args.rr, dns.rdatatype.to_text(rds.rdtype)) xml = BindToR53Formatter().delete_record(zone, name, rdataset) ret = r53.change_rrsets(args.zone, xml) print 'Success' pprint(ret.ChangeResourceRecordSetsResponse) else: print 'Record not found: %s' % args.rr
def addRecord(self, domain, host, ip, klass, type, ttl): host = "%s." % host records = [x for x in self.zones if x.domain == domain] if not records: raise RuntimeError("Invalid domain") record = records[0] file = record.file zone = dns.zone.from_file(file, os.path.basename(file),relativize=False) node = zone.get_node(host, create=True) if type == "A": t = dns.rdatatype.A if klass == "IN": k = dns.rdataclass.IN ds = node.get_rdataset(t, k, covers=dns.rdatatype.NONE, create=True) ds.ttl = ttl if type == "A" and klass == "IN": item = A(k, t, ip) ds.items.append(item) # update version for k, v in zone.nodes.iteritems(): for ds in v.rdatasets: if ds.rdtype == dns.rdatatype.SOA: for item in ds.items: item.serial += 1 zone.to_file(file, relativize=False) self.restart()
def test_zone_rollback(zone): a99 = dns.name.from_text("a99.example.") try: with zone.writer() as txn: rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99") txn.add(a99, rds) assert txn.name_exists(a99) raise ExpectedException except ExpectedException: pass assert not zone.get_node(a99)
def test_zone_rollback(zone): try: with zone.writer() as txn: a99 = dns.name.from_text('a99.example.') rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99') txn.add(a99, rds) assert txn.name_exists(a99) raise ExpectedException except ExpectedException: pass assert not zone.get_node(a99)
def convert_zone(domain, zone): new_zone = dns.zone.Zone(origin=(domain.rstrip('.') + '.')) for name in zone: new_name = adjust_node_name(zone.origin, domain, str(name)) node = zone.get_node(name) for rdataset in node.rdatasets: new_rdataset = new_zone.find_rdataset(new_name, rdtype=rdataset.rdtype, create=True) for rdata in rdataset: new_rdata = dns.rdata.from_text(1, rdata.rdtype, rdata.to_text()) if rdataset.rdtype == dns.rdatatype.CNAME: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text( adjust_node_name( zone.origin, domain, str(new_rdata.target.derelativize( zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.MX: if not new_rdata.exchange.is_absolute(): new_rdata.exchange = dns.name.from_text( adjust_node_name( zone.origin, domain, str( new_rdata.exchange.derelativize( zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.NS: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text( adjust_node_name( zone.origin, domain, str(new_rdata.target.derelativize( zone.origin))), origin=new_zone.origin) elif rdataset.rdtype == dns.rdatatype.SRV: if not new_rdata.target.is_absolute(): new_rdata.target = dns.name.from_text( adjust_node_name( zone.origin, domain, str(new_rdata.target.derelativize( zone.origin))), origin=new_zone.origin) new_rdataset.add(new_rdata, ttl=rdataset.ttl) return new_zone
def reply_from_zone(zone, query, allnames): name = query.question[0].name rtype = query.question[0].rdtype rclass = query.question[0].rdclass if not name.is_subdomain(zone.origin): return gen_refused(query) # qname is below delegation -> referral to the delegation t = name while len(t.labels) > len(zone.origin.labels): r = zone.get_rrset(t, dns.rdatatype.NS) if r: return gen_referral(zone, query, r) t = t.parent() # find exact match to qname & qtype r = zone.get_rrset(name, rtype) if r: return gen_answer(zone, query, (r, )) # try CNAME r = zone.get_rrset(name, dns.rdatatype.CNAME) if r: return gen_answer(zone, query, (r, )) # try ANY if rtype == dns.rdatatype.ANY: r = zone.get_node(name) rrsets = [] if r: rrsets = [] for rdataset in r.rdatasets: rrsets.append( dns.rrset.from_rdata_list(name, rdataset.ttl, rdataset)) return gen_answer(zone, query, rrsets) # TODO: try DNAME, wildcard,... # NODATA # 1. rrset not found for qname # (we have A record only for qname but qtype is AAAA) # 2. empty non-terminal if name in allnames: return gen_nxdomain_nodata(zone, query, nxdomain=False) # generate NXDOMAIN return gen_nxdomain_nodata(zone, query, nxdomain=True)
def cmd_rrdelete(args, r53): zone = _get_records(args, r53) name = dns.name.from_text(args.rr, zone.origin) node = zone.get_node(args.rr) if node: if len(node.rdatasets) > 1 and not args.type: rtypes = [ dns.rdatatype.to_text(rds.rdtype) for rds in node.rdatasets ] logging.warning( 'Ambigious record - several resource types for record %r found: %s' % (args.rr, ', '.join(rtypes))) else: rdataset = None for rds in node.rdatasets: if args.type == dns.rdatatype.to_text( rds.rdtype) or not args.type: if args.identifier is not None: for rdtype in rds.items: if hasattr( rdtype, 'identifier' ) and rdtype.identifier == args.identifier: rdataset = rds break else: rdataset = rds break if not rdataset: logging.warning('Record not found: %s, type: %s' % (args.rr, args.type)) return logging.info('Deleting %s %s...' % (args.rr, dns.rdatatype.to_text(rds.rdtype))) f = BindToR53Formatter() for xml in f.delete_record(zone, name, rdataset): ret = r53.change_rrsets(args.zone, xml) if args.wait: wait_for_sync(ret, r53) else: logging.info('Success') pprint(ret.ChangeResourceRecordSetsResponse) else: logging.warning('Record not found: %s' % args.rr)
def cmd_instances(args, r53): logging.info('Getting DNS records') zone = _get_records(args, r53) if args.off: filters = {} else: filters = {'instance-state-name': 'running'} if args.credentials: connections = _read_aws_cfg(args.credentials) else: connections = [boto.ec2.connect_to_region(region) for region in args.regions.split(',')] def get_instances(): for conn in connections: for r in conn.get_all_instances(filters=filters): for i in r.instances: yield i suffix = '.' + zone.origin.to_text().strip('.') creates = [] deletes = [] instances = get_instances() # limit to instances with a Name tag instances = (i for i in instances if i.tags.get('Name')) if args.match: instances = (i for i in instances if re.search(args.match, i.tags['Name'])) logging.info('Getting EC2 instances') instances_by_name = {} for inst in instances: name = inst.tags.get('Name') if not name: continue # strip domain suffix if present if name.endswith(suffix): name = name[0:-len(suffix)] name = dns.name.from_text(name, zone.origin) if name not in instances_by_name or inst.state == 'running': # on duplicate named instances, running takes priority instances_by_name[name] = inst if args.write_a_record: rtype = dns.rdatatype.A else: rtype = dns.rdatatype.CNAME for name, inst in instances_by_name.iteritems(): node = zone.get_node(name) if node and node.rdatasets and node.rdatasets[0].rdtype != rtype: # don't replace/update existing manually created records logging.warning("Not overwriting record for %s as it appears to have been manually created" % name) continue newvalue = None if inst.state == 'running': if inst.public_dns_name and not args.internal: newvalue = inst.ip_address if args.write_a_record else inst.public_dns_name else: newvalue = inst.private_ip_address if args.write_a_record else inst.private_dns_name elif args.off == 'delete': newvalue = None elif args.off and name not in creates: newvalue = args.off if node: if args.write_a_record: oldvalue = node.rdatasets[0].items[0].address else: oldvalue = node.rdatasets[0].items[0].target.strip('.') if oldvalue != newvalue: if newvalue: logging.info('Updating record for %s: %s -> %s' % (name, oldvalue, newvalue)) else: logging.info('Deleting record for %s: %s' % (name, oldvalue)) deletes.append((name, node.rdatasets[0])) else: logging.debug('Record %s unchanged' % name) continue else: logging.info('Creating record for %s: %s' % (name, newvalue)) if newvalue: if args.write_a_record: rd = _create_rdataset('A', args.ttl, [newvalue], None, None, None, None) else: rd = _create_rdataset('CNAME', args.ttl, [newvalue], None, None, None, None) creates.append((name, rd)) if not deletes and not creates: logging.info('No changes') return if args.dry_run: logging.info('Dry run - not making changes') return f = BindToR53Formatter() parts = f.replace_records(zone, creates, deletes) for xml in parts: ret = retry(r53.change_rrsets, args.zone, xml) if args.wait: wait_for_sync(ret, r53) else: logging.info('Success') pprint(ret.ChangeResourceRecordSetsResponse)
def cmd_instances(args, r53): logging.info('Getting DNS records') zone = _get_records(args, r53) if args.off: filters = {} else: filters = {'instance-state-name': 'running'} if args.credentials: connections = _read_aws_cfg(args.credentials) else: connections = [ boto.ec2.connect_to_region(region) for region in args.regions.split(',') ] def get_instances(): for conn in connections: for r in conn.get_all_instances(filters=filters): for i in r.instances: yield i suffix = '.' + zone.origin.to_text().strip('.') creates = [] deletes = [] instances = get_instances() # limit to instances with a Name tag instances = (i for i in instances if i.tags.get('Name')) if args.match: instances = (i for i in instances if re.search(args.match, i.tags['Name'])) logging.info('Getting EC2 instances') instances_by_name = {} for inst in instances: name = inst.tags.get('Name') if not name: continue # strip domain suffix if present if name.endswith(suffix): name = name[0:-len(suffix)] name = dns.name.from_text(name, zone.origin) if name not in instances_by_name or inst.state == 'running': # on duplicate named instances, running takes priority instances_by_name[name] = inst if args.write_a_record: rtype = dns.rdatatype.A else: rtype = dns.rdatatype.CNAME for name, inst in instances_by_name.iteritems(): node = zone.get_node(name) if node and node.rdatasets and node.rdatasets[0].rdtype != rtype: # don't replace/update existing manually created records logging.warning( "Not overwriting record for %s as it appears to have been manually created" % name) continue newvalue = None if inst.state == 'running': if inst.public_dns_name and not args.internal: newvalue = inst.ip_address if args.write_a_record else inst.public_dns_name else: newvalue = inst.private_ip_address if args.write_a_record else inst.private_dns_name elif args.off == 'delete': newvalue = None elif args.off and name not in creates: newvalue = args.off if node: if args.write_a_record: oldvalue = node.rdatasets[0].items[0].address else: oldvalue = node.rdatasets[0].items[0].target.strip('.') if oldvalue != newvalue: if newvalue: logging.info('Updating record for %s: %s -> %s' % (name, oldvalue, newvalue)) else: logging.info('Deleting record for %s: %s' % (name, oldvalue)) deletes.append((name, node.rdatasets[0])) else: logging.debug('Record %s unchanged' % name) continue else: logging.info('Creating record for %s: %s' % (name, newvalue)) if newvalue: if args.write_a_record: rd = _create_rdataset('A', args.ttl, [newvalue], None, None, None) else: rd = _create_rdataset('CNAME', args.ttl, [newvalue], None, None, None) creates.append((name, rd)) if not deletes and not creates: logging.info('No changes') return if args.dry_run: logging.info('Dry run - not making changes') return f = BindToR53Formatter() parts = f.replace_records(zone, creates, deletes) for xml in parts: ret = r53.change_rrsets(args.zone, xml) if args.wait: wait_for_sync(ret, r53) else: logging.info('Success') pprint(ret.ChangeResourceRecordSetsResponse)