Основы использования Fabric

Немного рекламы

Оглавление

Введение

Как известно, то, что обычный пользователь делает мышкой, повторяя одни и те же действия по 10 раз, программисты и, особенно, админы, делают скриптами. Инструментов и языков для написания скриптов существует огромное количество. Некоторые из них предназначены для определенных задач, например, на сборку и компиляцию программ, как Makefile и его последователи, другие являются более низкоуровневыми, например тот же bash. Промежуточное место я бы отвел для таких скриптовых языков как Python, Perl, Ruby. Преимущество последних в первую очередь в читаемости кода, когда нужно писать что-то большое, но в то же время, например, для работы с файлами, при работе с этими языками приходится писать больше кода, чем на том же bash.

В этой статье мы поговорим о Fabric - библиотеке для Python (в данный момент поддерживаются версии 2.5 - 2.7), которую можно использовать для многих задач автоматизации, при этом она позволяет легко вызывать команды того же bash, где это необходимо или оправдано.

Если мельком взглянуть на документацию Fabric, то складывается впечатление, что эта библиотека предназначена в первую очередь для работы на удаленном сервере при подключении к нему через SSH, однако, ее можно использовать и для локальных задач.

Лично я на данный момент Fabric использую для трех типов задач:

  1. В качестве замены Makefile для сборки программы OutWiker.
  2. Для создания резервных копий на сетевом хранилище.
  3. Для управления сайтами через SSH (развертывание, создание резервных копий, перезапуск сервера и т.д.)

Разбираться с Fabric мы начнем с самого начала, т.е. с установки.

Установка

Установка под Linux не должна вызывать никаких затруднений. Достаточно написать в консоли

$ easy_install fabric

или

$ pip install fabric

кому что больше нравится. И Fabric будет установлен вместе с необходимыми зависимостями.

А вот под Windows с установкой зависимостей могут возникнуть затруднения. Fabric использует библиотеку шифрования PyCrypto, которая, как правило, распространяется в исходных кодах и компилируется на месте при установке. Компиляция ее под Linux проблем не возникает, потому что редкая Linux-система (особенно система админа или программиста) не имеет компилятора C/C++.

Под Windows его может и не быть. Но, к счастью, можно найти готовые сборки PyCrypto под Windows. Доверять им или нет - ваше дело. Однако, если PyCrypto установить, то затем установка Fabric завершится без проблем.

Чтобы убедиться, что Fabric установлен, наберем в консоли команду

Если вы получите сообщение вида:

Fatal error: Couldn't find any fabfiles!

Remember that -f can be used to specify fabfile path, and use -h for help.

Aborting.
Couldn't find any fabfiles!

Remember that -f can be used to specify fabfile path, and use -h for help.

То значит, что все нормально и можно двигаться дальше.

Если вы пользуетесь Windows и вы получите сообщение о том, что команда fab не найдена, то убедитесь, что у вас добавлен путь до папки C:\Python27\Scripts\ или ее аналога для той версии Python, который вы используете, в переменную окружения PATH. В крайнем случае, вы всегда можете писать команду в виде

> C:\Python27\Scripts\fab

но просто fab писать удобнее.

Использование Fabric

Использование Fabric во многом напоминает использование Makefile, т.е. вы создаете в какой-то папке файл fabfile.py, в котором описываете задачи (как они описываются, мы рассмотрим чуть позже). Например, там может быть задача backup (для создания резервной копии) или deploy (для развертывания сайта) и т.д.

Затем в консоли вы запускаете команду

$ fab backup

или

$ fab deploy

И Fabric выполняет нужные задачи. Можно указать несколько задач, тогда они будут выполнены последовательно, например, сначала создать резервную копию, а потом развернуть приложение:

$ fab backup deploy

В задачи можно передавать параметры. Параметры передаются после символа ":" после имени задачи. Пусть, например, мы можем указать путь до папки, куда делать резервную копию:

$ fab backup:"~/backups/mycoolsite"

