Fabric est une librairie python ainsi qu’un outil en ligne de commande visant à simplifier l’utilisation de SSH lors du déploiement d’applications ou lors de tâches d’administration système.
C’est un projet python :
$ pip install fabric
Dans un fichier fabfile.py
, à la racine de notre
projet.
from fabric.api import task
@task
def hello():
print("Hello world!")
NB : la syntaxe `@task est un décorateur en python : C’est un design pattern qui permet d’attacher dynamiquement des foncionnalités à une fonction
Puis, on lance la tâche
$ fab hello
Hello world!
Done.
from fabric.api import task, local
@task()
def commit_and_push():
"nosetests tests/unit/")
local("git add -p && git commit")
local("git push") local(
NB : nosetests est une librairie de tests pour python
Ce qui donne :
$ fab commit_and_push
...........
Ran 55 fabulous tests in 0.2529 seconds
[localhost] run: git add -p && git commit
<interactive Git add / git commit edit message session>
[localhost] run: git push
<git push session, possibly merging conflicts interactively>
Done.
from fabric.api import task, local
@task
def test():
"nosetests tests/unit/")
local(
@task
def commit():
"git add -p && git commit")
local(
@task
def push():
"git push")
local(
@task
def prepare_deploy():
test()
commit() push()
La tâche prepare_deploy
est toujours disponible, mais
l’organisation plus granulaire permet d’appeler spécifiquement une
sous-tâche si besoin.
À noter :
fab --list
Available commands:
commit
prepare_deploy
push
test
Et allons au bout, en ajoutant les commentaires dans chaque tâche.
from fabric.api import task, local
@task
def test():
"""Runs the tests.
"""
"nosetests tests/unit/")
local(
@task
def commit():
"""Put on stage the modified content of the working tree, and commit.
"""
"git add -p && git commit")
local(
@task
def push():
"""Push the code to the remote repository.
"""
"git push")
local(
@task
def prepare_deploy():
"""Locally Prepare the release : test the code, commit-it and push it to the remote repository.
"""
test()
commit() push()
NB : En python, un commentaire multiligne en première position
d’un function, d’une classe, d’une méthode ou d’un module constitue une
docstring
. Les capacité d’introspection de python
permettent de décupérer ce commentaire lors de l’execution d’un
script.
Et zou !
$ fab --list
Available commands:
commit Put on stage the modified content of the working tree, and commit.
prepare_deploy Locally Prepare the release : test the code, commit-it and push it to the remote repository.
push Push the code to the remote repository.
test Runs the tests.
C’est bien gentil, mais l’idée à l’origine c’était de pouvoir
executer la même tâche sur plusieurs hosts distants… Facile, c’est
run
qui nous allons utiliser dans notre tâche. On en
profite pour ajouter deux-trois petites choses…
from fabric.api import task, settings, run, sudo
@task
def deploy():
= '/var/www/my_project'
code_dir # check the existence of the repositiry
with settings(warn_only=True):
if run("test -d %s" % code_dir).failed:
"git clone user@vcshost:/path/to/repo/.git %s" % code_dir)
run(# update
with cd(code_dir):
"git pull")
run("apachectl -k graceful") sudo(
Et pour lancer la tâche sur une machine:
$ fab deploy
No hosts found. Please specify (single) host string for connection: my_server
[my_server] run: test -d /var/www/my_project
Warning: run() encountered an error (return code 1) while executing 'test -d /var/www/my_project'
[my_server] run: git clone user@vcshost:/path/to/repo/.git /var/www
[my_server] out: Cloning into /var/www/my_project...
[my_server] out: Password: <enter password>
[my_server] out: remote: Counting objects: 6698, done.
[my_server] out: remote: Compressing objects: 100% (2237/2237), done.
[my_server] out: remote: Total 6698 (delta 4633), reused 6414 (delta 4412)
[my_server] out: Receiving objects: 100% (6698/6698), 1.28 MiB, done.
[my_server] out: Resolving deltas: 100% (4633/4633), done.
[my_server] out:
[my_server] run: git pull
[my_server] out: Already up-to-date.
[my_server] out:
[my_server] sudo: apachectl -k graceful
À noter que l’on aurait pu appeler la tâche en passant le host en paramètre :
$ fab -H my_server deploy
......
Maintenant, on va s’affranchir du passage de paramètre :
Première possiblité :
from fabric.api import run, env
env.hosts = ['host1', 'host2']
La commande run
s’executera sur chacun des hosts.
Autre possibilité, les rôles
from fabric.api import env
env.roledefs = {
'web': ['www1', 'www2', 'www3'],
'db': ['db1']
}
Il faudra alors cibler quelle tâche peu executer quel rôle. Par
exemple deploy
sur les frontaux, et migrate
sur la DB. On utilise pour cela d’autre décorateurs @hosts
et @role
.
from fabric.api import run, roles
= {
env.roledefs 'db': ['db1', 'db2'],
'web': ['web1', 'web2', 'web3'],
}
@roles('db')
def migrate():
# Database stuff here.
pass
@roles('web')
def update():
# Code updates here.
pass
Plus fort, maintenant… on va pouvoir depuis le script fabric
dynamiquement définir la liste des hosts. La commande
execute
permet de passer un paramètre hosts à un appel de
task.
from fabric.api import run, execute, task
# une librairie qui me permet trouver des hosts selon des critères de recherche
from mylib import find_hosts
def do_work()
"""le code à executer sur les hosts
"""
"my_super_command")
run(
# et donc la commande qu'on appelle depuis la ligne de commande
@task
def deploy(param=None):
# c'est ici que la magie opère
# on récupère la liste des hosts
= find_hosts(param)
host_list
# et on la passe à notre fonction
# done.
=host_list) execute(do_work, hosts
Notez bien le param
. On peut appeler des tâches en
passant des paramètres ; ca peut devenir démentiel !
$ fab deploy:web
$ fab deploy:db
On va utiliser une autre tâche pour spécifier les hosts
from fabric.api import run, task
from mylib import find_hosts
# cette fois c'est une tache
@task
def do_work():
"my_super_command")
run(
@task
def set_hosts(param):
# On met à jour env.hosts plutôt que d'appeler execute()
= find_hosts(param) env.hosts
Aussi l’appel devient
$ fab set_hosts:prod do_work
Et au final, on peut imaginer des tâches du genre :
$ fab set_hosts:db snapshot
$ fab set_hosts:cassandra,cluster2 repair_ring
$ fab set_hosts:redis,environ=prod status
Fabtools s’appuie sur fabric pour proposer un ensemble de helpers tout prêts.
Juste l’exemple commenté de la doc :
from fabric.api import *
from fabtools import require
import fabtools
@task
def setup():
# Require some Debian/Ubuntu packages
require.deb.packages(['imagemagick',
'libxml2-dev',
])
# Require a Python package
with fabtools.python.virtualenv('/home/myuser/env'):
'pyramid')
require.python.package(
# Require an email server
'example.com')
require.postfix.server(
# Require a PostgreSQL server
require.postgres.server()'myuser', 's3cr3tp4ssw0rd')
require.postgres.user('myappsdb', 'myuser')
require.postgres.database(
# Require a supervisor process for our app
'myapp',
require.supervisor.process(='/home/myuser/env/bin/gunicorn_paster /home/myuser/env/myapp/production.ini',
command='/home/myuser/env/myapp',
directory='myuser'
user
)
# Require an nginx server proxying to our app
'example.com',
require.nginx.proxied_site(='/home/myuser/env/myapp/myapp/public',
docroot='http://127.0.0.1:8888'
proxy_url
)
# Setup a daily cron task
'maintenance', 'myuser', 'my_script.py') fabtools.cron.add_daily(
Cette œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution 3.0 France.