Capistrano3で `deploy_via :copy` 相当のデプロイをする（CakePHP3用サンプル付き）

Capistrano3では、Capistrano2にあった `deploy_via :copy` が標準で導入されず、プライベートリポジトリからのソースコードのデプロイがしにくい状況でした。

当時、涙をのんでCapistrano2の継続利用を選択した人も多いと思います。

しかし、今はGitのリポジトリであれば、capistrano-scm-gitcopy.gem を使って簡単に `deploy_via :copy` 相当のデプロイが可能です。

https://github.com/xuwupeng2000/capistrano-scm-gitcopy

というわけで、手順を追って環境を作ってみます。

## Capistrano3の環境を構築する

適当なディレクトリを作ってGemfileを作成します。

```sh
$ bundle init
```

Gemfileには `capistrano` だけでなく、`capistrano-scm-gitcopy` も記載します。

```ruby
source "https://rubygems.org"

gem "capistrano"
gem "capistrano-scm-gitcopy"
```

Capistrano3をインストールして初期設定をします。

```sh
$ bundle install
$ bundle exec cap install
```

Capistrano2の `capistrano-ext-multistage` 相当の環境切り替えは既にCapistrano3では標準搭載されているので、それを利用する形で実施します。

## ディレクトリ構成

```sh
.
├── Capfile
├── Gemfile
├── Gemfile.lock
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   ├── deploy.rb
│   ├── production
│   │   └── app_local.php
│   └── staging
│       └── app_local.php
├── lib
│   └── capistrano
│       └── tasks
└── log
    └── capistrano.log
```

> Capistrano3では、`app_local.php` のようなファイルはリポジトリに含めずデプロイ先のsharedディレクトリ置くことを推奨していますが、今回はローカルファイルをアップロードをする方法を取ります。

## deploy.rb

かなり雑ですが、CakePHP3の場合のサンプルはこんな感じでしょうか。

```rb
# config valid only for current version of Capistrano
lock '3.6.1'

set :application, 'my_app_name'
set :repo_url, 'git@github.com:k1LoW/my_app_name.git'

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

set :deploy_to, '/var/www/deploy/to'
set :scm, :gitcopy

set :pty, true

append :linked_dirs, 'tmp/cache/models', 'tmp/cache/persistent', 'tmp/cache/views', 'logs'

set :keep_releases, 5

namespace :deploy do
  desc 'Setup CakePHP app'
  task :setapp do
    on roles(:all) do
      within release_path do
        # @notice https://github.com/capistrano/capistrano/issues/719
        execute :sudo, :chmod, '-R', '777', 'tmp/'
        execute :rm, '-rf', 'tmp/cache/models/*'
        execute :rm, '-rf', 'tmp/cache/persistent/*'
        execute :rm, '-rf', 'tmp/cache/views/*'
        execute :curl, '-s', 'http://getcomposer.org/installer', '|', 'php'
        execute :php, 'composer.phar', 'install', '--no-dev'
      end
      upload!("#{File.dirname(__FILE__)}/#{fetch(:stage)}/app_local.php", "#{release_path}/config/app_local.php")
    end
  end

  desc 'Migrate database'
  task :migrate do
    on roles(:all) do
      within release_path do
        execute 'bin/cake', 'migrations', 'migrate'
      end
    end
  end
end

after 'deploy:symlink:linked_dirs', 'deploy:setapp'
after 'deploy:setapp', 'deploy:migrate'
```

ポイントは `set :scm, :gitcopy` ですね。これで `deploy_via :copy` と同じような動きをします。

あとは

```sh
$ bundle exec cap staging deploy
```

や

```sh
$ bundle exec cap production deploy
```

を実行したら、プライベートリポジトリからソースコードが一旦ローカルにcloneされて、tarでまとめてデプロイが開始されます。

これで、CakePHP3のBakerの皆さんも思う存分Capistrano3が使えますね！

---

## 以下、注意点

### `within *_path do` が withinじゃなくなる問題

スペースが入っている文字列でコマンドを表すと発動します。これはバックエンドのSSHKitの仕様っぽいので、しっかり分割しましょう。

### 実行時にSkipping taskエラーがでる問題

```bash
Skipping task `gitcopy:set_current_revision'.
Capistrano tasks may only be invoked once. Since task `gitcopy:set_current_revision' was previously invoked, invoke("gitcopy:set_current_revision") at /Users/k1low/.anyenv/envs/rbenv/versions/2.3.1/lib/ru
by/gems/2.3.0/gems/capistrano-3.6.1/lib/capistrano/tasks/deploy.rake:228 will be skipped.
If you really meant to run this task again, first call Rake::Task["gitcopy:set_current_revision"].reenable
THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.
https://github.com/capistrano/capistrano/issues/1686
```

これは、Rakeの `#invoke` は「1回実行すると、`reenable`を明示的に実行しないかぎり、同じタスクが2回目以降実行できなくなる」仕様らしいのですが、それがCapistranoでの利用想定と合っていないことによる問題のようです。
（Capistranoとしては、[対応を議論中のようにみえます](https://github.com/capistrano/capistrano/issues/1686
))。

実際は capistrano-scm-gitcopy.gem でエラーが出ているのですが、今のところ問題は発生していないので大丈夫だと思います。