Если в параметрах нет пробелов, то кавычки можно не писать. Если параметров несколько, то они перечисляются через запятую. Но про передачу параметров мы еще поговорим.

Файл fabfile.py

Структура файла

Библиотека Fabric замечательна тем, что для ее использования не нужно разбираться с новым языком описания задач или каким-то новым синтаксисом, все задачи описываются на языке Python, возможно, с использованием вспомогательных средств Fabric. Так, например, для создания двух задач, которые были приведены выше в качестве примера, достаточно создать файл с именем fabfile.py (имя важно, хотя при желании его можно изменить, о чем будет сказано ниже) со следующим содержимым:

# -*- coding: UTF-8 -*-


def backup ():
    print u'Run backup...'
    print u'Ok'


def deploy ():
    print u'Run deploy...'
    print u'Ok'

В данном случае у нас задачи backup и deploy не принимают никаких параметров. Как вы видите, описание задачи - это просто функция на языке Python. Мы можем выполнить описанные выше команды в консоли (в качестве рабочей папки у нас должна быть папка с файлом fabfile.py):

$ fab backup

Run backup...
Ok

Done.

$ fab deploy

Run deploy...
Ok

Done.

По умолчанию Fabric ищет задачи в файле с именем fabfile.py, но при желании вы можете назвать этот файл по-другому, но в этом случае вам надо будет явно указывать имя файла с задачами с помощью с помощью одного из параметров -f или --fabfile. Например, если наш файл с задачами имеет имя my_fabfile.py, то мы можем запустить задачи так, как показано ниже:

$ fab -f my_fabfile.py backup

или

$ fab --fabfile=my_fabfile.py backup

Кроме того, в файле профиля пользователя можно создать файл .fabricrc (под Linux путь будет выглядеть как ~/.fabricrc), в котором задаются различные настройки по умолчанию. Если в этот файл добавить строку

fabfile=my_fabfile.py

то по умолчанию Fabric будет искать не файл fabfile.py, а файл my_fabfile.py.

Документирование задач

Команда fab имеет множество параметров, один из самых полезных параметров - это параметр -l или --list. Этот параметр показывает, какие задачи есть в fabfile.py. Вот, например, результат работы параметра --list для приведенного выше fabfile.py.

$ fab --list

Available commands:

    backup
    deploy

В таком виде подсказка уже полезна, но лучше все-таки еще добавить комментарии к каждой задаче. Чтобы их добавить, достаточно для каждой функции добавить описание в виде docstring:

# -*- coding: UTF-8 -*-


def backup ():
    """
    Создание резервной копии
    """

    print u'Run backup...'
    print u'Ok'


def deploy ():
    """
    Развертывание сайта mycoolsite.com
    """

    print u'Run deploy...'
    print u'Ok'

В этом случае параметр --list выведет список задач следующим образом:

$ fab --list

Available commands:

    backup  Создание резервной копии
    deploy  Развертывание сайта mycoolsite.com

На самом деле лучше не использовать в описаниях русские символы, поскольку в консоли под Windows они превратятся в кракозяблы (под Linux все будет работать нормально). Кроме того, не стоит писать очень длинные описания, поскольку они будут обрезаны до чуть больше чем 70 символов.

Понятно, что одни задачи могут вызывать другие задачи, кроме того внутри fabfile.py могут быть функции для внутреннего использования, попадание которых в список задач, выводимых с помощью параметра --list, нежелательно. Такие "внутренние" функции должны начинаться с символа "_". Так, например, функция _internal() из следующего прмиера fabfile.py не будет видна как задача:

# -*- coding: UTF-8 -*-


def backup ():
    """
    Создание резервной копии
    """

    print u'Run backup...'
    print u'Ok'


def deploy ():
    """
    Развертывание сайта mycoolsite.com
    """

    print u'Run deploy...'
    print u'Ok'


def _internal ():
    pass
$ fab --list

Available commands:

    backup  Создание резервной копии
    deploy  Развертывание сайта mycoolsite.com

Передача параметров

