SSH Scripting with Fabric and Python

Reading and writing files is a basic task that most software applications need to do. You will also find that you sometimes need to read and write files to a remote machine or perhaps run commands on a remote machine as well. Python is well-suited for this type of activity using tools such as Paramiko. However, in this tutorial, you will spend some time learning how to use a different package called Fabric.

Fabric is a high-level Python package designed especially to execute shell commands remotely over SSH and then yielding useful Python objects in return. This article will focus on the latest version of Fabric, which is 3.2.2 at the time of writing.

Getting Fabric

Fabric is a third party package. That means you need to install Fabric to be able to use it. Fortunately, you can use Python’s pip tool to do so.

Open up a terminal application and run the following command:

python -m pip install fabric

If you don’t want to clutter up your main Python environment, then you should use a Python virtual environment. You can learn more about those in An Intro to Python Virtual Environments.

Once you are finished installing Fabric, you can move on to learning how to use it!

Connecting to the Server with Fabric

The Fabric documentation uses the following as a super simple example of running a command on a remote machine using SSH:

from fabric import Connection
result = Connection('web1.example.com').run('uname -s', hide=True)

You only need two lines of code to start running commands on a remote machine. But what if the remote machine requires credntials?

In that case, you need to create a Config object and update your instantiation of Connection like this:

from fabric import Connection, Config

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}})
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config)

If you have a machine that uses an SSH key pair, you can use this alternate connect_kwargs dictionary:

connect_kwargs={
        "key_filename": "/home/myuser/.ssh/private.key",
    }

Then simply update the call to Connection and you’re good to go.

Running Commands with Fabric

Now that you have the knowledge needed to connect to the remote machine, you probably want to start running more complex commands. Here is an example of running a ping command:

from fabric import Connection, Config

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}}) 
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config)

conn.run("ping -c 2 www.google.com")

What if you want to be able to use the super user (i.e. root) when running a command? Fabric makes that easy by using the sudo() method:

from fabric import Connection, Config

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}}) 
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config)

conn.sudo("systemctl restart nfs-kernel-server")

Transferring Files with Fabric

If you want to download a file from a remote machine, Fabric makes this rudimentary task even easier. Here’s how to do it:

from fabric import Connection, Config 

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}})  
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config)

conn.get("remote_file_path", "local_file_path")

Note that all you need to do is call get() while specifying the location of the remote file that you want for the first argument and the local path for where you want to download the file as the second argument.

Sending a file to the remote server is done using the put() method:

from fabric import Connection, Config 

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}})  
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config)

conn.put("local_file_path", "remote_file_path")

You reverse the arguments for put() versus get(). The local path is passed in first, followed by the remote location that you want to upload to.

But what if you want to upload to a restricted area of the file system on the remote machine? You know, like to the etc folder or any of the other root-owned folders?

Fabric doesn’t have a built-in way to do that. Instead you use a two-step process:

  • Upload the file to a directory that your user owns
  • Then use sudo() to move the file to the restricted location

Here’s an example:

from fabric import Connection, Config 

config = Config(overrides={"sudo": {"password": "MyAmazingPassword123"}})  
conn = Connection("mike@10.10.166.128:22", connect_kwargs={"password": "MyAmazingPassword123!"}, config=config) 

# Send the file to a user directory
conn.put("local_file_path", "remote_file_path")

# Use sudo to move that file to a root location
conn.sudo("mv remote_file_path root_location_path")

Wrapping Up

Fabric is a great tool that greatly simplifies running SSH commands on remote computers. If you know how to use common Linux commands or know Python well, you can do lots of different things. For example, you could even upload a Python script to the remote server, run it, and then remove the file. At that point, you could do just about anything that you needed to.

Give Fabric a try and see what you can do!