Fabric

"Brief introduction"

About Me: Héctor Alvarez

twitter: @monobotblog

email: monobot.soft@gmail.com

Backend developer at cabaana

Up arrow Fabric

    Fabric is a high level interface for the command line

    Allows both the execution of commands in local and remote, in one or several servers. Before you ask, yes integrates perfectly with ssh

    To do so if you have ssh to connect without verification, then fabric will not ask either

    Due to these characteristics, makes it very versatile on executing long commands, packs of commands, maintenance tasks, deployments, etc...

Why Python instead of bash directly?

    You can create the scripts with only a few lines of code and obviouslly more legible than with bash.

    Less prone to errors than hand typing and less boring too if you ask me.

    The same stack of commands can be run on different servers.

Why automate?

why automate

installation

    Directly with pip for python3

    
    $ pip install fabric3
                                

    And for for python 2

    
    $ pip install fabric
                                

    Or with repositories (Ubuntu example)

    
    $ sudo apt-get install fabric
                                

Lets see code!!

In any fabric file you will define some enviroment variables and some functions.

simple example:


# -*- coding: utf-8 -*-
from fabric.api import run, env
from datetime import date

from os import path
today = date.today()

env.user = 'user'
env.hosts = ['9.9.9.9', '99.99.99.99', ]
env.DB_ENDPOINT = 'eu-west-1.rds.amazonaws.com'
env.DB_NAME = 'development'

def get_filepath():
    filename = '{date}.sql'.format(date=today.strftime('%Y%m%d'))
    return path.join('/', 'home', 'user', 'dbBackUp', '01daily', filename)

def db_backup():
    run('/usr/pgsql-9.5/bin/pg_dump -U dbuser -h {EP} {NM} > {fp}'
        .format(
            EP=env.DB_ENDPOINT,
            NM=env.DB_NAME,
            fp=get_filepath(),
            )
        )
                            
                        

API

There are many fabric commands, for complete reference see the documentation

The most used commands are:

local

run commands on your local machine

run

run commands on remote machines

* requires to know on what machine you will run the commands.

* requires a password.

sudo

Run the commands with superuser privileges

* requires a password

* works only remote

cd / lcd

Run the commands on specific directory

get

Copies remote files locally

put

copies local files to remote computer

Such a boring person, so much talking and no code anywhere!!!

You are right, how to work with those commands?

lets see local, run and cd in action

cd works inside a with statement, that is a confortable wrapper that allows the commands be executed in that specific directory.


from fabric.api import local, run, cd

def archive(foldername, filename):
    # we will see a more elegant way of doing this later
    verb = run
    if self.server_name == 'localhost':
        verb = local

    working_dir = '/home/monobot/sync/{foldername}'.format(
        foldername=foldername
    )
    with cd(working_dir):
        verb('mv {filename} archived/ -rf')
                            

Wait a moment, there are arguments in that function, How is that?

Yes, we can pass arguments from the command line, like so:


$ fab archive:downloads,myfile.zip
                            

env

Its a class that inherits from dict

env will be accessed by fabric to check its configuration

So there is where you define the enviromental variables and where you configure some specific behaviours of fabric

There are many env variables and its encouraged to take a look to the complete list

env also works as storage and access to our specific developing requirements.

The most important env enviromental variables are:

user / sudo_user

    user

    Where you define what user will run the command, fallbacks to the user that run the fabric script.

    sudo_user

    As you guessed the name of the sudo username (will be read by the sudo command).

host / hosts

    host

    Where the remote command will be run (run command).

    hosts

    Its a list of remote computers where the run command will be executed.

password / passwords

    password

    Where you store the password of the user that will run the command.

    passwords

    If each remote computer requires an specific username/password you can make use of this enviromental variable.

sudo_password / sudo_passwords

    sudo_password

    If that variable does not exist will fallback to password.

    sudo_passwords

    Needed if each of the servers needs specific password and user.

shell

    To define the shell we want to use defaults to bash.

At some specific point you could preefer to change an env variable inside an specific task


from fabric.api import settings, run

def changing_inside(path):
    with settings(warn_only=True):
        return run('ls -la')
                            

That warn_only will override the content of env.warn_only inside the with statement

Come on Héctor, show us some code!!!

Roger! How to manage different configurations:

Usually the different configurations are managed manipulating the env dictionary on run time.

eg:


from fabric.api import env, run, local

env.user = 'user'
env.foo = 'bar'

def localhost():
    env.run = local
    env.DB_ENDPOINT = 'eu-west-1.rds.amazonaws.com'
    env.DB_NAME = 'development'

def testing():
    hosts = ['8.8.8.8']

    env.run = run
    env.DB_ENDPOINT = 'eu-west-2.rds.amazonaws.com'
    env.DB_NAME = 'testing'
                            
                        

So we can change the env at runtime:


$ fab -f mi_fabfile.py development db_backup
                            
                            

$ fab -f mi_fabfile.py testing db_backup
                            
                            

How to run fabric

Fabric will allways look for a fabfile.py file in your current folder, if that doesn't exist you will have to specify what file you want to run specifically.


$ fab development db_backup
                            

$ fab -f mi_fabfile.py development db_backup
                            

Usually i like it more the second, but i will soon explain the power of the former

How to send arguments or keyword arguments

Like with regular python you can send arguments and keyword arguments, but using the command line


$ fab big_task:first_arg,second_arg,third=foo,fourth=bar
                            

As you can see only accepts string arguments, you could transform them inside

BTW nothing stops you of doing:


$ fab -f mi_fabfile.py development db_backup production db_backup
                            
                            

tasks

From inside the fabfiles you can specify what functions are the tasks and which ones arent

This is something relativelly new to fabric, so for backwards compatibility its not required to mark them, that is all functions are considered tasks, but if you use task inside a file those that are not marked arent considered tasks any more.

You can mark functions using the handy task decorator

OMG!, can you show some code without asking for it?


from fabric.decorators import task

@task
def foo():
    run('foo')

@task(alias='bar')
def my_bar():
    run('bar')
                            

Tasks can also be defined as classes that inherit from Task

IMHO its a bit of overkill, what makes fabric nice is been simple. But guess some people can make good use of it.


from fabric.api import run
from fabric.tasks import Task

class MyTask(Task):
    name = "my task"
    def run(self, environment, domain="whatever.com"):
        run("foo")

instance = MyTask()
                            

Last but not least

How to make a fabric complete module

you can create an structure like this:


fabfile/
    ├── __init__.py
    |   from configuration import inv, development, localhost, production
    |   from foo import foo_task
    |   from bar import bar_task
    |
    ├── configuration.py
    |   @task
    |   def localhost():
    |       inv.hosts = []
    |   @task
    |   def development():
    |       inv.hosts = []
    |   @task
    |   def production():
    |       inv.hosts = []
    |
    ├── foo.py
    |   @task
    |   def foo_bar():
    |
    └── bar.py
        @task
        def foo_bar():

                            

Thanks!

The complete slides will be always available at https://monobot.github.io/fabric_pyday2017/