Выше мы уже говорили о том, что в задачи для Fabric мы можем передавать параметры, приведем несколько примеров. Для демонстрации мы будем использовать простую функцию, которая просто выводит переданный параметр:

# -*- coding: UTF-8 -*-

def task (param):
    print param

Если мы забудем передать параметр, то вызов команды fab task приведет к ошибке:

$ fab task

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/tasks.py", line 424, in execute
    results['<local-only>'] = task.run(*args, **new_kwargs)
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
TypeError: task() takes exactly 1 argument (0 given)

Если мы укажем значение параметра, то все будет работать, как положено:

$ fab task:"Hello world!"

Hello world!

Done.

Аналогичного результата мы добьемся, если будем указывать имя параметра:

$ fab task:param="Hello world!"

Hello world!

Done.

Как и везде в языке Python, можно использовать значения параметров по умолчанию:

# -*- coding: UTF-8 -*-

def task (param=False):
    print param

В этом случае значение параметра можно не передавать:

$ fab task
False

Done.

Если же задача функция принимает два и более значений, то параметры передаются через запятую. Например:

# -*- coding: UTF-8 -*-

def task (param1, param2):
    print param1
    print param2
$ fab task:"Hello","World"

Hello
World

Done.

Главное тут не ошибиться и не добавить после запятой пробел, иначе второй параметр будет воспринят, как имя другой команды:

$ fab task:"Hello", "World"

Warning: Command(s) not found:
    World

Available commands:

    task

Как видно из предыдущей ошибки, Fabric позволяет одной командой запускать сразу несколько задач, которые будут выполняться последовательно.

# -*- coding: UTF-8 -*-

def task1 ():
    print "Task 1"


def task2 ():
    print "Task 2"
$ fab task1 task2

Task 1
Task 2

Done.

Но если одна из них завершится с ошибкой, то следующая задача выполнена не будет.

# -*- coding: UTF-8 -*-

def task1 ():
    raise Exception


def task2 ():
    print "Task 2"
$ fab task1 task2

Task 1
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/main.py", line 743, in main
    *args, **kwargs
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/tasks.py", line 424, in execute
    results['<local-only>'] = task.run(*args, **new_kwargs)
  File "/usr/local/lib/python2.7/dist-packages/Fabric-1.10.1-py2.7.egg/fabric/tasks.py", line 174, in run
    return self.wrapped(*args, **kwargs)
  File "/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/03/fabfile.py", line 5, in task1
    raise Exception
Exception

Теперь, когда мы рассмотрели способы запуска задач, перейдем к рассмотрению того, какие возможности нам предоставляет библиотека Fabric и ее модули. Внутри функций-задач мы можем использовать любые функции Python, в том числе и для копирования, удаления, переименования файлов и т.д. Также в Python есть средства для запуска программ. Но давайте посмотрим, какие функции нам предоставляет Fabric для облегчения работы на локальной компьютере и на удаленном сервере.

Использование Fabric на локальном компьютере

Знакомство с Fabric у меня началось с того, что я начал использовать эту библиотеку в качестве замены команды make и Makefile. Когда-то давно для сборки OutWiker использовался обычный Makefile. Например, команда make win собирала версию под Windows, а команда make deb делала deb-пакет под Linux'ы на основе Debian.

В целом такая система работала, но была одна вещь, которая ужасно раздражала - это плохая переносимость Makefile между Windows и Linux. Как известно, под Windows команды make могут выполнены различными версиями интерпретатора (например, той версией, что прилагается к cygwin или отдельно скачанной сборкой make или make, идущий вместе с Visual Studio). Я уже даже не помню, какие версии у меня стояли, но внезапно после установки какого-то софта make по-другому стал воспринимать переводы строк и то ли вместо написания команд на разных строках пришлось писать команды через &&, то ли наоборот, не суть важно. Главное, что Makefile не работал под разными сборками make. Тогда я понял, что надо искать замену, причем желательно, чтобы сборщик описывал бы задачи на любимом языке, т.е. на Python. Путем поверхностного осмотра кандидатов (одним из них был waf), я остановился на Fabric. В первую очередь на это решение повлияло то, что Fabric позволяет писать не только на Python, но и где нужно, вставлять команды bash, благодаря чему makefile был переписан под Fabric буквально за час. После этого уже можно было его доводить до ума заменой некоторых команд bash на Python, где это улучшало читаемость кода.

