Running SSH commands on multiple servers At the company I work at, we have no real way to quickly execute shell commands on all of our customers systems, which often results in what we call Tiny Tim tasks, where the list of all of our systems is split between the 5 devs we have, and we simply go through one-by-one, SSHing into each one and executing the necessary commands.

To streamline this process, I created a script in Python3 using Paramiko for SSH.
The script takes the path to a private OpenSSH key from command-line args, reads a list of hosts from a file called hosts.txt (lines in this file can be commented by prepending them with --), and a list of shell commands from a file called cmds.txt. Due to the way we set up our customers' systems, each domain has 2 servers, so the script loops over each host in the list twice, prepending disc1. and disc2. respectively to send the commands to both servers.

from paramiko.client import AutoAddPolicy
from paramiko.client import SSHClient
import sys
from sys import argv

if len(argv) &lt; 2:
    print("Invalid args!")
    print("Usage: python3 run.py &lt;private ssh key filepath&gt;")
    exit()

with open('cmds.txt') as c:
    cmds = c.readlines()

with open('hosts.txt') as f:
    lines = f.readlines()

for line in lines:
    for i in range(1, 3):
        line = line.strip()
        host = "disc%s.%s" % (i, line)
        if line.startswith('--'):
            print("Host %s began with --, skipping" % line)
            continue
        client = SSHClient()
        client.set_missing_host_key_policy(AutoAddPolicy)
        client.load_system_host_keys()
        try:
            print("[%s]&gt; Connecting as root on port 22..." % host)
            client.connect(host, 22, 'root', None, None, argv[1])
        except KeyboardInterrupt:
            break
        except Exception as err:
            print("[%s]&gt; Connection failed: %s" % (host, err))
            continue
        for c in cmds:
            print("[%s]&gt; Executing %s..." % (host, c.strip()))
            stdin, stdout, stderr = client.exec_command(c)
            print(stdout.read().decode('utf-8'))
            print(stderr.read().decode('utf-8'))
        client.close()
        print("[%s]&gt; Done" % host)


I ran it through PEP8 and surprisingly didn't get any errors or warnings, but it's still quite messy and I'd appreciate feedback.
Looks like Fabric is what you need.

Fabric is a pretty simple deploy tool (comparing to Ansible/Puppet/etc), that lets you execute simple shell commands over SSH (Paramiko inside).  

Features you will get from it:


You can specify SSH key path using command line:

fab command -i /path/to/key.pem


or parse it from command line:

import sys
from fabric.api import *
...
env.key_filename = sys.argv[2] or '/path/to/keyfile.pem'

Describe local/remote tasks in pythonic way:
    from fabric.api import *

def local_test():
    local('nosetests .')

def remote_pull():
    run('git pull')


And chain/group/do-anything-you want to them.     
You can describe host list:

hosts = [&lt;your_hosts_list&gt;]
env.hosts = ['disc{}.{}'.format(host, index) for host in hosts for index in xrange(1,3)]


or you can describe roles:

env.roledefs['group1'] = ['host.name.com']
env.roledefs['group2'] = ['host1.name.com', 'host2.name.com']


and use this roles/hosts in your tasks configuration then:

@roles('group1')
@runs_once
def add_tag(version):
    run('git tag -f {}'.format(version))



And many more features out of the box!