Wednesday, January 16, 2013

cloudbasher: a simple CloudStack API wrapper in <40 lines of code

Yes, there already exist some CloudStack API wrappers, but all that I've looked at come with dependencies not satisfied on a stock machine. I didn't want to have to propagate a Java environment or copy lots of Python source or install various CPAN dependencies to be able to call into a CloudStack instance.

So I wrote this script which has no external dependencies aside from its assumption that you either will set your API key, secret user key and CloudStack host URL in environment variables or hardcode them within the script. This script will work for any CloudStack API, e.g.,

 cloudbasher listTemplates templatefilter=featured
Note that argument values containing spaces or shell meta characters should be quoted so as not to violate cloudbasher's assumption that its arguments each constitute a parameter=value pair.
:
set -o noglob
CS_APIKEY=${CS_APIKEY-your_value_here}
CS_ADMIN_SECRET_KEY=${CS_ADMIN_SECRET_KEY-your_value_here}
CS_URL=${CS_URL-your_value_here}
CS_RESPONSE_TYPE=${CS_RESPONSE_TYPE-json}

command=$1; shift
t=$TMP/cloudbasher.$$
trap "rm $t" 0

Uri_escape()
{
        perl -MURI::Escape -ne 'chomp;print uri_escape($_),"\n"'   
}


(
echo apiKey=$CS_APIKEY 
echo command=$command
echo response=$CS_RESPONSE_TYPE
) > $t
while [ -n "$1" ]; do
        echo "$1" >> $t
done
# other implementations URI encode the individual values for the
# parameters, but since the command and parameter names don't 
# use reserved characters, we can just URI encode the entire 
# thing safely; the one hitch is that this converts
# the '=' to %3D, but we can undo that w/ a quick sed call:
cat $t | Uri_escape | sed -e 's/%3D/=/' > $t.uri_encoded
trap "rm $t.uri_encoded" 0
all=`sort < $t.uri_encoded | tr '\n' '&' | sed -e 's/&$//'`
downcased_all=`echo -n "$all" | tr 'A-Z' 'a-z'`
base64_all_hashed=`echo -n "$downcased_all" | openssl dgst -sha1 -binary -hmac "$CS_ADMIN_SECRET_KEY" | base64`
signature=`echo -n "$base64_all_hashed" | Uri_escape`
url="$CS_URL/client/api?$all&signature=$signature"
curl -L  --silent "$url"