В результате Makefile по-прежнему используется, но только как часть сборки deb-пакетов под Linux, в остальном везде используется Fabric.

Многие полезные функции из Fabric содержатся в модуле fabric api. Первые две функции, которые мы рассмотрим будут lcd и local. Первая из них - аналог команды cd консоли, вторая позволяет выполнять произвольные команды, как будто мы их запускаем в консоли. Главное удобство команды lcd перед стандартной для Python функции os.chdir(), которая выполняет, на первый взгляд, аналогичные действия (т.е. устанавливает рабочую директорию) в том, что функцию lcd можно использовать вместе с оператором with, при выходе из которого рабочая директория будет изменена на прежнее значение. Если рассмотреть работу функции lcd подробнее, то окажется, что она на самом деле не изменяет текущую рабочую директорию с точки зрения скрипта Python.

Рассмотрим пример, в котором будут выводиться рабочие директории с помощью функции стандартной библиотеки os.getpwd() и с помощью команды pwd консоли (для выполнения команды pwd будет использоваться вышеупомянутая функция local:

# -*- coding: UTF-8 -*-

import os

from fabric.api import lcd, local


def task ():
    # Выведем исходные рабочие директории
    print "**** Before 'with' operator"
    print os.getcwd()
    local ("pwd")
    print

    with lcd ("~"):
        print "**** Inside 'with' operator"
        # Выведем рабочие директории внутри оператора with
        print os.getcwd()
        local ("pwd")
        print

    # Выведем рабочие директории после оператора with
    print "**** After 'with' operator"
    print os.getcwd()
    local ("pwd")
    print

Результат в моем случае выглядит так (понятно, что у вас директории будут называться иначе):

jenyay@jenyay-desktop:/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04$ fab task

**** Before 'with' operator
/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04
[localhost] local: pwd
/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04

**** Inside 'with' operator
/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04
[localhost] local: pwd
/home/jenyay

**** After 'with' operator
/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04
[localhost] local: pwd
/media/Data/Workdir/Черновики/В процессе/Fabric/__attach/examples/04


Done.

Разумеется, внутри функции local можно выполнять что-то более полезное. Например, в fabfile проекта OutWiker в задаче win, с помощью которой создается сборка OutWiker под Windows, можно найти такие строки:

def win ():
...
    with lcd ("src"):
            local ("python setup_win.py build")
...
    with lcd ("build/outwiker_win"):
            local ("7z a ..\outwiker_win_unstable.zip .\* .\plugins -r -aoa")
...

На момент написания этих строк fabfile проекта OutWiker из модуля fabric.api используются только функции lcd и local.

Использование Fabric для взаимодействия с удаленным сервером

Настройка удаленного интерпретатора

Fabric удобно использовать на локальном компьютере, но больше всего возможностей эта библиотека предоставляет для работы по сети на удаленных серверах. Все возможности библиотеки в этой статье мы не рассмотрим, но, чтобы понять принцип работы, поговорим о некоторых из них.

Для работы на удаленном сервере существуют аналоги локальных функций lcd и local, это функции cd и run соответственно. Чтобы научиться подключаться к серверу, сделаем fabfile с одной задачей, которая просто выполняет команду ls на сервере, чтобы вывести список файлов в домашней директории.

В простейшем случае скрипт будет такой, но нужно заранее предупредить, что он может не сработать:

# -*- coding: UTF-8 -*-

from fabric.api import run


def runls ():
    run ("ls")

Как видите, в скрипте мы не задаем никакого адреса (хотя мы может это сделать, и воспользуемся этой возможностью позже). Если мы теперь выполним команду fab runls, то Fabric сначала попросит ввести адрес, куда мы должны подключиться, а затем результат уже будет зависеть от настроек удаленного сервера. Далее приведен вывод команды fab runls при подключении к удаленному хранилищу по локальной сети:

$ fab runls

No hosts found. Please specify (single) host string for connection: 192.168.100.10
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.
Disconnecting from 192.168.100.10... done.

Но вполне может случиться и такая ошибка:

$ fab runls

No hosts found. Please specify (single) host string for connection: 192.168.100.10
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] Login password for 'jenyay':
[192.168.100.10] out: sh: /bin/bash: not found
[192.168.100.10] out:


