Phing - деплой проекта на продакшен в 1 клик

Время от времени, как это ни странно, приходится деплоить проект на продакшен. И всё чаще и чаще приходит мысль о том, что ведь можно же как-то упростить этот процесс буквально до нажатия одной кнопки.

Как показывает практика google, есть множество инструментов для автоматического деплоя проекта и автоматизации иных процессов, связанных с последним, но наиболее удобным для проектов, написанных на PHP, по моему мнению, является Phing. Разберём на примере вариант его использования для автоматического деплоя проекта на удалённый web-сервер.


В двух словах, что такое Phing - это 1 раз запущенная команда composer require и довольно понятный XML-файл конфигурации. В общем-то, это всё, что минимально необходимо знать про него :)

Установка Phing

Установка Phing возможна как глобально через mv phing.phar /usr/sbin/phing, так и в каждый проект отдельно. Я предпочитаю добавлять его в composer.json проекта:

{
    "require-dev": {
        "phing/phing": "2.*"
    }
}

После этого в папке vendor проекта появится подпапка phing, в которой среди прочих файлов будет находиться исполняемый файл vendor/phing/phing/bin/phing

Небольшая предыстория

Некоторые исходные данные для нашей задачи:

  • проект на php (не принципиально, но будет учтено в дальнейшем)
  • проект на git
  • разработка ведётся по gitflow, всё тестируется на dev/stage хостах (не принципиально) и только потом выкатывается на продакшен
  • разработка ведётся в PHPStorm IDE (не принципиально)
  • production на удалённом сервере, к которому есть доступ по SSH

Если бы деплой сводился к банальной команде git pull, то вопросов бы особых не возникало, я думаю. Но ведь обычно ещё нужно скинуть файловый кеш как минимум twig'а, скинуть кеш статики, кеш доктриры, сделать composer dumpautoload и т.д., и т.п.

Предположим, что наступил тот момент, когда очередная задача была успешно выполнена, feature-ветка была влита в релизную, та, в свою очередь, проверена, и в итоге было получено добро на релиз. Предполагается, что всё это делается в PHPStorm'е через удобное меню gitflow на панели инструментов, поэтому особо заострять на этом внимание не будем.

Также был успешно сделан Finish release, все изменения попали в master/develop ветки, после чего был сделан git push и изменения благополучно улетели в remote-репозиторий.

И вот тут-то вступает в игру Phing!

Прежде, чем мы начнём

Следует убедиться, что плагин Phing корректно установлен и настроен в PHPStorm, вот тут есть официальня документация. Если кратко, то для этого достаточно немного потыкать мышкой в экран:

Аккуратно! Последовательность нажатий может привести к работоспособности этого плагина ;)

Пункт 3 на скрине не обязателен, если ранее вы уже установили в проект Phing через composer install

Также обязательно должен быть установлен пакет php-ssh2 (например, для mac такой пакет устанавливается как brew install php70-ssh2)

Первые шаги

Теперь создадим в корне проекта файл phing.xml вот примерно с таким содержанием:

<?xml version="1.0" encoding="UTF-8"?>
<project name="MyProject" basedir="/" default="info">

    <property name="HOST" value="192.168.100.200"/>
    <property name="PORT" value="12345"/>
    <property name="USER.NAME" value="username"/>
    <property name="DIR" value="/home/www/domain.com/httpdocs"/>

    <!-- Info -->
    <target name="info">
        <echo>
            Hello, bro!

            You should specify parameter (what you want me to do for you) to run me :)
            Try these:
            - deploy:prod - deploy to production
            - deploy:test - deploy to test
            - deploy:stage - deploy to stage
        </echo>
    </target>

    <!-- Pre-request -->
    <target name="pre-request">
        <propertyprompt propertyName="USER.PASSWORD" promptText="Введите пароль" defaultValue="" />
        <ssh
                host="${HOST}"
                port="${PORT}"
                username="${USER.NAME}"
                password="${USER.PASSWORD}"
                command="echo 'Current branches:' &amp;&amp; cd ${DIR} &amp;&amp; echo '- - - - - - - - - -' &amp;&amp; git branch -a echo '- - - - - - - - - -'"
                display="true"
        />
        <propertyprompt propertyName="BRANCH" promptText="Введите ветку" defaultValue="" />
    </target>

    <!-- Production -->
    <target name="deploy:prod" depends="pre-request">
        <ssh
                host="${HOST}"
                port="${PORT}"
                username="${USER.NAME}"
                password="${USER.PASSWORD}"
                command="cd ${DIR} &amp;&amp; ./update.sh ${BRANCH}"
        />
        <echo>Done!</echo>
    </target>

