В последнее время в посещаемых мною форумах и конференциях часто всплывает тема деплоя php-приложений на production-сервер. Я решил поделиться собственным рецептом.

В распоряжении имеется:

  1. svn-репозитарий с проектом.
  2. ftp-доступ к рабочему серверу.
  3. установленный и готовый к тяжёлой рутине phing

Сценарий работ следующий:

  1. Сделать экспорт кода из svn
  2. Подготовить его к загрузке (например убрать комментарии, “склеить” мелкие в один большой, удалить лишнее и т.п.)
  3. Проверить на наличие ошибок (они могли попасть в репозитарий или образоваться на предыдущем шаге)
  4. Закачать полученное на рабочий сервер.

Всё вроде бы просто, но смущает один момент – экспорт всего и вся. Со времени последнего обновления на сервере в репозитарии обновились 3 файла, а экспортировать нужно всё? Никак нет! Экспортировать будем только то, что изменилось. То есть первый шаг сценария будет выглядеть так:

  1. Получить последнюю версию проекта на сервере – RevFrom
  2. Получить последнюю версию проекта в репозитарии – RevTo
  3. Экспортировать файлы, которые изменились или добавились c RevFrom до RevTo

Версию рабочего проекта, не мудрствуя лукаво, предлагаю хранить в текстовом файле. Например revision.txt. Версию проекта в репозитарии поможет узнать SvnLastRevisionTask. Неясным остался лишь момент – каким образом выяснить какие файлы изменились. Те, кто пользовались TortoiseSVN наверняка знают о чудесной его возможности – сравнить две ревизии и экспортировать “разницу”. Но svn в чистом виде такой возможности не предоставляет. Так что пришлось немного поработать.

Результатом работы стал класс – SvnExportRevisionDiffTask Принцип его работы таков: получаю список файлов для каждой ревизии в виде xml, потом эти списки сравниваются на предмет изменившихся или добавленных файлов. Список получается путём вызова svn, т.к. более удобного способа не нашлось.

Закачивать файлы будем посредством task-а, который я опубликовал ранее – FtpUploadTask.

Ну вот все вроде бы в сборе и можно приступать с сборке: раскладываем php-классы в соответствующие места, берём файл example.xml

<?xml version='1.0'?>
<project name='Deploy' default='build' basedir='./' description='Demo phing-ftp-deploy'>    
        <target name='propset'>        
        <property name='projectUrl' value='http://example.com/project_root/' />
        <property name='revisionFile' value='revision.txt' />
        <projectlastrev url='${projectUrl}' revisionFile='${revisionFile}' propertyName='fromrev' />      
        <property name='svnroot' value='http://svn.example.com/project/branches/stable/' />
        <property name='svnuser' value='joe' />
        <property name='svnpassword' value='s3cre7' />
        <property name='outputdir' value='project-temp' />
        <svnlastrevision username='${svnuser}' password='${svnpassword}' repositoryurl='${svnroot}' propertyName='torev' />
        <property name='hostname' value='ftp.example.com' />
        <property name='user' value='ftpuser@example.com' />
        <property name='passwd' value='s3cr3tk3y' />
        <property name='dstdir' value='project_root' />
        <property name='overwrite' value='true' />
    </target>
    <target name='build' depends='propset,diff,lint'>   
        <ftpupload host='${hostname}' username='${user}' password='${passwd}' targetDir='${dstdir}' mode='bin' overwriteExisten='${overwrite}'>
            <fileset dir='${outputdir}'>
                <include name='**' />
                <exclude name='config.php.dist' />
                <exclude name='examples/**' />
            </fileset>
        </ftpupload>
    </target>    
    <target name='diff' depends='propset'>
         <delete includeemptydirs='true'>
            <!-- сначала очистить директорию, в которую будут экспортироваться файлы -->
            <fileset dir='.'>
                <include name='${outputdir}/**' />
            </fileset>
         </delete>
         <mkdir dir='${outputdir}' />
         <!-- сохранить номер текущей ревизии в файл -->
         <echo file='${outputdir}/${revisionFile}' message='${torev}' />
         <!-- экспортировать нужные файлы -->
         <svndiff toDir='${outputdir}' svnpath='/usr/local/subversion/bin/svn' fromRevision='${fromrev}' toRevision='${torev}' force='true' username='${svnuser}' password='${svnpassword}' repositoryurl='${svnroot}' />
    </target>
    <target name='lint'>
        <phplint haltOnFailure='true'>
            <fileset dir='${outputdir}'>
                <include name='**/*.php'/>
                <!-- файлы в pear::php_compat проверять в php5 не надо -->
                <exclude name='**/Php/Compat/**/*.php' />
            </fileset>        
        </phplint>
        <jslint haltOnFailure='true'>
            <fileset dir='${outputdir}'>
                <include name='**/*.js'/>
            </fileset>        
        </jslint>
    </target>