Fatal error: run() received nonzero return code 127 while executing!

Requested: ls
Executed: /bin/bash -l -c "ls"

Aborting.
Disconnecting from 192.168.100.10... done.
run() received nonzero return code 127 while executing!

Requested: ls
Executed: /bin/bash -l -c "ls"

Здесь нет ничего страшного, это довольно частая ошибка, возникающая из-за того, что по умолчанию команда run на удаленном сервере пытается выполнить команду с помощью строки "/bin/bash -l -c "<Команда>"". Здесь можно обратить внимание на то, что явно указан путь до интерпретатора bash и то, что используется параметр -c, говорящий о том, что команды передаются далее в качестве строкового параметра. На удаленном сервере может не быть интерпретатора bash или он может быть расположен в другом месте.

Указать путь до интерпретатора можно разными способами. Во-первых, можно воспользоваться параметром командной строки --shell:

$ fab runls --shell="sh -c"

No hosts found. Please specify (single) host string for connection: 192.168.100.10
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.
Disconnecting from 192.168.100.10... done.

Поскольку на данном сервере не установлен интерпретатор bash, я воспользовался интерпретатором sh.

Второй способ задания параметров подключения состоит в том, чтобы воспользоваться классом env из fabric.api. Класс env содержит различные настройки, в том числе и для подключения к удаленному серверу. Для этого нам надо изменить скрипт:

# -*- coding: UTF-8 -*-

from fabric.api import run, env

env.shell = "sh -c"


def runls ():
    run ("ls")

Теперь нам не нужно использовать параметр --shell:

$ fab runls

No hosts found. Please specify (single) host string for connection: 192.168.100.10
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.
Disconnecting from 192.168.100.10... done.

Способы ввода адресов удаленных хостов

Теперь сделаем так, чтобы нам не нужно было каждый раз вводить адрес сервера. Эту проблему можно решить также двумя способами: воспользоваться параметром командной строки -H или --hosts или добавить хосты с помощью свойства env.hosts. Для начала воспользуемся параметром командной строки. Обратите внимание, что параметр (как и свойство в классе env) называется hosts во множественном числе. Дело в том, что одну и ту же задачу можно одновременно запускать сразу на нескольких серверах (последовательно или параллельно). Поэтому при использовании параметра --hosts мы можем задать список хостов, разделенных запятой, а свойство env.hosts - это список строк.

Сначала воспользуемся предыдущим примером с использованием параметра --hosts:

$ fab runls --hosts="192.168.100.10"

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.
Disconnecting from 192.168.100.10... done.

Теперь изменим скрипт, чтобы адрес хоста задавался в нем:

# -*- coding: UTF-8 -*-

from fabric.api import run, env

env.shell = 'sh -c'
env.hosts = ['192.168.100.10']


def runls ():
    run ('ls')

Теперь мы можем запускать эту задачу без дополнительных параметров:

$ fab runls

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.
Disconnecting from 192.168.100.10... done.

Чтобы продемонстировать работу сразу с несколькими хостами, добавлю в env.hosts адрес еще одного сервера:

# -*- coding: UTF-8 -*-

from fabric.api import run, env

env.shell = 'sh -c'
env.hosts = ['192.168.100.10',
             '192.168.100.20']


def runls ():
    run ('ls')

Результат работы будет выглядеть следующим образом:

