Skip to content

Commit

Permalink
Merge pull request #281 from ndbroadbent/assets_rollback_and_expiry
Browse files Browse the repository at this point in the history
Added support for rolling back assets, and removing expired assets
  • Loading branch information
carsomyr committed Nov 8, 2012
2 parents 9a0303e + 23452d6 commit 5793c33
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 5 deletions.
2 changes: 1 addition & 1 deletion lib/capistrano/configuration/actions/invocation.rb
Expand Up @@ -160,7 +160,7 @@ def run(cmd, options={}, &block)
# or #invoke_command. # or #invoke_command.
def run_tree(tree, options={}) #:nodoc: def run_tree(tree, options={}) #:nodoc:
if tree.branches.empty? && tree.fallback if tree.branches.empty? && tree.fallback
logger.debug "executing #{tree.fallback}" logger.debug "executing #{tree.fallback}" unless options[:silent]
elsif tree.branches.any? elsif tree.branches.any?
logger.debug "executing multiple commands in parallel" logger.debug "executing multiple commands in parallel"
tree.each do |branch| tree.each do |branch|
Expand Down
105 changes: 101 additions & 4 deletions lib/capistrano/recipes/deploy/assets.rb
Expand Up @@ -3,11 +3,15 @@
_cset :asset_env, "RAILS_GROUPS=assets" _cset :asset_env, "RAILS_GROUPS=assets"
_cset :assets_prefix, "assets" _cset :assets_prefix, "assets"
_cset :assets_role, [:web] _cset :assets_role, [:web]
_cset :expire_assets_after, (3600 * 24 * 7)


_cset :normalize_asset_timestamps, false _cset :normalize_asset_timestamps, false


before 'deploy:finalize_update', 'deploy:assets:symlink' before 'deploy:finalize_update', 'deploy:assets:symlink'
after 'deploy:update_code', 'deploy:assets:precompile' after 'deploy:update_code', 'deploy:assets:precompile'
before 'deploy:assets:precompile', 'deploy:assets:update_asset_mtimes'
after 'deploy:cleanup', 'deploy:assets:clean_expired'
after 'deploy:rollback:revision', 'deploy:assets:rollback'


namespace :deploy do namespace :deploy do
namespace :assets do namespace :assets do
Expand All @@ -20,7 +24,7 @@
:assets_prefix variable to match. :assets_prefix variable to match.
DESC DESC
task :symlink, :roles => assets_role, :except => { :no_release => true } do task :symlink, :roles => assets_role, :except => { :no_release => true } do
run <<-CMD run <<-CMD.compact
rm -rf #{latest_release}/public/#{assets_prefix} && rm -rf #{latest_release}/public/#{assets_prefix} &&
mkdir -p #{latest_release}/public && mkdir -p #{latest_release}/public &&
mkdir -p #{shared_path}/assets && mkdir -p #{shared_path}/assets &&
Expand All @@ -39,7 +43,29 @@
set :asset_env, "RAILS_GROUPS=assets" set :asset_env, "RAILS_GROUPS=assets"
DESC DESC
task :precompile, :roles => assets_role, :except => { :no_release => true } do task :precompile, :roles => assets_role, :except => { :no_release => true } do
run "cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile" run <<-CMD.compact
cd -- #{latest_release.shellescape} &&
#{rake} RAILS_ENV=#{rails_env.shellescape} #{asset_env.shellescape} assets:precompile &&
cp -- #{shared_path.shellescape}/assets/manifest.yml #{current_release.shellescape}/assets_manifest.yml
CMD
end

desc <<-DESC
[internal] Updates the mtimes for assets that are required by the current release.
This task runs before assets:precompile.
DESC
task :update_asset_mtimes, :roles => assets_role, :except => { :no_release => true } do
# Fetch assets/manifest.yml contents.
manifest_path = "#{shared_path}/assets/manifest.yml"
manifest_yml = capture("[ -e #{manifest_path.shellescape} ] && cat #{manifest_path.shellescape} || echo").strip

