linode-api-python-part-2

10 Jun 2008

Ataraxia Consulting


Some of us are CLI junkies, and would prefer their hands never have to leave the keyboard to perform tasks. With that in mind I did a quick work up of an interactive shell for the API. One of the goals I had for the bindings was to introduce as few dependencies outside the core python libraries as possible, so the interactive console logically inherits from code.InteractiveConsole, which is for the most part a modified version of HistoryConsole from this example. The example claims that the console (presumably when used with .interact()) would have tab complete and history enabled. Much to my dismay and wasted time debugging, this is not the case. The reality is the console is sufficiently sandboxed (my guess) as to not inherit what you may setup of readline by the time you need it (as far as I could tell). The solution I came up with was to force my will upon the new interactive shell before the user enters it.

console = LinodeConsole()
console.runcode('import readline,rlcompleter')
console.runcode('readline.parse_and_bind("tab: complete")')
console.runcode('readline.set_completer(rlcompleter.Completer().complete')
console.interact()

There. Now everything seems kosher, vars, modules, methods and what not will tab complete (finally). Geeze, why didn’t someone just tell me that in the first place?! Ok, but almost certainly you’re going to want to modify the completion routine to return only what you see fit:

class LinodeComplete(rlcompleter.Completer):
  def complete(self, text, state):
    result = rlcompleter.Completer.complete(self, text, state)
    if result and result.find('__') > -1:
      result = ''
    return result

This kludgy code looks for ‘private’ like results and prevents them from being returned when you press tab, when properly motivated I’m sure I’ll prettify that such that only the Api class is kludged with. There really isn’t much more to the code that should be interesting, I make sure that a method pp is available to you that wraps print simplejson.dumps and your default object for accessing the api is called ‘linode’. On startup the shell looks for the environment variable LINODE_API_KEY if it’s not set it will ask you to input your api key.

So what you end up with (after pressing tab twice):

>>> linode.
                             linode.domainResourceGet
linode.domainDelete          linode.domainResourceList
linode.domainGet             linode.domainResourceSave
linode.domainList            linode.domainSave
linode.domainResourceDelete  linode.linodeList
>>> linode.

and you can do normal python-y things:

>>> for domain in linode.domainList():
...   print '--------'+domain['DOMAIN']
...   for rr in linode.domainResourceList(DomainID=domain['DOMAINID']):
...     pp(rr)
... 
--------example.com
--------tj-beta.atxconsulting.com
{
  "DOMAINID": 14, 
  "NAME": "", 
  "RESOURCEID": 52, 
  "TARGET": "1.2.3.4", 
  "TYPE": "A", 
  "TTL_SEC": 0
}
{
  "DOMAINID": 14, 
  "NAME": "www", 
  "RESOURCEID": 53, 
  "TARGET": "1.2.3.4", 
  "TYPE": "A", 
  "TTL_SEC": 0
}
{
  "DOMAINID": 14, 
  "NAME": "mail", 
  "RESOURCEID": 54, 
  "TARGET": "1.2.3.4", 
  "TYPE": "A", 
  "TTL_SEC": 0
}

James Sinclair (irgeek in irc parlance) has been helping out, and in his branch you’ll find interesting additions including some potential caching and more high level operations like searching through your domains for records with specific parameters (e.g. “Type=’MX’, Priority=5) pretty neat stuff, I’m sure this kind of stuff will eventually make it upstream and into the interactive shell.

The next step for the shell script is to have a means to execute actions without firing up the interactive portion:

linode-shell --action=domainResourceSave --ResourceID=54 --Name=mail --Target=1.2.3.5 --Type=A

Ideally whatever the shell inherits from, base api or higher level, it should all just work the same and handle parameters properly. But if you do have the higher level one you could have a nice action domainResourceUpdate that could lookup by FQDN (which performs some aggregate of domainList and domainResourceList). Hell, we can probably have an rc file that you can stick custom defined functions in as well

and then … profit!

As usual the code is available from my git repo

git clone git://github.com/tjfontaine/linode-python.git

and viewable at

http://github.com/tjfontaine/linode-python