def refresh_zone_history(request): """Update DNS zone file history with the current live zones. This API call can be used to notify Jinx of an update made directly to a zone. """ acquire_lock("dns") hg = init_repository(jinx_global_settings['DNS_REPOSITORY_URL']) zone_list = hg.get_file("zone-list") or "" my_zones = [dns.name.from_text(zone) for zone in zone_list.splitlines()] _store_zone_history(my_zones, hg) hg.commit("jinx: refreshed zones from master nameserver (requested by %s)" % request.META['REMOTE_USER']) hg.push() return True
def update_dns_records(request, add_rrsets, delete_rrsets, modify_rrsets, comments, description, simulate=False): """Perform a DNS update. This API call provides the ability to modify DNS records in one or more zones. Updates are processed on "resource record sets" (RRsets), which are groups of records with the same name and type. For example, consider this zone file snippet: foo 300 IN A 1.1.1.1 300 IN A 1.1.1.2 IN TXT "foo round-robin" This comprises _two_ RRsets: one for the two A records together, and a second for the TXT record. Operations must be requested on entire RRsets. For example, if I want to add another A record to the round-robin above, I'd need to specify a modification of the existing RRset, replacing it with a set containing all three records. Similarly, if I want to change one IP in the round- robin, I request to replace the entire RRset with a modified one. I'm not allowed to add or modify just one record of an RRset. When deleting an RRset, the client must include a complete definition of the existing RRset. Jinx will verify that the RRset exists and contains exactly the data the client expects. If it does not, the update is rejected because Jinx presumes another client has modified the zone while this client was preparing its modification. RRset modifications are simply processed as a delete and an add. Just as for a delete, the current data in the RRset must be completely specified, and Jinx will reject the modification if the current data in DNS does not match. The data formats for the add_rrsets and deletE_rrsets are as follows: [ ["foo.lindenlab.com.", 300, "IN", "A", [ '1.1.1.1', '1.1.1.2' ] ] ["foo.lindenlab.com.", 3600, "IN", "TXT", [ "foo round-robin" ] ] ] The argument is a list of RRsets. Each RRset is a list, containing the domain name (fully-qualified with trailing dot), TTL, class, type, and values. The values are always a list, even for single records. This allows for round-robins to be specified. modify_rrsets is a bit different, to allow for both the old and new RRset data to be specified. For example, to modify the "foo" RRset to change 1.1.1.2 to 1.1.1.3, the client would pass this: [ ["foo.lindenlab.com.", [300, "IN", "A", [ '1.1.1.1', '1.1.1.2' ] ], [300, "IN", "A", [ '1.1.1.1', '1.1.1.3' ] ] ] ] The name is specified only once because an RRset must always be replaced with another of the same name -- otherwise it's a deletion and an addition of an unrelated RRset. The old data comes next, enclosed in a list. The new data comes after that, also enclosed in a list. Assuming all of the client's notions of the current state of DNS pan out, Jinx will update the DNS zone as requested. It will also record the new comments and store the entire change in its version tracking backend for later review. Arguments: add_rrsets -- A list of RRsets to add. delete_rrsets -- A list of RRsets to delete. modify_rrsets -- A list of RRsets to modify (delete then add). comments -- A dict of record comments to apply. Keys are the FQDN (including trailing dot) of the record set to attach the comment to. Each value is a string or None (indicating that any existing comment should be deleted). description -- A textual description of the change (commit message). simulate -- If True, updates will not be sent to the DNS server, history will not be updated, and comments will not be updated. Use this to just run checks on your updates. Returns: The number of zones updated. Exceptions Raised: JinxInvalidStateError -- A condition or assumption of the client was invalid. For example, the client tried to delete a record that did not exist, or modify a record that had a value different from what the client expected. This exception can also be raised if certain DNS parse errors are encountered, such as an attempt to create a CNAME record and an A record with the same name (which is disallowed by the DNS RFC). This can happen due to user error or if two users are preparing DNS modifications at around the same time. JinxInvalidRequestError -- Something was incorrect about the records passed by the client (e.g. a TTL that's not a number). See exception text for a description. """ # try: acquire_lock("dns") hg = init_repository(jinx_global_settings['DNS_REPOSITORY_URL']) zone_list = hg.get_file("zone-list") or "" my_zones = [dns.name.from_text(zone) for zone in zone_list.splitlines()] name_re = re.compile('^[*0-9a-zA-Z_][-*0-9a-zA-Z_.]*\.') # Convert arguments into dnspython RRsets: add_rrsets, delete_rrsets = _parse_rrsets(add_rrsets, delete_rrsets, modify_rrsets) # Make sure every updated record is for a zone I handle. _check_record_names(add_rrsets + delete_rrsets, my_zones) # Make sure the comment changes are for records in zones I handle. _check_comment_names(comments, my_zones) updates = _prepare_dns_updates(add_rrsets, delete_rrsets, my_zones) _check_zones(updates.keys(), add_rrsets, delete_rrsets) if not simulate: _send_updates(updates.values()) _store_zone_history(updates.keys(), hg) _update_comments(comments, hg) hg.commit("%s: %s" % (request.META['REMOTE_USER'], description)) hg.push() # The lock on "dns" will be released automatically when the transaction is # committed. return len(updates)