master
Raw Download raw file
  1#!/usr/bin/python
  2
  3import ConfigParser
  4import requests
  5import optparse
  6import sys
  7
  8
  9# This is a command-line script to import and export DNS records for a
 10# single domain into or out of a hover account. Run it like so:
 11# ./bulkhover.py -u USERNAME -p PASSWORD (import|export) DOMAIN DNS_FILE
 12# or create a config file like this:
 13#
 14# [hover]
 15# username=USERNAME
 16# password=PASSWORD
 17#
 18# and run it like this:
 19# ./bulkhover.py -c PATH_TO_CONF (import|export) DOMAIN DNS_FILE
 20#
 21# The DNS file should have one record per line, in the format:
 22# {name} {type} {content}
 23#
 24# For example:
 25#
 26# www A 127.0.0.1
 27# @ MX 10 example.com
 28# 
 29# You can even copy the entire contents of one domain to another, like so:
 30# ./bulkhover.py -c CONF export example.com - | ./bulkhover.py -c CONF -f import other.com -
 31
 32
 33class HoverException(Exception):
 34    pass
 35
 36
 37class HoverAPI(object):
 38    def __init__(self, username, password):
 39        params = {"username": username, "password": password}
 40        r = requests.post("https://www.hover.com/api/login", params=params)
 41        if not r.ok or "hoverauth" not in r.cookies:
 42            raise HoverException(r)
 43        self.cookies = {"hoverauth": r.cookies["hoverauth"]}
 44    def call(self, method, resource, data=None):
 45        url = "https://www.hover.com/api/{0}".format(resource)
 46        r = requests.request(method, url, data=data, cookies=self.cookies)
 47        if not r.ok:
 48            raise HoverException(r)
 49        if r.content:
 50            body = r.json()
 51            if "succeeded" not in body or body["succeeded"] is not True:
 52                raise HoverException(body)
 53            return body
 54
 55
 56def import_dns(username, password, domain, filename, flush=False):
 57    try:
 58        client = HoverAPI(username, password)
 59    except HoverException as e:
 60        raise HoverException("Authentication failed")
 61    if flush:
 62        records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
 63        for record in records:
 64            client.call("delete", "dns/{0}".format(record["id"]))
 65            print "Deleted {name} {type} {content}".format(**record)
 66    
 67    domain_id = client.call("get", "domains/{0}".format(domain))["domain"]["id"]
 68    
 69    if filename == "-": filename = "/dev/stdin"
 70    with open(filename, "r") as f:
 71        for line in f:
 72            parts = line.strip().split(" ", 2)
 73            record = {"name": parts[0], "type": parts[1], "content": parts[2]}
 74            client.call("post", "domains/{0}/dns".format(domain), record)
 75            print "Created {name} {type} {content}".format(**record)
 76
 77def export_dns(username, password, domain, filename):
 78    try:
 79        client = HoverAPI(username, password)
 80    except HoverException as e:
 81        raise HoverException("Authentication failed")
 82    records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
 83    
 84    if filename == "-": filename = "/dev/stdout"
 85    with open(filename, "w") as f:
 86        for record in records:
 87            f.write("{name} {type} {content}\n".format(**record))
 88    
 89
 90def main():
 91    usage = "usage: %prog (-c CONF|-u USERNAME -p PASSWORD) (import|export) DOMAIN DNS_FILE"
 92    description = "Import or export DNS records for a single domain in a hover account."
 93    parser = optparse.OptionParser(usage=usage, description=description)
 94    parser.add_option("-c", "--conf", default=None, help="The conf file that contains your username and password")
 95    parser.add_option("-u", "--username", default=None, help="Your hover.com username")
 96    parser.add_option("-p", "--password", default=None, help="Your hover.com password")
 97    parser.add_option("-f", "--flush", default=False, action="store_true", help="Flush all DNS records associated with the domain before importing")
 98    (options, args) = parser.parse_args()
 99    
100    if len(args) < 3:
101        parser.error("You must specify an operation, a domain, and a file")
102    
103    operation, domain, filename = args
104    
105    if operation not in ("import", "export"):
106        parser.error("Invalid operation: {0} - Valid operations are import and export".format(operation))
107    
108    def get_conf(filename):
109        config = ConfigParser.ConfigParser()
110        config.read(filename)
111        items = dict(config.items("hover"))
112        return items["username"], items["password"]
113
114    if options.conf is None:
115        if not all((options.username, options.password)):
116            parser.error("You must specifiy either a conf file, or a username and password")
117        else:
118            username, password = options.username, options.password
119    else:
120        username, password = get_conf(options.conf)
121
122    if operation == "import":
123        import_dns(username, password, domain, filename, options.flush)
124    elif operation == "export":
125        export_dns(username, password, domain, filename)
126
127
128if __name__ == "__main__":
129    try:
130        main()
131    except HoverException as e:
132        print "Failed while importing DNS: {0}".format(e)
133        sys.exit(1)
134    sys.exit(0)