</project>

По идее сам «шторм» сразу же предложит ассоциировать этот файл с Phing-плагином, о чём вы увидите соответствующее уведомление. Но если этого не произошло, то просто кликните на этом файле правой кнопкой мыши и в контекстном меню выберите Add as Phing build file (скорее всего будет в самом низу списка). После этого появится соответствующий Tool Window:

На скрине виден список всех доступных «целей», которые можно запускать нажатием как раз всего одной кнопки :) Но давайте немного подробнее разберём, что же это за «цели» (target).

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

1. Корневой тег <project> - тут мы указываем название нашего проекта, базовую дирректорию и то задание (target), которое должно быть выполнено по-умолчанию, если не указано иного. Благодаря интеграции с PHPStorm можно спокойно пользоваться автодополнением при написании атрибутов, все они более, чем понятны (ну, например, ещё есть атрибут description - даже в документацию не надо лезть, чтобы понять, для чего он нужен ;)

2. Далее мы описываем нужные «свойства» (или, по сути, переменные) , все они понятны, опять же) Есть множество встроенных «свойств», для остальных же названия нужно придумывать самостоятельно.

3. Затем описывается тег <target> - это конкретное задание, которое мы хотим выполнить (например, деплой проекта на test/stage/production), среди прочих атрибутов указываются имя name="info" задания. name для своего задания вы придумываете сами)

4. <target name="info"> - это задание, суть которого просто вывести информацию на экран пользователя.

5. <target name="pre-request"> - это задание, которое выполняется перед «основным» заданием. Чтобы не плодить copy/paste в разных target'ах, рекомендую общие части вынести вот в такой pre-request. В данном случае мы сначала запрашиваем пароль, следующая инструкция (подробнее о ней ниже) выведет нам на экран список доступных git-веток, затем просим указать ветку, из которой надо выкатить изменения на продакшен.

6. <target name="deploy:prod" depends="pre-request"> - это «задание» как раз и делает деплой на продакшен. Оно зависит от pre-request, что указано в атрибуте depends="pre-request" и содержит всего одну команду .

7. <ssh ... /> - эта инструкция, как не трудно догадаться, фактически коннектит по ssh на удалённый сервер и выполняет там нужную нам команду, а точнее запускает один единственный скрипт update.sh, который может выглядеть примерно вот так:

#!/bin/sh

# Определяем текущего пользователя, запустившего скрипт
CU=`whoami`

# Если это "нужный" пользователь
if [ "$CU" = "username" ] ; then

	# Удалим "лишние" ветки
	/bin/git remote prune origin

	# Определяем текущую ветку
	CURRENT_BRANCH="$(git symbolic-ref HEAD 2>/dev/null)" || CURRENT_BRANCH="(unnamed branch)"
	CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/}
	echo "  - current branch: $CURRENT_BRANCH"

	# Определяем новую ветку, которую нужно выкатить
	NEW_BRANCH="$1"
	if [ "$NEW_BRANCH" = "" ] ; then
	    NEW_BRANCH=$CURRENT_BRANCH
	fi;

	# Делаем чекаут и пулл
	git checkout -b $NEW_BRANCH origin/$NEW_BRANCH
	git pull

	# ну и при необходимости запускаем любые дополнительные процедуры, например:
	npm install
	grunt production > /dev/null 2>&1
	composer install
	rm -rf ./tmp/cache/*
	# перезапускаем memcache, nginx, php-fpm и так далее... что только душе угодно :)

fi;

Что в итоге

Весь деплой сводится к тому, чтобы просто нажать на «зелёный треугольник» в соответствующем Tool Window и указать пароль (который можно прописать в xml'ке, если не боитесь, что он «уйдёт» в чужие руки).