В последнее время в посещаемых мною форумах и конференциях часто всплывает тема деплоя php-приложений на production-сервер. Я решил поделиться собственным рецептом.
В распоряжении имеется:
- svn-репозитарий с проектом.
- ftp-доступ к рабочему серверу.
- установленный и готовый к тяжёлой рутине phing
Сценарий работ следующий:
- Сделать экспорт кода из svn
- Подготовить его к загрузке (например убрать комментарии, “склеить” мелкие в один большой, удалить лишнее и т.п.)
- Проверить на наличие ошибок (они могли попасть в репозитарий или образоваться на предыдущем шаге)
- Закачать полученное на рабочий сервер.
Всё вроде бы просто, но смущает один момент – экспорт всего и вся. Со времени последнего обновления на сервере в репозитарии обновились 3 файла, а экспортировать нужно всё? Никак нет! Экспортировать будем только то, что изменилось. То есть первый шаг сценария будет выглядеть так:
- Получить последнюю версию проекта на сервере – RevFrom
- Получить последнюю версию проекта в репозитарии – RevTo
- Экспортировать файлы, которые изменились или добавились 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. Но использовать всё как есть у меня рука не поднялась.
При загрузке файлов на сервер возникает проблема, что на нём скапливаются ненужные файлы, которые были в свое время успешно удалены на девелоперской машине. Вообщем через некоторое время сервер превращается в помойку. Моё решение было следующим:
при сравнении версий в svn я составлял 2 списка файлов, один для новых и измененных файлов, другой для удаленных и экспортировал новые и измененные файлы из svn. Затем я написал 2 новых таска FTPDelete и FTPUpload, один удалял файлы на сервере, другой – добавлял по созданным ранее спискам. Таким образом на сервере остается проект идентичный head’у в svn’е.
Еще один момент. Файл revision.txt хранящийся на сервере лучше забирать опять таки через ftp. Это уменьшит число входных данных. А ради секурности можно запретить revision.txt в .htaccess. Кстати, для всех таксов использующих ftp лучше создать базовый класс типа FTPBaseTask, в котором будут храниться методы для коннекта, реконнекта и дисконнекта, а также геттеры и сеттеры свойств типа user, passwd, host, port и mode. Благодарю за внимание.
FtpDelete я тоже было сделал, но потом решил, что цена ошибки будет слишком велика
Да и в моей реальности файлы удаляются очень редко, настолько редко, что этим можно пренебречь (при случае удалить руками).
По поводу revision.txt – да конечно, можно и через ftp его забирать, но я не вижу ничего криминального в публичности этого файла (если я не прав – объясните где я заблуждаюсь?), а поскольку URL проекта уже есть в properties (он нужен и для других целей), то было грех им не воспользоваться.
P.S.
FtpBaseTask в реальности конечно же есть а здесь я привёл минимально-возможный код, которым смог бы воспользоваться непосвящённый.
Спасибо за отзыв!
А что это за projectlastrev таск? Можно ли ознакомиться с его исходниками? Не совсем понятно зачем он нужен. В этом таске происходит считывание текущего номера ревизии проекта? Тогда зачем указывается урл ?
Да, конечно – вот он.