$ fab runls

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.10] Passphrase for private key:
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:

[192.168.100.20] Executing task 'runls'
[192.168.100.20] run: ls
[192.168.100.20] Passphrase for private key:
[192.168.100.20] out: Maildir           backups         domains         imap            public_html     tmp
[192.168.100.20] out:


Done.
Disconnecting from 192.168.100.20... done.
Disconnecting from 192.168.100.10... done.

Избавляемся от ввода пароля

В приведенных выше примерах при каждом запуске приходилось вводить пароли. Это сравнительно безопасно, но не удобно, особенно если вы используете Fabric для запуска скриптов сразу на нескольких серверах. С помощью класса env мы можем записать пароли в скрипт, воспользовавшись словарем env.passwords. В этом словаре ключ - полный адрес сервера, обязательно включающий имя пользователя и порт, а значение - пароль. Например, предыдущий скрипт может выглядеть следующим образом (разумеется, все пароли вымышленные, любые совпадения случайны :) ):

# -*- coding: UTF-8 -*-

from fabric.api import run, env

env.shell = 'sh -c'
env.hosts = ['192.168.100.10',
             '192.168.100.20']

env.passwords = {
    'jenyay@192.168.100.10:22': '123456',
    'jenyay@192.168.100.20:22': '111111',
}


def runls ():
    run ('ls')

Теперь, если все задано правильно, то вводить пароль просить не будут:

$ fab runls

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:

[192.168.100.20] Executing task 'runls'
[192.168.100.20] run: ls
[192.168.100.20] out: Maildir           backups         domains         imap            public_html     tmp
[192.168.100.20] out:


Done.
Disconnecting from 192.168.100.20... done.
Disconnecting from 192.168.100.10... done.

Параллельное выполнение задач

Поскольку мы полностью избавились от ввода текста в консоль, теперь мы можем воспользоваться еще одной особенностью Fabric - мы можем запустить задачу на двух серверах параллельно. Для этого достаточно добавить параметр --parallel или -P. Результат будет выглядеть примерно таким образом:

$ fab runls --parallel

[192.168.100.10] Executing task 'runls'
[192.168.100.20] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.20] run: ls
[192.168.100.20] out: Maildir           backups         domains         imap            public_html     tmp
[192.168.100.20] out:

