Deployer で複数プロジェクトのデプロイを行う(Version 7対応版)

以前に「PHP 製のデプロイツール Deployer で複数プロジェクトのデプロイを行う」で Deployer を使った複数プロジェクトのデプロイ方法を紹介しましたが、最新版の Version 7 では、使い勝手がだいぶ変わっていたので、改めてサンプルコードを紹介したいと思います。

想定しているのは、1つのサーバで複数のサイトを運用していて、そのサーバに SSH でログインしてデプロイを行うケースです。ローカル環境からリモートサーバにデプロイする場合の設定が省かれているので注意してください。

前回の記事では minimum_version をメジャーバージョンでしか設定できませんでしたが、今回は 1.2.0 のように設定すると、1.2.0 以降のみが表示されるように改良しています。実際に使ってみると、メジャーバージョンを上げることってなかなか少ないので、メジャーバージョンだけしか指定できないと選択肢が増えすぎて選びにくかったので。

<?php

namespace Deployer;

require 'recipe/common.php';

// スクリプトがファイルを書き込む場合の設定
// 詳しくは https://deployer.org/docs/7.x/recipe/deploy/writable を参照
set('writable_mode', 'chown');
//set('writable_use_sudo', true);

// ホストの設定。Version 7.x では必須
localhost();

desc('Deploy Application');
task('deploy', function () {

    // デプロイするものをユーザー選択式にする
    $hostname = askChoice('デプロイするものを選択してください:',
        [
            'prod_foo', 'stg_foo', 'prod_bar', 'stg_bar'
        ]
    );

    // 各デプロイの設定
    switch ($hostname) {
        case 'prod_foo':
            set('repository', 'git@github-foo:example/foo.git');
            set('deploy_path', '/var/www/prod_foo');
            set('labels', ['env' => 'prod', 'name' => 'foo']);
            set('minimum_version', '1.0.0');
            break;
        case 'stg_foo':
            set('repository', 'git@github-foo:example/foo.git');
            set('deploy_path', '/var/www/stg_foo');
            set('branch', 'staging');
            set('labels', ['env' => 'stg', 'name' => 'foo']);
            break;
        case 'prod_bar':
            set('repository', 'git@github-bar:example/bar.git');
            set('deploy_path', '/var/www/prod_bar');
            set('labels', ['env' => 'prod', 'name' => 'bar']);
            set('minimum_version', '0.0.0');
            break;
        case 'stg_bar':
            set('repository', 'git@github-bar:example/bar.git');
            set('deploy_path', '/var/www/stg_bar');
            set('labels', ['env' => 'stg', 'name' => 'bar']);
            set('branch', 'staging');
            break;
        default:
            exit();
    }

    // 本番の場合にはタグを取得して選択式にする
    on(select('env=prod'), function ($host) {
        set('branch', function () {

            $minimum_version_ary = explode('.', get('minimum_version'));
            $minimum_version_int = (int)sprintf('%02d%02d%02d', $minimum_version_ary[0], $minimum_version_ary[1], $minimum_version_ary[2]);

            $repository = get('repository');
            exec("git ls-remote --tags -h {$repository}", $outputLines);
            $versions = [];
            foreach ($outputLines as $line) {
                if (preg_match('#refs/tags/v([0-9]+?)\.([0-9]+?)\.([0-9]+?)$#', $line, $matches)) {
                    // minimum_version 未満のバージョンは表示しない
                    $compare = (int)sprintf('%02d%02d%02d', $matches[1], $matches[2], $matches[3]);

                    if ($compare < $minimum_version_int) {
                        continue;
                    }

                    $versions[] = [
                        'major' => $matches[1],
                        'minor' => $matches[2],
                        'patch' => $matches[3]
                    ];
                }
            }
            // タグをバージョン順に並び替える
            $major = array_column($versions, 'major');
            $minor = array_column($versions, 'minor');
            $patch = array_column($versions, 'patch');

            array_multisort($major, SORT_ASC, $minor, SORT_ASC, $patch, SORT_ASC, $versions);

            $tags = array_map(static function ($array) {
                return 'v'.$array['major'].'.'.$array['minor'].'.'.$array['patch'];
            }, $versions);

            $tag = askChoice("デプロイするバージョンを選択してください:", $tags);
            if ( ! askConfirmation("本番環境を {$tag} にして問題ありませんか?", false)) {
                writeln('deploy was stopped');
                exit();
            }

            return $tag;
        });

    });

    on(select('name=foo'), function ($host) {
        // foo プロジェクトごとの設定はここで
        set('shared_files', [
            'public/.htaccess',
        ]);

        invoke('deploy:prepare');
        invoke('deploy:symlink');
        invoke('deploy:unlock');
        invoke('deploy:cleanup');
    });

    on(select('name=bar'), function ($host) {
        // bar プロジェクトごとの設定はここで
        set('shared_files', [
            'public/.htaccess',
        ]);

        invoke('deploy:prepare');
        invoke('deploy:symlink');
        invoke('deploy:unlock');
        invoke('deploy:cleanup');
    });

});
after('deploy', 'deploy:success');

// [Optional] if deploy fails automatically unlock.
fail('deploy', 'deploy:failed');
after('deploy:failed', 'deploy:unlock');

Version 7.x の特徴のひとつは labels と on ~ select を使った処理の分岐ができるようになったことです。これによって、環境ごと(本番環境、開発環境)、プロジェクトごとの設定を書き分けるのが楽になりました。

Version 6.x からのバージョンアップで注意が必要なのは、Deployer が用意しているタスクの中に名前が同じにもかかわらず処理が変更されているものがあることです。例えば、deploy:prepare は Version 6.x ではデプロイ先のディレクトリを作成するタスクでしたが(ソース)、Version 7.x では deploy:info、deploy:setup、deploy:lock、deploy:release、deploy:update_code、deploy:shared、deploy:writable をまとめたタスクとなっています(ドキュメントソース)。

そのため、Version 6.x のように、deploy:prepare と deploy:lock を実行すると、deploy:prepare の中で既に deploy:lock が実行されているため、次の deploy:lock がエラーになってしまい、デプロイが完了しません。Version 6.x の deploy:prepare に相当するのは、Version 7.x では deploy:setup です。

このように変更点に注意は必要ですが、全体的には機能が整理されてわかりやすくなった気がします。