Last active
February 20, 2017 11:43
-
-
Save Yoshyn/353f6518dec413ae9bfcc3b99e1fe22b to your computer and use it in GitHub Desktop.
Capistrano : Set of task/script that can be add to deploy.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Contains : | |
# * deploy_git_sync.rb : stop deployment if git is not synchronized remote branch. | |
# * deploy_lock_per_user.rb : stop deployment if somebody is already deploying the application. | |
# * deploy_smart_asset.rb : Avoid compiling asset if not needed. | |
# * passenger_restart.rb : Restart passenger after deploy. | |
# * sidekiq_restart.rb : Restart sidekiq after deploy. | |
# Question? Why this and not use gem ? | |
# -> Answer : Why use a gem for this? :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Sync with the deploy branch | |
namespace :deploy do | |
desc "Makes sure local git is in sync with remote." | |
task :check_revision do | |
on roles :all, exclude: :no_release do |server| | |
unless `git rev-parse HEAD` == `git rev-parse origin/#{fetch(:branch)}` | |
info "WARNING: HEAD is not the same as origin/#{fetch(:branch)}" | |
info "Run `git push` to sync changes." | |
exit | |
end | |
end | |
end | |
before :deploy, "deploy:check_revision" | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Avoid several user launch deployment in same time | |
# Set defaults. | |
set :lock_expire_duration, fetch(:lock_expire_duration, (30 * 60)) # 30 minutes | |
set :deploy_lock_filename, fetch(:deploy_lock_filename, 'capistrano.lock.yml') | |
set :deploy_lock_filepath, -> { shared_path.join(fetch(:deploy_lock_filename)) } | |
namespace :deploy do | |
namespace :lock do | |
def fetch_current_lock(server) | |
current_lock = if test("[ -f #{fetch(:deploy_lock_filepath)} ]") | |
c_lock_data = capture "cat #{fetch(:deploy_lock_filepath)}" | |
YAML.load(c_lock_data) if c_lock_data && c_lock_data.size > 0 | |
end | |
set :current_lock, current_lock | |
end | |
task :check do | |
on roles :all, exclude: :no_release do |server| | |
fetch_current_lock(server) | |
if !(current_lock = fetch(:current_lock)) | |
info "No lock file #{fetch(:deploy_lock_filename)} found on #{server}." | |
else | |
current_time = Time.now.getutc.to_i | |
warn "Found #{fetch(:deploy_lock_filename)} on #{server}." | |
warn "This lock was created by #{current_lock[:user]} at #{Time.at(current_lock[:created_at]).strftime('%Y-%m-%d-%H:%M')}." | |
if (remain_seconds = (current_lock[:expire_at].to_i - current_time)) > 0 | |
info "The lock expire in ~#{remain_seconds/60} minute(s)." | |
info "You can manualy remove this lock : cap #{fetch(:rails_env)} deploy:lock:remove" | |
error "Aborting deployment !" | |
exit 1 | |
else | |
info " -> This lock file is expired, removing..." | |
invoke "deploy:lock:remove" | |
end | |
end | |
end | |
end | |
task :create do | |
on roles :all, exclude: :no_release do |server| | |
user = `hostname`.strip | |
created_at = Time.now.getutc.to_i | |
info "Create a new lock at #{Time.at(created_at).strftime('%Y-%m-%d-%H:%M')} on #{server} for #{user}" | |
current_lock = { | |
user: user, | |
created_at: created_at, | |
expire_at: created_at + fetch(:lock_expire_duration) | |
} | |
upload! StringIO.new(current_lock.to_yaml), fetch(:deploy_lock_filepath) | |
end | |
end | |
task :remove do | |
on roles :all, exclude: :no_release do |server| | |
info "Remove lock on #{server}" | |
execute :rm, "-f #{fetch(:deploy_lock_filepath)}" | |
end | |
end | |
end | |
before "deploy:starting", "deploy:lock:check" | |
before "deploy:starting", "deploy:lock:create" | |
after "deploy:finished", "deploy:lock:remove" | |
after 'deploy:failed', :failed do | |
invoke "deploy:lock:remove" | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Into production.rb file ! | |
# frozen_string_literal: true | |
set :branch, "production" | |
set :migration_role, :app | |
set :migration_servers, :app | |
set :assets_roles, :web | |
set :deploy_to, "/path/to/the/project/#{fetch(:application)}/#{fetch(:branch)}" | |
set :conditionally_migrate, true | |
server 'XX.XX.XX.XXX', user: 'my_user', roles: %w{app} | |
server 'XX.XX.XX.XXX', user: 'my_user', roles: %w{web} | |
# set the locations that we will look for changed assets to determine whether to precompile | |
set :assets_dependencies, %w(app/assets lib/assets vendor/assets Gemfile.lock) | |
# clear the previous precompile task | |
Rake::Task["deploy:assets:precompile"].clear_actions | |
class PrecompileRequired < StandardError; end | |
namespace :deploy do | |
namespace :assets do | |
desc "Precompile assets" | |
task :precompile do | |
on release_roles(fetch(:assets_roles)) do | |
within release_path do | |
with rails_env: fetch(:rails_env) do | |
begin | |
# find the most recent release | |
latest_release = capture(:ls, '-xr', releases_path).split[1] | |
# precompile if this is the first deploy | |
raise PrecompileRequired unless latest_release | |
latest_release_path = releases_path.join(latest_release) | |
# precompile if the previous deploy failed to finish precompiling | |
execute(:ls, latest_release_path.join('assets_manifest_backup')) rescue raise(PrecompileRequired) | |
fetch(:assets_dependencies).each do |dep| | |
# execute raises if there is a diff | |
execute(:diff, '-Naur', release_path.join(dep), latest_release_path.join(dep)) rescue raise(PrecompileRequired) | |
end | |
info("Skipping asset precompile, no asset diff found") | |
# copy over all of the assets from the last release | |
execute(:cp, '-r', latest_release_path.join('public', fetch(:assets_prefix)), release_path.join('public', fetch(:assets_prefix))) | |
rescue PrecompileRequired | |
execute(:rake, "assets:precompile") | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace :deploy do | |
desc "Restart Web server. Avoid call this : (cap env deploy no_restart_web=true)" | |
task :restart_web do | |
on roles(:web) do |host| | |
display_passenger_informations() | |
restart_web = ENV['restart_web'] || ask_in("Restart web", %w{yes no}) | |
if restart_web.start_with?('y') | |
info "Restart passenger on host : #{host} (role=web)" | |
execute 'passenger-config', "restart-app --rolling-restart #{current_path}" | |
end | |
end | |
end | |
after :finished, "deploy:restart_web" | |
end | |
def ask_in(title, choises) | |
input_tmp = nil | |
while !choises.include? input_tmp | |
ask(:input, "#{title} (#{choises.join('|')}) ? ") | |
input_tmp = fetch(:input) | |
end | |
input_tmp | |
end | |
def display_passenger_informations() | |
passenger_pids = capture("ps aux | grep Passenger | grep RubyApp | grep -v grep | awk '{print $2}'", raise_on_non_zero_exit: false).split("\n") | |
if passenger_pids && passenger_pids.size > 0 | |
warn "Found running passenger process !" | |
passenger_folders_timestamps = capture("pwdx #{passenger_pids.join(' ')}").split("\n") | |
passenger_folders_with_pids = passenger_folders_timestamps.inject(Hash.new { |hash, key| hash[key] = [] }) do |acc, sft| | |
match_data = sft.match(/^(\d+):\s+\/.*\/(\d+)$/) | |
acc[match_data[2].to_i] << match_data[1] | |
acc | |
end | |
releases = capture("ls #{deploy_path}/releases", raise_on_non_zero_exit: false).split("\n").map(&:to_i).sort.reverse | |
releases.each do |release| | |
release_info = capture("cat #{deploy_to}/revisions.log | grep #{release}", raise_on_non_zero_exit: false) | |
if release_info.to_s.size == 0 | |
info "=> \e[31m#{release}\e[0m : Release folder exist but does not appear into revisions.log !" | |
elsif !(pids = passenger_folders_with_pids[release]).empty? | |
info "=> \e[35m#{release_info}\e[0m : Passenger process are running with PID : #{pids.join(', ')}" | |
else | |
info "=> #{release_info}" | |
end | |
end | |
else | |
info "No Passenger Process seems to be running." | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace :deploy do | |
desc "Clean sidekiq log on app server. Avoid call this : (cap env deploy no_remove_sidekiq_log=true)" | |
task :clean_sidekiq_log do | |
on roles(:app) do |host| | |
info "Remove the shared/log/sidekiq.log on host : #{host} (role=app)" | |
sidekiq_path = "#{current_path}/shared/log/sidekiq.log" | |
if test("[ -f #{sidekiq_path} ]") | |
execute :rm, "#{sidekiq_path}" | |
end | |
end | |
end | |
desc "Kill and reset sidekiq, relaunch queue and restart process" | |
task :sidekiq_manage do | |
on roles(:app) do |host| | |
within release_path do | |
with rails_env: fetch(:rails_env) do | |
display_sidekiq_informations() | |
manage_sidekiq = ENV['manage_sidekiq'] || ask_in("Manage sidekiq (Will kill sidekiq process)", %w{yes no}) | |
if manage_sidekiq.start_with?('y') | |
info "Sidekiq : Kill sidekiq process on host : #{host} (role=app)" | |
execute :rake, "sidekiq:kill_process" | |
execute :rake, "sidekiq:clean_tmp_files" | |
info "Sidekiq : start_process on host : #{host} (role=app)" | |
execute :rake, "sidekiq:start_process" | |
end | |
end | |
end | |
end | |
end | |
after :finishing, "deploy:clean_sidekiq_log" | |
after :finished, "deploy:sidekiq_manage" | |
end | |
def display_sidekiq_informations() | |
sidekiq_pids = capture("ps aux | grep sidekiq | grep -v grep | awk '{print $2}'", raise_on_non_zero_exit: false).split("\n") | |
if sidekiq_pids && sidekiq_pids.size > 0 | |
warn "Found running sidekiq process !" | |
sidekiq_folders_timestamps = capture("pwdx #{sidekiq_pids.join(' ')}").split("\n") | |
sidekiq_folders_with_pids = sidekiq_folders_timestamps.inject(Hash.new { |hash, key| hash[key] = [] }) do |acc, sft| | |
match_data = sft.match(/^(\d+):\s+\/.*\/(\d+)$/) | |
acc[match_data[2].to_i] << match_data[1] | |
acc | |
end | |
releases = capture("ls #{deploy_path}/releases", raise_on_non_zero_exit: false).split("\n").map(&:to_i).sort.reverse | |
releases.each do |release| | |
release_info = capture("cat #{deploy_to}/revisions.log | grep #{release}", raise_on_non_zero_exit: false) | |
if release_info.to_s.size == 0 | |
info "=> \e[31m#{release}\e[0m : Release folder exist but does not appear into revisions.log !" | |
elsif !(pids = sidekiq_folders_with_pids[release]).empty? | |
info "=> \e[35m#{release_info}\e[0m : Sidekiq process are running with PID : #{pids.join(', ')}" | |
else | |
info "=> #{release_info}" | |
end | |
end | |
execute :rake, "sidekiq:stats" | |
else | |
info "No sidekiq process seems to be running." | |
end | |
end | |
############# RAKE TASKS ############## | |
execute :rake, "sidekiq:kill_process" | |
namespace :sidekiq do # FIX_ME : 10 busy is a dummy grep search | |
desc "Kill all sidekiq_process" | |
task kill_process: :environment do | |
ps = Sidekiq::ProcessSet.new | |
ps.each { |p| p.quiet! }; sleep(2) | |
ps.each { |p| p.stop! }; sleep(1) | |
system %x( | |
pid=$(ps -ef | grep sidekiq | grep '10 busy' | grep -v tmux | grep -v sidekiq:kill_process | grep -v grep | awk '{ print $2 }'); | |
if ! [ -z "$pid" ]; then | |
kill -9 $pid | |
fi | |
) | |
end | |
end | |
execute :rake, "sidekiq:clean_tmp_files" | |
namespace :sidekiq do | |
desc "Clean all sidekiq temporary File in the tmp directory" | |
task clean_tmp_files: :environment do | |
system %x( | |
cd #{release_path}/current/tmp/; | |
rm -rf uploads/* && | |
rm -f /tmp/open-uri* | |
rm -f /tmp/mini_magick*; | |
rm -f /tmp/magick-*; | |
) | |
end | |
end | |
execute :rake, "sidekiq:start_process" | |
namespace :sidekiq do | |
desc "Start all the process" | |
task start_process: :environment do | |
system "bundle exec sidekiq -e #{Rails.env} -C 'config/sidekiq.yml' -d" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment