aheber
5/15/2013 - 5:53 PM

Example code to use the (unofficial, unsupported, undocumented) hover.com DNS API.

Example code to use the (unofficial, unsupported, undocumented) hover.com DNS API.

#!/bin/bash

[[ $# -lt 3 ]] && echo "Usage: $0 USERNAME PASSWORD DNS_ID"

USERNAME=${1}
PASSWORD=${2}
DNS_ID=${3}

# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/

# (replace "yourdomain.com" with your actual domain, and look for the record
# you want to change. The ID looks like: dns1234567)

IP=$(curl "http://ifconfig.me/ip" -s)

curl "https://www.hover.com/api/dns/${DNS_ID}" \
     -X PUT            \
     -d "content=${IP}" \
     -s                \
     -b <(curl "https://www.hover.com/api/login" \
               -X POST                   \
               -G                        \
               -d "username=${USERNAME}" \
               -d "password=${PASSWORD}" \
               -s                        \
               -o /dev/null              \
               -c -)

echo

#!/usr/bin/env python
"""
dynhover.py 1.2

This tool will update an A record for given (sub)domain in your hover.com
with your IP, or an IP that you specify

Usage:
  dynhover.py (-c <conf> | -u <user> -p <password>) <domain>
  dynhover.py (-h | --help)
  dynhover.py --version

Options:
  -h --help             Show this screen
  --version             Show version
  -c --conf=<conf>      Path to conf
  -u --username=<user>  Your hover username
  -p --password=<pass>  Your hover password
  -i --ip=<ip>          An IP to set (auto-detected by default)
"""

import ConfigParser
import docopt
import requests
import sys


class HoverException(Exception):
    pass


class HoverAPI(object):
    def __init__(self, username, password):
        params = {"username": username, "password": password}
        r = requests.post("https://www.hover.com/api/login", params=params)
        if not r.ok or "hoverauth" not in r.cookies:
            raise HoverException(r)
        self.cookies = {"hoverauth": r.cookies["hoverauth"]}
    def call(self, method, resource, data=None):
        url = "https://www.hover.com/api/{0}".format(resource)
        r = requests.request(method, url, data=data, cookies=self.cookies)
        if not r.ok:
            raise HoverException(r)
        if r.content:
            body = r.json()
            if "succeeded" not in body or body["succeeded"] is not True:
                raise HoverException(body)
            return body


def get_public_ip():
    return requests.get("http://ifconfig.me/ip").content


def update_dns(username, password, fqdn, ip):
    try:
        client = HoverAPI(username, password)
    except HoverException as e:
        raise HoverException("Authentication failed")
    dns = client.call("get", "dns")
    dns_id = None
    for domain in dns["domains"]:
        if fqdn == domain["domain_name"]:
            fqdn = "@.{domain_name}".format(**domain)
        for entry in domain["entries"]:
            if entry["type"] != "A": continue
            if "{0}.{1}".format(entry["name"], domain["domain_name"]) == fqdn:
                dns_id = entry["id"]
                break
    if dns_id is None:
        raise HoverException("No DNS record found for {0}".format(fqdn))

    response = client.call("put", "dns/{0}".format(dns_id), {"content": my_ip})
    
    if "succeeded" not in response or response["succeeded"] is not True:
        raise HoverException(response)
    

def main(args):
    if args["--username"]:
        username, password = args["--username"], args["--password"]
    else:
        config = ConfigParser.ConfigParser()
        config.read(args["--conf"])
        items = dict(config.items("hover"))
        username, password = items["username"], items["password"]

    domain = args["<domain>"]
    ip = args["--ip"] or get_public_ip()
    
    try:
        update_dns(username, password, domain, ip)
    except HoverException as e:
        print "Unable to update DNS: {0}".format(e)
        return 1
    
    return 0


if __name__ == "__main__":
    version = __doc__.strip().split("\n")[0]
    args = docopt.docopt(__doc__, version=version)
    status = main(args)
    sys.exit(status)
#!/usr/bin/python
"""
bulkhover.py 1.1

This is a command-line script to import and export DNS records for a single
domain into or out of a hover account.

Usage:
  bulkhover.py [options] (import|export) <domain> <dnsfile>
  bulkhover.py (-h | --help)
  bulkhover.py --version

Options:
  -h --help             Show this screen
  --version             Show version
  -c --conf=<conf>      Path to conf
  -u --username=<user>  Your hover username
  -p --password=<pass>  Your hover password
  -f --flush            Delete all existing records before importing

Examples:
  The DNS file should have one record per line, in the format:
  {name} {type} {content}

  For example:

  www A 127.0.0.1
  @ MX 10 example.com

  
  Since the script output is in the same format as its input, you can use shell
  pipelines to do complex operations.

  Copy all DNS records from one domain to another:
    bulkhover.py -c my.conf export example.com - | ./bulkhover.py -c my.conf -f import other.com -

  Copy only MX records from one domain to another:
    ./bulkhover.py -c my.conf export foo.com - | awk '$2 == "MX" {print $0}' | ./bulkhover.py -c my.conf import bar.com -


  To avoid passing your username and password in the command-line, you can use
  a conf file that contains them instead:

  [hover]
  username=YOUR_USERNAME
  password=YOUR_PASSWORD
"""

import ConfigParser
import docopt
import requests
import sys


class HoverException(Exception):
    pass


class HoverAPI(object):
    def __init__(self, username, password):
        params = {"username": username, "password": password}
        r = requests.post("https://www.hover.com/api/login", params=params)
        if not r.ok or "hoverauth" not in r.cookies:
            raise HoverException(r)
        self.cookies = {"hoverauth": r.cookies["hoverauth"]}
    def call(self, method, resource, data=None):
        url = "https://www.hover.com/api/{0}".format(resource)
        r = requests.request(method, url, data=data, cookies=self.cookies)
        if not r.ok:
            raise HoverException(r)
        if r.content:
            body = r.json()
            if "succeeded" not in body or body["succeeded"] is not True:
                raise HoverException(body)
            return body


def import_dns(username, password, domain, filename, flush=False):
    try:
        client = HoverAPI(username, password)
    except HoverException as e:
        raise HoverException("Authentication failed")
    if flush:
        records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
        for record in records:
            client.call("delete", "dns/{0}".format(record["id"]))
            print "Deleted {name} {type} {content}".format(**record)
    
    domain_id = client.call("get", "domains/{0}".format(domain))["domain"]["id"]
    
    if filename == "-": filename = "/dev/stdin"
    with open(filename, "r") as f:
        for line in f:
            parts = line.strip().split(" ", 2)
            record = {"name": parts[0], "type": parts[1], "content": parts[2]}
            client.call("post", "domains/{0}/dns".format(domain), record)
            print "Created {name} {type} {content}".format(**record)


def export_dns(username, password, domain, filename):
    try:
        client = HoverAPI(username, password)
    except HoverException as e:
        raise HoverException("Authentication failed")
    records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"]
    
    if filename == "-": filename = "/dev/stdout"
    with open(filename, "w") as f:
        for record in records:
            f.write("{name} {type} {content}\n".format(**record))
    

def main(args):
    def get_conf(filename):
        config = ConfigParser.ConfigParser()
        config.read(filename)
        items = dict(config.items("hover"))
        return items["username"], items["password"]

    if args["--conf"] is None:
        if not all((args["--username"], args["--password"])):
            print("You must specifiy either a conf file, or a username and password")
            return 1
        else:
            username, password = args["--username"], args["--password"]
    else:
        username, password = get_conf(args["--conf"])

    try:
        if args["import"]:
            import_dns(username, password, args["<domain>"], args["<dnsfile>"], args["--flush"])
        elif args["export"]:
            export_dns(username, password, args["<domain>"], args["<dnsfile>"])
    except HoverException as e:
        print "Unable to update DNS: {0}".format(e)
        return 1


if __name__ == "__main__":
    version = __doc__.strip().split("\n")[0]
    args = docopt.docopt(__doc__, version=version)
    status = main(args)
    sys.exit(status)
import requests

class HoverException(Exception):
    pass


class HoverAPI(object):
    def __init__(self, username, password):
        params = {"username": username, "password": password}
        r = requests.post("https://www.hover.com/api/login", params=params)
        if not r.ok or "hoverauth" not in r.cookies:
            raise HoverException(r)
        self.cookies = {"hoverauth": r.cookies["hoverauth"]}
    def call(self, method, resource, data=None):
        url = "https://www.hover.com/api/{0}".format(resource)
        r = requests.request(method, url, data=data, cookies=self.cookies)
        if not r.ok:
            raise HoverException(r)
        if r.content:
            body = r.json()
            if "succeeded" not in body or body["succeeded"] is not True:
                raise HoverException(body)
            return body


# connect to the API using your account
client = HoverAPI("myusername", "mypassword")

# get details of a domains without DNS records
client.call("get", "domains")

# get all domains and DNS records
client.call("get", "dns")


# notice the "id" field of domains in response to the above calls - that's needed
# to address the domains individually, like so:

# get details of a specific domain without DNS records
client.call("get", "domains/dom123456")

# get DNS records of a specific domain:
client.call("get", "domains/dom123456/dns")

# create a new A record:
record = {"name": "mysubdomain", "type": "A", "content": "127.0.0.1"}
client.call("post", "domains/dom123456/dns", record)

# create a new SRV record
# note that content is "{priority} {weight} {port} {target}"
record = {"name": "mysubdomain", "type": "SRV", "content": "10 10 123 __service"}
client.call("post", "domains/dom123456/dns", record)

# create a new MX record
# note that content is "{priority} {host}"
record = {"name": "mysubdomain", "type": "MX", "content": "10 mail"}
client.call("post", "domains/dom123456/dns", record)


# notice the "id" field of DNS records in the above calls - that's
#  needed to address the DNS records individually, like so:

# update an existing DNS record
client.call("put", "dns/dns1234567", {"content": "127.0.0.1"})

# delete a DNS record:
client.call("delete", "dns/dns1234567")