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 -H
)
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
commented lines:
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
The read_hosts_csv()
task supports different CSV dialects too due to the
**kwargs
being passed to the csv.reader()
function:
$ 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.