About Me: Héctor Alvarez
twitter: @monobotblog
email: monobot.soft@gmail.com
Backend developer at cabaanaFabric 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...
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.
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
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(),
)
)
There are many fabric commands, for complete reference see the documentation
The most used commands are:
run commands on your local machine
run commands on remote machines
* requires to know on what machine you will run the commands.
* requires a password.
Run the commands with superuser privileges
* requires a password
* works only remote
Run the commands on specific directory
Copies remote files locally
copies local files to remote computer
You are right, how to work with those commands?
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')
Yes, we can pass arguments from the command line, like so:
$ fab archive:downloads,myfile.zip
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
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
Where the remote command will be run (run command).
hosts
Its a list of remote computers where the run command will be executed.
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
If that variable does not exist will fallback to password.
sudo_passwords
Needed if each of the servers needs specific password and user.
To define the shell we want to use defaults to bash.
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
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
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
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
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
from fabric.decorators import task
@task
def foo():
run('foo')
@task(alias='bar')
def my_bar():
run('bar')
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()
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():
The complete slides will be always available at https://monobot.github.io/fabric_pyday2017/