Remote Ruby/Bash Exec using SSH and Heredocs

There are a few bash shell tricks I’ve learned lately that have all come together to assist in helping me write a script that can remotely execute ruby code on a server.  The first trick is heredocs in bash:


echo << +++
This is a heredoc.
More than one line is just fine.
+++

The use of << is the start of a heredoc. It means that you want to include a multi-line string as an argument to the command (‘echo’), beginning and ending with a specific string. Any string can go after the angle brackets, so I chose “+++” since it’s unlikely to appear in my quoted text. The “+++” at the beginning of the 3rd line indicates the end of the heredoc (block of text). It has to be at the beginning.

Next up is literal heredocs:


echo << '+++'
This is a $literal heredoc!
The $dollar signs are not treated as variables,
and the bang (!) is not special.

Strangely, by simply quoting the heredoc start marker, the heredoc becomes a literal one: there will be no string interpolation or variable substitution or any other manipulation that bash normally does with strings. This is really handy when dealing with code because dollar signs and other special characters are more common.

While not strictly necessary (unless you need variable substitution, see below), I also combine with the ‘tee’ operator to make piping to other commands for preprocessing easier:


tee << '+++' | echo
This is a heredoc again.
+++

The 'tee' command simply takes standard input and sends it to standard output, no buffering. So now that I have a literal heredoc being sent to echo, what if I want to explicitly substitute certain "variables" in my text? I use sed to do the replacement:


tee << '+++' | sed "s/MYVAR/$MYVAR/g" | echo
puts "You said MYVAR!"
+++

So this might seem like a strange thing to do... I'm using a literal heredoc to AVOID variable substitution, and then I'm using sed to do variable substitution! The reason for this is that I want more control over what is substituted. When I have a large chunk of ruby code that I want to execute, I don't want to have to worry about escaping dollar signs and bang characters. Instead, I use a literal heredoc and then explicitly replace special strings with their substitutions using sed. Safe and effective.

Finally, instead of piping to echo, we can simply pipe to ssh and add 'ruby' as the last parameter to ssh:


tee << '+++' | sed "s/MYVAR/$MYVAR/g" | ssh root@myserver ruby
puts "You said MYVAR!"
+++

Alternatively, you could pipe to bash. Let's do that and send a heredoc within the heredoc!


tee << '+++' | \
sed "s/SSH_HOST/$SSH_HOST/g" | \
sed "s/ADMIN_PASSWORD/$ADMIN_PASSWORD/g" | \
ssh $SSH_LOGIN@$SSH_HOST /bin/bash

tee << '---' |debconf-set-selections
# URL of Chef Server (e.g., http://chef.example.com:4000):
chef	chef/chef_server_url	string	http://chef.SSH_HOST:4000
# New password for the 'admin' user in the Chef Server WebUI:
chef-server-webui	chef-server-webui/admin_password	password	ADMIN_PASSWORD
# New password for the 'chef' AMQP user in the RabbitMQ vhost "/chef":
chef-solr	chef-solr/amqp_password	password	ADMIN_PASSWORD
# Upgrading from 1.5.4 and below.
rabbitmq-server	rabbitmq-server/upgrade_previous	note
---

  # Install chef
  aptitude -y install chef-server

+++

This last example is one that I've actually used--it remotely installs chef-server (without asking for prompts) on a Ubuntu box (Lucid).

blog comments powered by Disqus