Tab Completion for EC2 Domains
Little-known unix command: complete
At least for the bash shell, this command controls the programmable tab-completion, allowing you to configure certain programs so their tab-completion only matches files, dirs, some arbitrary set of terms, etc.
A common example is to set up “svn” so that bash knows its commands, and tab-completes them for you. A very simplistic version would be like the following:
complete -W "add blame checkout commit copy delete diff log revert status switch update" svn
Granted, for brevity I left out a lot of commands, including their shortened versions, etc. However it turns out it’s pretty cool, because then you can type “svn u” and hit tab, and suddenly it’s got “svn update” in there for you, rather than trying to match filenames.
Of course, that brings up an interesting issue — you probably want to match a directory or filename for the second argument… And if you want to get crazy, complete lets you do that by passing it a shell function that returns possible completions for the current situation. There’s a whole package out there with specialized functions for common unix commands, which you can find at http://www.caliban.org/bash/.
However, this blog post is about abusing the tab-completion to match certain domain names. Existing EC2 instances, that is. At work we use DynDns to provide a dynamic domain name to each of our EC2 instances, and of course we often want to ssh into them by the dynamic names rather than the default Amazon ones, which look like “ec2-67-123-123-123.compute-1.amazonaws.com”. For that, first of all, we need to figure out which domain names are currently being used by instances.
That’s done with a combination of parsing IP addresses out of the data returned from ec2-describe-instances, and looking up the current IP addresses of each of our dynamic domain names. The ec2 IP parsing looks kinda’ like this (in Ruby):
running_ips = []
`ec2-describe-instances`.each_line { |line|
running_ips << $1.gsub('-','.') if line =~ /\bec2-([0-9-]+)/
}
The domain lookup is only marginally more complicated…
domains = ("001".."010").collect { |n| "server-#{n}.example.com" }
domain_lookup = {}
`dig #{domains.join(' ')}`.each_line { |line|
domain_lookup[$2] = $1 if line =~ /^\s*(server-\d+\.example\.com).+?\s+IN\s+A\s+([\d.]+)/
}
And then we can get a list of currently running domains by pulling the relevant slice of IPs from domain_lookup, and excluding any nils:
running_domains = domain_lookup.values_at(*running_ips).compact
Printing that out looks something like this:
>> puts running_domains.join(' ')
server-001.example.com server-002.example.com server-004.example.com
So … Coming back to the bash completion, we could put all that in a script and then do something of this sort:
complete -W '$(ruby ec2_domain_lookup.rb)' ssh
But of course, that’d limit ssh’s tab-completion to only these servers, and would leave out useful things like the username, etc. Helping that, however is the fact that I’ve already got two aliases in my profile for working with ec2 boxes:
alias ec2='ssh -i ~/.ec2/ssh-keypair -o StrictHostKeyChecking=no -o ServerAliveInterval=240 -l root'
alias escp='scp -i ~/.ec2/ssh-keypair -o User=root'
These let me connect to an ec2 box with “ec2 hostname” or scp files to it with “escp file hostname:” (assuming you get the path to your keypair correct, etc.)
So coming back to the tab-completion, we finally get this:
complete -W '$(ruby ec2_domain_lookup.rb)' ec2
And then since the escp command should also include directories and filenames in its completion, we use the following for it:
complete -W '$(ruby ec2_domain_lookup.rb)' -d -f escp
So that’s the basics… The next step is to set it up so you always have those available in your shell, and don’t have to wait 10-15 seconds for all the IP/domain lookups to complete, as it generates the completion lists. So I have a cron job that periodically runs the lookup script, and outputs a file with the two complete commands in it. My .bash_login script just sources the file like so:
. ~/.ec2_tab_completion_commands
And it’s always available without delays. Enjoy. :)