if manifest_yml != ""
manifest = YAML.load(manifest_yml)
current_assets = manifest.to_a.flatten.map {|a| [a, "#{a}.gz"] }.flatten
logger.info "Updating mtimes for ~#{current_assets.count} assets..."
command = "touch -cm -- " << current_assets.map {|a| "#{shared_path}/assets/#{a}".shellescape }.join(' ')
run command, :silent => true
end
end end


desc <<-DESC desc <<-DESC
Expand All @@ -56,5 +82,76 @@
task :clean, :roles => assets_role, :except => { :no_release => true } do task :clean, :roles => assets_role, :except => { :no_release => true } do
run "cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:clean" run "cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:clean"
end end

desc <<-DESC
Clean up any assets that haven't been deployed for more than :expire_assets_after seconds.
Default time to keep old assets is one week. Set the :expire_assets_after variable
to change the assets expiry time. Assets will only be deleted if they are not required by
an existing release.
DESC
task :clean_expired, :roles => assets_role, :except => { :no_release => true } do
# Fetch all assets_manifest.yml contents.
manifests_output = capture <<-CMD.compact
for manifest in #{releases_path.shellescape}/*/assets_manifest.yml; do
cat -- "$manifest" 2> /dev/null && printf ':::' || true;
done
CMD
manifests = manifests_output.split(':::')

if manifests.empty?
logger.info "No manifests in #{releases_path}/*/assets_manifest.yml"
else
logger.info "Fetched #{manifests.count} manifests from #{releases_path}/*/assets_manifest.yml"
current_assets = Set.new
manifests.each do |yaml|
manifest = YAML.load(yaml)
current_assets += manifest.to_a.flatten.flat_map do |file|
[file, "#{file}.gz"]
end
end
current_assets += %w(manifest.yml sources_manifest.yml)

# Write the list of required assets to server.
# Files must be sorted in dictionary order using Linux sort
logger.info "Writing required assets to #{deploy_to}/REQUIRED_ASSETS..."
escaped_assets = current_assets.to_a.join("\\n").gsub("\"", "\\\"")
run "printf -- \"#{escaped_assets}\" | sort > #{deploy_to.shellescape}/REQUIRED_ASSETS", :silent => true

# Finds all files older than X minutes, then removes them if they are not referenced
# in REQUIRED_ASSETS.
expire_after_mins = (expire_assets_after.to_f / 60.0).to_i
logger.info "Removing assets that haven't been deployed for #{expire_after_mins} minutes..."
run <<-CMD.compact
cd -- #{shared_path.shellescape}/assets/ &&
for f in $(
find * -mmin +#{expire_after_mins.to_s.shellescape} -type f | sort |
comm -23 -- - #{deploy_to.shellescape}/REQUIRED_ASSETS
); do
echo "Removing unneeded asset: $f";
rm -f -- "$f";
done;
rm -f -- #{deploy_to.shellescape}/REQUIRED_ASSETS
CMD
end
end

desc <<-DESC
Rolls back assets to the previous release by symlinking the release's manifest
to shared/assets/manifest.yml, and finally recompiling or regenerating nondigest assets.
DESC
task :rollback, :roles => assets_role, :except => { :no_release => true } do
previous_manifest = "#{previous_release}/assets_manifest.yml"
if capture("[ -e #{previous_manifest.shellescape} ] && echo true || echo false").strip != 'true'
puts "#{previous_manifest} is missing! Cannot roll back assets. " <<
"Please run deploy:assets:precompile to update your assets when the rollback is finished."
return false
else
run <<-CMD.compact
cd -- #{previous_release.shellescape} &&
cp -f -- #{previous_manifest.shellescape} #{shared_path.shellescape}/assets/manifest.yml &&
#{rake} RAILS_ENV=#{rails_env.shellescape} #{asset_env.shellescape} assets:precompile:nondigest
CMD
end
end
end end
end end

0 comments on commit 5793c33

Please sign in to comment.