[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.

Подобного результата мы можем добиться без использования параметров командной строки, для этого нужно обернуть функцию runls в fabfile.py декоратором paralell из fabric.api:

# -*- coding: UTF-8 -*-

from fabric.api import run, env, parallel

env.shell = 'sh -c'
env.hosts = ['192.168.100.10',
             '192.168.100.20']

env.passwords = {
    'jenyay@192.168.100.10:22': '123456',
    'jenyay@192.168.100.20:22': '111111',
}


@parallel
def runls ():
    run ('ls')

Запускаем и проверяем результат:

$ fab runls

[192.168.100.10] Executing task 'runls'
[192.168.100.20] Executing task 'runls'
[192.168.100.10] run: ls
[192.168.100.20] run: ls
[192.168.100.20] out: Maildir           backups         domains         imap            public_html     tmp
[192.168.100.20] out:

[192.168.100.10] out: Distrib         VirtualBox VMs  music           projects        Кино        Фото
[192.168.100.10] out: Downloads       backup          photo           temp            Книги
[192.168.100.10] out:


Done.

Еще раз отмечу, что задачи можно запускать параллельно только если они не требуют ввода текста в консоли. В противном случае возникнет ошибка.

Обработка ошибок

Теперь посмотрим, как мы можем отслеживать ошибки, возникающие на удаленном сервере во время выполнения команды. Для демонстрации мы изменим предыдущий пример, чтобы команда ls выводила содержимое не домашней директории, а директории, имя которой будет передаваться в качестве параметра:

# -*- coding: UTF-8 -*-

from fabric.api import run, env

env.shell = 'sh -c'
env.hosts = ['192.168.100.10']

env.passwords = {
    'jenyay@192.168.100.10:22': '12345',
}


def runls (dirname):
    command = 'ls {}'.format (dirname)
    run (command)

    print "**** Task is completed"

Если мы передадим существующую на удаленном хосте директорию, то все будет работать хорошо, и мы увидим сообщение о том, что выполнение задачи доведено до конца:

$ fab runls:projects

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls projects
[192.168.100.10] out: LJournalist_old  drawspectrum     ljournalist      ljwatcher        training_site
[192.168.100.10] out: Scripts          exercise_maker   ljournalist_web  outwiker
[192.168.100.10] out:

**** Task is completed

Done.
Disconnecting from 192.168.100.10... done.

Если же возникнет какая-то ошибка, то сразу после нее выполнение скрипта прервется, даже если мы могли бы обработать возникшую ошибку:

$ fab runls:abyrvalg

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls abyrvalg
[192.168.100.10] out: ls: abyrvalg: No such file or directory
[192.168.100.10] out:


Fatal error: run() received nonzero return code 1 while executing!

Requested: ls abyrvalg
Executed: sh -c "ls abyrvalg"

Aborting.
Disconnecting from 192.168.100.10... done.
run() received nonzero return code 1 while executing!

Requested: ls abyrvalg
Executed: sh -c "ls abyrvalg"

Как быть? Мы можем указать Fabric, чтобы при возникновении ошибки, выполнение скрипта не прерывалось, а, например, только выводился текст ошибки, после чего выполнение скрипта продолжалось. Это можно сделать глобально, установив env.warn_only=True, но можно сделать более красиво, обернув команду, которая может ожидаемо вызвать ошибку, в блок with. Для этого мы воспользуемся функцией settings. Эта функция может принимать большое количество параметров, мы пока воспользуемся только одним - warn_only. Также мы воспользуемся тем, что функция run, возвращает объект, из которого можно извлечь код ошибки и текст, который команда выводила в консоль:

# -*- coding: UTF-8 -*-

from fabric.api import run, env, settings

env.shell = 'sh -c'
env.hosts = ['192.168.100.10']

env.passwords = {
    'jenyay@192.168.100.10:22': '12345',
}


def runls (dirname):
    with settings (warn_only=True):
        command = 'ls {}'.format (dirname)
        result = run (command)

    print 'result: {}'.format (result.return_code)

    print "**** Task is completed"

Если мы передадим в задачу существующую директорию, то результат будет напоминать предыдущий, с той лишь разницей, что еще будет выведен нулевой код ошибки:

$ fab runls:projects

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls projects
[192.168.100.10] out: LJournalist_old  drawspectrum     ljournalist      ljwatcher        training_site
[192.168.100.10] out: Scripts          exercise_maker   ljournalist_web  outwiker
[192.168.100.10] out:

result: 0
**** Task is completed

Done.
Disconnecting from 192.168.100.10... done.

Если же переданной директории не существует, то выполнение задачи все равно будет доведено до конца. Мы могли бы как-то обработать ошибку, но в данном случае мы ее просто проигнорируем:

$ fab runls:abyrvalg

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls abyrvalg
[192.168.100.10] out: ls: abyrvalg: No such file or directory
[192.168.100.10] out:


Warning: run() received nonzero return code 1 while executing 'ls abyrvalg'!

result: 1
**** Task is completed

Done.
Disconnecting from 192.168.100.10... done.

Мы могли бы указать, что не нужно писать даже предупреждения. Для этого в качестве первого параметра функции settings нужно передать менеджер контекста, который можно создать с помощью функции hide. Эта функция принимает текстовые параметры, описывающие, что именно нужно скрыть, это могут быть строки вида 'running', 'stdout', 'stderr', 'warnings'. Мы скроем только предупреждения:

# -*- coding: UTF-8 -*-

from fabric.api import run, env, settings, hide

env.shell = 'sh -c'
env.hosts = ['192.168.100.10']

env.passwords = {
    'jenyay@192.168.100.10:22': '12345',
}


def runls (dirname):
    with settings (hide('warnings'), warn_only=True):
        command = 'ls {}'.format (dirname)
        result = run (command)

    print 'result: {}'.format (result.return_code)

    print "**** Task is completed"

Теперь в случае ошибки вывод будет таким:

$ fab runls:abyrvalg

[192.168.100.10] Executing task 'runls'
[192.168.100.10] run: ls abyrvalg
[192.168.100.10] out: ls: abyrvalg: No such file or directory
[192.168.100.10] out:

result: 1
**** Task is completed

Done.
Disconnecting from 192.168.100.10... done.

Расширенные возможности запуска задач. Декоратор task

Новый стиль задач

До сих пор мы создавали задачи, используя просто функции верхнего уровня в файле fabfile.py (или его аналоге), однако, начиная с Fabric 1.1 появились так называемые задачи нового стиля (new-style tasks), позволяющие еще более гибко их настраивать. Главное нововведение Fabric 1.1 - это декоратор task и класс Task, наследуя который можно создавать новые задачи. В этой статье мы рассмотрим только декоратор. Ранее все функции, не начинающиеся с символа "_", считались задачей. С новым стилем мы можем создавать функции с любыми именами, но снаружи видны будут только те из них, кто помечен декоратором task. Это правило работает, если есть хотя бы одна функция, отмеченная этим декоратором.

Например, пусть у нас есть такой fabfile.py:

# -*- coding: UTF-8 -*-

from fabric.api import task


@task
def task1 ():
    print "Task 1"


@task
def task2 ():
    print "Task 2"


def internal_func ():
    print "internal"

С помощью параметра --list посмотрим список всех задач в этом файле:

$ fab --list

Available commands:

    task1
    task2

Как видно, функция internal_func не отображается, хотя она и не начинается с символа "_".

Задачи по умолчанию

С помощью декоратора task можно указать задачу, которая будет выполняться в случае, если команде fab не будет передано имя задачи. Для этого в качестве параметра декоратора нужно задать default=True:

# -*- coding: UTF-8 -*-

from fabric.api import task


@task (default=True)
def task1 ():
    print "Task 1"


@task
def task2 ():
    print "Task 2"

Теперь нам не нужно использовать команду fab task1, чтобы выполнить первую задачу, достаточно просто запустить команду fab:

$ fab

Task 1

Done.

Разумеется, если мы выполним только команду fab task2, то будет выполнена только она:

$ fab task2

Task 2

Done.

Псевдонимы

С помощью декоратора task можно создавать псевдонимы для задач. В этом случае у задачи будет два имени, вызывать ее можно по любому из них. Чтобы создать псевдоним, в декоратор task нужно добавить параметр alias="псевдоним". Изменим предыдущий пример:

# -*- coding: UTF-8 -*-

from fabric.api import task


@task (alias="mytask")
def task1 ():
    print "Task 1"


@task
def task2 ():
    print "Task 2"

Если мы теперь выполним команду fab --list, то в списке будет показаны три задачи:

$ fab --list

Available commands:

    mytask
    task1
    task2

Если мы выполним команду fab mytask, то это будет равносильно выполнению команды fab task1:

$ fab mytask

Task 1

Done.

Заключение

В этой статье мы рассмотрели основные, но далеко не все возможности библиотеки Fabric. Эта библиотека позволяет легко автоматизировать рутинные задачи как при работе на локальном компьютере, так и на удаленном сервере. С помощью Fabric можно создавать резервные копии, развертывать веб-приложения и даже использовать его для сборки программ. Преимущество Fabric в том, что эта библиотека не будет требовать от вас изучения нового синтаксиса, все скрипты пишутся на Python.

Спасибо за внимание, жду ваших отзывов и комментариев. Интересно, многие ли из вас дочитают эту большую статью до конца. :)

Другие статьи на тему программирования на Python

Немного рекламы

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
5 stars

Рейтинг 4.7/5. Всего 7 голос(а, ов)




Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 594