Remote control of hosts over SSH

Todo

Customize (hosts etc) all examples to match student VM setup

subprocess.Popen()

It is possible to control a local ssh session using subprocess.Popen() if no libraries are available. This is a super primative way to do things, and not recommended if you can avoid it.

Here is an example: [1]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import subprocess
import sys

HOST="www.example.org"
# Ports are handled in ~/.ssh/config since we use OpenSSH
COMMAND="uname -a"

ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],
                       shell=False,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE)
result = ssh.stdout.readlines()
if result == []:
    error = ssh.stderr.readlines()
    print >>sys.stderr, "ERROR: %s" % error
else:
    print result

Fabric

Fabric is a library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks.

It provides a basic suite of operations for executing local or remote shell commands (normally or via sudo) and uploading/downloading files, as well as auxiliary functionality such as prompting the running user for input, or aborting execution.

Basic Usage

Typical use involves creating a Python file named fabfile.py, containing one or more functions, then executing them via the fab command-line tool. Below is a small but complete fabfile.py containing a single task:

from fabric.api import run

def host_type():
    run('uname -s')

Once a task is defined, it may be run on one or more servers, like so:

(sysadmin)$ fab -H applebox,linuxbox host_type
[applebox] run: uname -s
[applebox] out: Darwin
[linuxbox] run: uname -s
[linuxbox] out: Linux

Done.
Disconnecting from localhost... done.
Disconnecting from linuxbox... done.

Task arguments

It’s often useful to pass runtime parameters into your tasks, just as you might during regular Python programming. Fabric has basic support for this using a shell-compatible notation: <task name>:<arg>,<kwarg>=<value>,.... It’s contrived, but let’s extend the above example to say hello to you personally: [2]

def hello(name="world"):
   print("Hello %s!" % name)

By default, calling fab hello will still behave as it did before; but now we can personalize it:

(sysadmin)$ fab hello:name=Jeff
Hello Jeff!

Done.

Those already used to programming in Python might have guessed that this invocation behaves exactly the same way:

(sysadmin)$ fab hello:Jeff
Hello Jeff!

Done.

For the time being, your argument values will always show up in Python as strings and may require a bit of string manipulation for complex types such as lists. Future versions may add a typecasting system to make this easier.

Library Usage

In addition to use via the fab tool, Fabric’s components may be imported into other Python code, providing a Pythonic interface to the SSH protocol suite at a higher level than that provided by e.g. the ssh library (which Fabric itself uses.) [3]

Consider the case where we want to collect average uptime from a list of hosts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from fabric import tasks

env.hosts = ['localhost', 'sunflower.heliotropic.us']
pattern = re.compile(r'up (\d+) days')

# No need to decorate this function with @task
def uptime():
    res = run('uptime')
    match = pattern.search(res)
    if match:
        days = int(match.group(1))
        env['uts'].append(days)

def main():
    env['uts'] = []
    tasks.execute(uptime)
    uts_list = env['uts']
    if not uts_list:
        return # Perhaps we should print a notice here?
    avg = sum(uts_list) / float(len(uts_list))
    print '-' * 80
    print 'Average uptime: %s days' % avg
    print '-' * 80

if __name__ == '__main__':
    main()

Footnotes

[1]https://gist.github.com/1284249
[2]http://docs.fabfile.org/en/1.4.2/tutorial.html#task-arguments
[3]http://stackoverflow.com/a/8166050