Friday, May 20, 2011

Neustar: BASHing UltraDNS

Last time I posted my python script that extracted all the methods from the PDF file and provided an interactive interface to the API here.

When I created the generic script, I had needed a way to view the CNAMEs and update them, so I had only needed the following methods: UDNS_UpdateCNAMERecords, UDNS_GetCNAMERecordsOfZone. Of course, since the associated Create and related ANAME methods were identical in nature, they were simple to test and, thus, my parse_xml techniques were based solely on these.

Yesterday, I had the need to create about a score of new zones as well as grant permissions to a few unprivileged users. As such, I had the chance to revisit the WebUI and, while adding a zone is not too taxing, granting permissions was like extracting wisdom teeth. First off, the tiny 15x15 (is it even that?) lock icon was not intuitive for me and it probably took half an hour to even figure out HOW to add permissions (I did not actually keep track of time so I may be exaggerating the length or brevity of time elapsed) and even when I did, I was rewarded with a complex collapsible folder browser method that reset itself after I went through all the check boxes on each individual user. After adding just one, I realized that I had to use the API and not subject myself to any more torture (not that the API is significantly less painful, mind you, but at least it provided a relief from the mundane repetition).

The UltraDNS_API.py contains a few examples at the bottom of how you would automate using python, but I decided to whip up a quick bash script for kicks. While doing so, I discovered that the output was not consumable by my existing parse_xml techniques, so I cloned the iterator to aide in the "unmatched" case, such as this one and will revisit it later on, if need be. I also removed my login credentials from the autogenerated UltraDNS_API.cf file and provided a way to pass them in via command line switch. The resulting UltraDNS_API.py can be found in the same GitHub location.

The BASH script itself, utilizes its own basename to create a users and zone lists, which it edits prior to asking for you username and password and then passes the parameters to the python script:


#!/bin/bash
# Using the UltraDNS_API.py to create multiple zones and their prmissions

SCR=${0##*/}
DIR=${0%/*}
[[ $DIR == '.' ]] && DIR=$PWD
cd $DIR

ZONE=${SCR%.sh}.zones
USER=${SCR%.sh}.users
TOOL=UltraDNS_API.py
CONF=${TOOL%.py}.cf

# Edit zones and users first
vi $ZONE $USER

# Ask for username and password
echo -n "Username: "; read username
echo -n "Password: "; read password

for zone in $(cat $ZONE); do
    python $TOOL -M UDNS_CreatePrimaryZone -c 'n' \
        -a "{'username': '$username', 'password': '$password'}" \
        -d -p "{'zonename': '$zone', 'forceimport': 'False'}"
    for user in $(cat $USER); do
        python $TOOL -M UDNS_GrantPermissionsToZoneForUser -c 'n' \
            -a "{'username': '$username', 'password': '$password'}" \
            -d -p "{'user': '$user', 'zone': '$zone',
            'allowcreate': 'True', 'allowread': 'True',
            'allowupdate': 'True', 'allowdelete': 'True',
            'denycreate': 'False', 'denyread': 'False',
            'denyupdate': 'False', 'denydelete': 'False'}"
    done
done

Worked like a charm! Of course, if you run the python script without parameters for the two methods utilized in the BASH script, you will be prompted with the last answers used.

Tuesday, May 3, 2011

Python API: UltraDNS

I needed to update a CNAME in our UltraDNS account last week and the WebUI was simply too much to bear given the amount of objects we have in a single domain.  Obviously it was not in alphabetical (or any) order. Nor does the search appear to work. So I finally decided to look into the XML-RPC API to see if there was a less painful way to handle it.

Like most people, I like to see what is available "out there" before I build something from scratch. I found Josh Rendek's pyUltraDNS, but it appears that it was built just to create A records. There is a method named 'generic_call' in the UDNS class, but it appears to be limited only to the CreateAName methodName (or any methodName that uses the exact same parameters). Additional methodNames could be incorporated if you duplicate the call and retrieve methods but, since creating one-offs of each methodName goes against my philosophies of automation and scale, I decided to keep looking. Though it is rather irksome that the uber-chic pyUltraDNS name is associated with a python module that does not cover ALL the UltraDNS methods, the script did teach me a little bit about retrieving data from an OpenSSL socket, so it was worth investigating.

I decided to follow the same logic as my ongoing AWS script, which was to make it interactive --- if the /user does not pass parameters to the class methods, then the script would prompt you each step of the way. Halfway through writing my interactive script, I also discovered Tim Bunce's UltraDNS perl module at CPAN, which does a really cool job on extracting the Methods from the PDF documentation. So, using pyPDF to avoid the need for the user to deal with the "save/export as plain text" step, I added something similar. In addition, I also have the script download the PDF file from UltraDNS if it is not found in the same directory as the script.

All the methodNames configurations are based off of the NUS_API_XML.pdf documentation since I do not have the need, nor the resources to test every single methodName. Since I was mostly dealing with the Create and Update CNAME methods, I added 2 "parsers" that will strip out the useful information from the XML responses (I am normally not a fan of XML, but UltraDNS responses make me less of a fan). If you add any parsers or want me to add additional parsers, please send me a copy of the XML response and I will try to incorporate it in. The same goes for any bugs, since the XML response will assist in helping me rework the script without having to reproduce the problem. Hope you find the script useful, download it from my github repository:

     UltraDNS_API.py

P.S. I have also NOT tested it on all versions of python, only 2.6, which is what I tried to constrain my MacPorts installations to for the sake of consistency.  Feel free to comment if any of the methodNames do not work correctly.