</project>

Открываем консоль, набираем phing -f example.xml и смотрим как вашу работу выполняет за вас бездушная машина…

Кстати говоря, эту задачу можно запускать не только вручную, но и например с помощью post-commit hook-а на репозитарии…

Данный сценарий не затрагивает изменений в БД и позволяет синхронизировать только файлы, но и это, согласитесь, уже кое-что. А быть может у вас есть идеи, как обновить БД?

phing.tasks.ext.SvnExportRevisionDiffTask
Tagged on:         

4 thoughts on “phing.tasks.ext.SvnExportRevisionDiffTask

  • 26.05.2008 at 22:22
    Permalink

    Хорошая статья, именно с неё я врубился что такое phing. Но использовать всё как есть у меня рука не поднялась.
    При загрузке файлов на сервер возникает проблема, что на нём скапливаются ненужные файлы, которые были в свое время успешно удалены на девелоперской машине. Вообщем через некоторое время сервер превращается в помойку. Моё решение было следующим:
    при сравнении версий в svn я составлял 2 списка файлов, один для новых и измененных файлов, другой для удаленных и экспортировал новые и измененные файлы из svn. Затем я написал 2 новых таска FTPDelete и FTPUpload, один удалял файлы на сервере, другой – добавлял по созданным ранее спискам. Таким образом на сервере остается проект идентичный head’у в svn’е.
    Еще один момент. Файл revision.txt хранящийся на сервере лучше забирать опять таки через ftp. Это уменьшит число входных данных. А ради секурности можно запретить revision.txt в .htaccess. Кстати, для всех таксов использующих ftp лучше создать базовый класс типа FTPBaseTask, в котором будут храниться методы для коннекта, реконнекта и дисконнекта, а также геттеры и сеттеры свойств типа user, passwd, host, port и mode. Благодарю за внимание.

  • 08.06.2008 at 16:51
    Permalink

    FtpDelete я тоже было сделал, но потом решил, что цена ошибки будет слишком велика 🙂
    Да и в моей реальности файлы удаляются очень редко, настолько редко, что этим можно пренебречь (при случае удалить руками).
    По поводу revision.txt – да конечно, можно и через ftp его забирать, но я не вижу ничего криминального в публичности этого файла (если я не прав – объясните где я заблуждаюсь?), а поскольку URL проекта уже есть в properties (он нужен и для других целей), то было грех им не воспользоваться.

    P.S.
    FtpBaseTask в реальности конечно же есть 🙂 а здесь я привёл минимально-возможный код, которым смог бы воспользоваться непосвящённый.

    Спасибо за отзыв!

  • 16.01.2009 at 08:21
    Permalink

    А что это за projectlastrev таск? Можно ли ознакомиться с его исходниками? Не совсем понятно зачем он нужен. В этом таске происходит считывание текущего номера ревизии проекта? Тогда зачем указывается урл ?

  • 16.01.2009 at 09:24
    Permalink

    Да, конечно – вот он.

    «Версию рабочего проекта, не мудрствуя лукаво, предлагаю хранить в текстовом файле. Например revision.txt.»

Leave a Reply