2012-09-11 | fabric, python
based on fabric version 0.9.1 and python 2.6
Fabric is a command-line tool and library which can be used to streamline various system administration and application deployment tasks providing facilities to run commands locally and remotely (through SSH).
For remote commands, you can specify the hosts to run the commands on in
various ways. They may be added to the
env.hosts right within the "fabfile"
or may be given using the
-H HOSTS, --hosts=HOSTS command-line options.
You can even use tasks to populate your
env.hosts in the way you want it.
Refer to the documentation for some examples.
I am currently using fabric mostly for ad-hoc tasks on remote systems, such as placing files, adjusting configuration and restarting services. Most of the time I am using it like this:
fab -H host1,host2,host3 example_task
Recently, I needed to execute some task on several hosts given as a file though.
The file consisted of several lines, with a hostname on each line
I was used to using the
-H option and came up with something like this:
fab -H `cat hosts | perl -pe 's/\n/,/' | perl -pe 's/(.*),$/$1/'` example_task
This reads the file "hosts", joins the lines using a comma (as required by
and removes the eventually trailing comma. The output is then used to call fab
with a list of hosts.
While this works, it looks ugly and could become unwieldy in case the list of hosts needs extra preprocessing/filtering IMHO. I would rather rewrite it to something like this:
cat hosts | fab read_hosts example_task
What basically needs to be done is read the lines from STDIN and use them to populate the "env.hosts" list. Furthermore I want this logic to reside within a fabric task in order to keep the possibility to use other ways of populating the hosts-list too.
The corresponding code would be something like this:
import sys from fabric.api import env, run def read_hosts(): """ Reads hosts from sys.stdin line by line, expecting one host per line. """ env.hosts = [line.strip() for line in sys.stdin.readlines()] def example_task(): """ Example task which executes the uptime command remotely. """ run("uptime")
Looking at the
read_hosts() function, it is actually quite simple to add
support for reading from STDIN to a fabfile. From here it should be easy to add
more processing and filtering if appropriate such as ignoring empty and
def read_hosts_filtered(): """ Reads hosts from sys.stdin line by line, expecting one host per line and ignoring empty or commented lines. """ env.hosts =  for line in sys.stdin.readlines(): host = line.strip() if host and not host.startswith("#"): env.hosts.append(host)
Another example reading CSV files with the fields host and role:
import csv def read_hosts_csv(**kwargs): """ Reads hosts from sys.stdin as CSV, passing any additional parameters to the csv.reader() function. The CSV is expected to have two columns, one for the hostname and one for the role which are used to populate the fabric roledefs. """ reader = csv.reader(sys.stdin, **kwargs) for row in reader: host, role = row env.roledefs[role] = env.roledefs.get(role, ) + [host]
Which then can be used to read a standard CSV file like this:
cat hosts_csv | fab read_hosts_csv example_task:role=web
read_hosts_csv() task supports different CSV dialects too due to the
**kwargs being passed to the
$ cat hosts_csv_colon localhost:db localhost:web localhost:cache 127.0.0.1:web $ cat hosts_csv_colon | fab read_hosts_csv:delimiter=":" example_task:role=web
Further error checking and validation is left as an exercise for the reader.
Similarly it would be easy to support different formats too, such as JSON or XML. Just implement a matching reader function which translates the input to hosts or other fabric settings. Then you could for example read the configuration from a remote feed using curl and pipe it to fabric. Or you could use a command-line database client to retrieve host information.