追記
2015 03/25 14:20頃に末尾に追記しました
表題の通り。capistrano3でデプロイするとき、リポジトリのurlは
config/deploy.rb
にこういう感じで書くと思う。
set :scm, :git set :repo_url, 'git@github.com:MYNAME/MYPROJECT.git'
ここで、リポジトリサーバの引っ越しとかで後からrepo_url
を変更したくなったとする。というかしたくなった。
set :repo_url, 'git@myGitServer:MYNAME/MYPROJECT.git'
この時、普通にrepo_url
を書き換えるだけではダメだったのと、とりあえずそれをなんとかするためにcapのタスクを書いた話をする。capistrano詳しい人がいたらもうちょっとよい解決策教えてください。
repo_urlを書き換えるだけではダメ
なぜ普通にrepo_url
を書き換えるだけではダメだったのかという話を先にする。
cap3でgitデプロイすると、デプロイ先のサーバ上でrepo_url
からgit clone
して、そこから実際のリリース先のpathに対してgit clone
している。このgitミラーは2回目以降のデプロイではそのまま使われる(git remote update
して同期する)。
読んでてだいたい想像がついてきたのではと思うけど、2回目以降のデプロイでgitミラーが存在するか確認するときに、ミラーのgit remote
とCapfileのrepo_url
が一致するかは確認していない。なので、repo_url
だけを後から変更しても、実際のデプロイでは変更前のリポジトリからデプロイされるのだった。
冒頭に書いた例だといつまでたってもMyGitServerではなくてgithubからデプロイされる。
capistrano3によるgit デプロイ
上記の挙動自体はcap
を実行してログを眺めてるとだいたい想像つくけど、なんでこうなってるかをソースを追いかけた。
cap3でcap production deploy
した時に実際に実行されるタスクは、公式のドキュメンテーションを見ると分かるようにこういう感じになっている。
deploy:starting - start a deployment, make sure everything is ready deploy:started - started hook (for custom tasks) deploy:updating - update server(s) with a new release deploy:updated - updated hook deploy:publishing - publish the new release deploy:published - published hook deploy:finishing - finish the deployment, clean up everything deploy:finished - finished hookhttp://capistranorb.com/documentation/getting-started/flow/
具体的にdeploy:updatingで実行されるタスクはこういう感じ。
task :updating => :new_release_path do invoke "#{scm}:create_release" invoke "deploy:set_current_revision" invoke 'deploy:symlink:shared' end
git:create_release
はgit:update
=> git:clone
と依存しており、ソースコードはこういう感じ。
desc 'Clone the repo to the cache' task clone: :'git:wrapper' do on release_roles :all do if strategy.test info t(:mirror_exists, at: repo_path) else within deploy_path do with fetch(:git_environmental_variables) do strategy.clone end end end end end desc 'Update the repo mirror to reflect the origin state' task update: :'git:clone' do on release_roles :all do within repo_path do with fetch(:git_environmental_variables) do strategy.update end end end end desc 'Copy repo to releases' task create_release: :'git:update' do on release_roles :all do with fetch(:git_environmental_variables) do within repo_path do execute :mkdir, '-p', release_path strategy.release end end end end
strategy.test
が真ならgitミラーが存在するとみなしてそこからupdateして、偽ならgitミラーが存在しないのでstrategy.clone
でcloneしてくる。strategyとはなんぞやというと、これはcapistrano/git.rb
の中でmodule DefaultStrategy
として定義されてる。このstrategy.testのコードを読むと、
def test test! " [ -f #{repo_path}/HEAD ] " end
となっていて、見事にHEADの存在しか見ていない。なのでrepo_urlが変わっていてもcapistrano3のgitデプロイでは追従してくれないのである。
workaround
capistranoにはあんまり明るくないので、世の中にはなんか格好良くその辺対応してるStrategyがあるのかなーと思ったけど、とりあえず自前でgit:clone
の手前でrepo_url
が一致してるかを確かめることにした。雑だからコピペして使うのやめた方がいい。
namespace :git do task :delete_repo_if_repo_url_mismatch do on release_roles :all do if strategy.test if capture("cd #{repo_path}; git remote -v show").split("\n").select{ |i| i.include?("#{fetch(:repo_url)}") }.empty? puts "repo url mismatch!" execute(:rm, "-rf", repo_path); else # puts "repo url match, so ALRIGHT*" end end end end before :clone, :delete_repo_if_repo_url_mismatch end
雑だけど、まぁとりあえず今のところはこれで要件は達成できそう。
結び
capistrano3について知見をお持ちの方がいらしたらもっとかっこいいソリューションとか是非共有いただけると幸いです。
追記(03/25 14:20)
同じリポジトリのurlが変わっただけ(=中身は同じリポジトリ)であるなら、mirrorを消さなくてもgit remote set-url
するだけでいいじゃん、との指摘をもらって、確かにと思ったので書き直したらタスクちょっとまともになった。
namespace :git do task :sync_remote_url do on release_roles :all do if strategy.test within repo_path do if capture(:git, "remote", "-v", "show").split("\n").select{ |i| i.include?("#{fetch(:repo_url)}") }.empty? puts "repo url mismatch!" execute :git, "remote", "set-url", "origin", "#{fetch(:repo_url)}" else # puts "repo url match, so ALRIGHT*" end end end end end before :clone, :sync_remote_url end