lib/capistrano-extensions/deploy.rb in capistrano-extensions-0.1.5 vs lib/capistrano-extensions/deploy.rb in capistrano-extensions-0.1.8

- old
+ new

@@ -1,11 +1,10 @@ require 'capistrano-extensions/geminstaller_dependency' -require 'capistrano/server_definition' # Overrides the majority of recipes from Capistrano's deploy recipe set. Capistrano::Configuration.instance(:must_exist).load do - # Add sls_recipes to the load path + # Add us to the load path @load_paths << File.expand_path(File.dirname(__FILE__)) # ======================================================================== # These variables MUST be set in the client capfiles. If they are not set, # the deploy will fail with an error. @@ -25,14 +24,14 @@ # These variables should NOT be changed unless you are very confident in # what you are doing. Make sure you understand all the implications of your # changes if you do decide to muck with these! # ========================================================================= - set(:use_sudo, false) # we don't want to use sudo-- we don't have to! - set(:deploy_via, :export) # we don't want our .svn folders on the server! - _cset(:deploy_to) { "/var/www/vhosts/#{application}" } - _cset(:deployable_environments, [:production]) + set(:use_sudo, false) # we don't want to use sudo-- we don't have to! + set(:deploy_via, :copy) # no need to have subversion on the production server + _cset(:deploy_to) { "/var/vhosts/#{application}" } + _cset(:deployable_environments, [:staging]) _cset(:rails_config_path) { File.join(latest_release, 'config') } _cset(:db_conf) { fetch(:config_structure, :rails).to_sym == :sls ? File.join(rails_config_path, rails_env, 'database.yml') : @@ -43,15 +42,40 @@ _cset(:content_dir, "content") _cset(:content_path) { File.join(shared_path, content_dir) } _cset(:public_path) { File.join(latest_release, 'public') } _cset(:log_path) { "/var/log/#{application}" } + # Local Properties + _cset(:tmp_dir, "tmp/cap") + # when local:syncing, should we keep backups just in case of failure? + _cset(:store_dev_backups, false) + # how long to allow remote backups to be valid (at both cache levels) + _cset(:remote_backup_expires, 172800) # 2 days in seconds. + # when remote:syncing, should we keep backups just in case of failure? + _cset(:store_remote_backups, true) + # paths to exclude during deployment + _cset(:exclude_paths, []) + + _cset(:copy_cache) { File.expand_path("~/.capistrano/#{application}") } + set(:copy_exclude) { + # don't deploy the other environment directories + envs = fetch(:deployable_environments).dup + envs.delete_if { |env| rails_env.to_sym == env.to_sym } + envs.map! { |env| File.join("config", "#{env}") } + + envs + fetch(:exclude_paths) + } + + _cset(:zip, "gzip") + _cset(:unzip, "gunzip") + _cset(:zip_ext, "gz") + # Allow recipes to ask for a certain local environment def local_db_conf(env = nil) env ||= fetch(:rails_env) fetch(:config_structure, :rails).to_sym == :sls ? - File.join('config', env, 'database.yml') : + File.join('config', env.to_s, 'database.yml') : File.join('config', 'database.yml') end # Read from the local machine-- BE CAREFUL!!! set(:db) { YAML.load_file(local_db_conf)[rails_env] } @@ -77,21 +101,20 @@ end # Now, let's actually include our common recipes! namespace :deploy do desc <<-DESC - [capistrano-extensions] Creates shared directories and symbolic links to them by the - :content_directories and :shared_content properties. See the README for - further explanation. + [capistrano-extensions] Creates shared directories and symbolic links to them by reading the + :content_directories and :shared_content properties. See the README for further explanation. DESC task :create_shared_file_column_dirs, :roles => :app, :except => { :no_release => true } do mappings = content_directories.inject(shared_content) { |hsh, dir| hsh.merge({"content/#{dir}" => "public/#{dir}"}) } mappings.each_pair do |remote, local| run <<-CMD + umask 0022 && mkdir -p #{shared_path}/#{remote} && - ln -sf #{shared_path}/#{remote} #{latest_release}/#{local} && - chmod 755 -R #{shared_path}/#{remote} + ln -sf #{shared_path}/#{remote} #{latest_release}/#{local} CMD end end desc <<-DESC @@ -113,93 +136,22 @@ [capistrano-extensions]: Tarballs deployable environment's rails logfile (identified by RAILS_ENV environment variable, which defaults to 'production') and copies it to the local filesystem DESC task :pull do - tmp_location = "#{shared_path}/#{rails_env}.log.gz" - run "cp #{log_path}/#{rails_env}.log #{shared_path}/ && gzip #{shared_path}/#{rails_env}.log" - get "#{tmp_location}", "#{application}-#{rails_env}.log.gz" + tmp_location = "#{shared_path}/#{rails_env}.log.#{zip_ext}" + run "cp #{log_path}/#{rails_env}.log #{shared_path}/ && #{zip} #{shared_path}/#{rails_env}.log" + get "#{tmp_location}", "#{application}-#{rails_env}.log.#{zip_ext}" run "rm #{tmp_location}" end end + load 'recipes/db_sync' + load 'recipes/content_sync' + namespace :remote do desc <<-DESC - [capistrano-extensions] Uploads the backup file downloaded from local:backup_db (specified via the FROM env variable), - copies it to the remove environment specified by RAILS_ENV, and imports (via mysql command line tool) it back into the - remote database. - DESC - task :restore_db, :roles => :db do - env = ENV['FROM'] || 'production' - - puts "\033[1;41m Restoring database backup to #{rails_env} environment \033[0m" - if deployable_environments.include?(rails_env.to_sym) - # remote environment - local_backup_file = "#{application}-#{env}-db.sql.gz" - remote_file = "#{shared_path}/restore_db.sql" - if !File.exists?(local_backup_file) - puts "Could not find backup file: #{local_backup_file}" - exit 1 - end - upload(local_backup_file, "#{remote_file}.gz") - - pass_str = pluck_pass_str(db) - run "gunzip -f #{remote_file}.gz" - run "mysql -u#{db['username']} #{pass_str} #{db['database']} < #{remote_file}" - run "rm -f #{remote_file}" - end - end - - desc <<-DESC - [capistrano-extensions]: Backs up target deployable environment's database (identified - by the FROM environment variable, which defaults to 'production') and restores it to - the remote database identified by the TO environment variable, which defaults to "staging." - DESC - task :sync_db do - system("capistrano-extensions-sync-db #{ENV['FROM'] || 'production'} #{ENV['TO'] || 'staging'}") - end - - desc <<-DESC - [capistrano-extensions]: Uploads the backup file downloaded from local:backup_content (specified via the - FROM env variable), copies it to the remote environment specified by RAILS_ENV, and unpacks it into the - shared/ directory. - DESC - task :restore_content do - from = ENV['FROM'] || 'production' - - if deployable_environments.include?(rails_env.to_sym) - local_backup_file = "#{application}-#{from}-content_backup.tar.gz" - remote_file = "#{shared_path}/content_backup.tar.gz" - - if !File.exists?(local_backup_file) - puts "Could not find backup file: #{local_backup_file}" - exit 1 - end - - upload(local_backup_file, "#{remote_file}") - remote_dirs = ["content"] + shared_content.keys - - run("cd #{shared_path} && rm -rf #{remote_dirs.join(' ')} && tar xzf #{remote_file} -C #{shared_path}/") - end - end - - desc <<-DESC - [capistrano-extensions]: Backs up target deployable environment's shared content (identified by the FROM environment - variable, which defaults to 'production') and restores it to the remote environment identified - by the TO envrionment variable, which defaults to "staging." - - Because multiple capistrano configurations must be loaded, an external executable - (capistrano-extensions-sync_content) is invoked, which independently calls capistrano. See the - executable at $GEM_HOME/capistrano-extensions-0.1.2/bin/capistrano-extensions-sync_content - - $> cap remote:sync_content FROM=production TO=staging - DESC - task :sync_content do - system("capistrano-extensions-sync-content #{ENV['FROM'] || 'production'} #{ENV['TO'] || 'staging'}") - end - - desc <<-DESC [capistrano-extensions]: Wrapper fro remote:sync_db and remote:sync_content. $> cap remote:sync FROM=production TO=staging DESC task :sync do sync_db @@ -207,124 +159,88 @@ end end namespace :local do desc <<-DESC - [capistrano-extensions]: Backs up deployable environment's database (identified by the - RAILS_ENV environment variable, which defaults to 'production') and copies it to the local machine + [capistrano-extensions]: Wrapper for local:sync_db and local:sync_content + $> cap local:sync RAILS_ENV=production RESTORE_ENV=development DESC - task :backup_db, :roles => :db do - pass_str = pluck_pass_str(db) - - run "mysqldump -u#{db['username']} #{pass_str} #{db['database']} > #{shared_path}/db_backup.sql" - run "gzip #{shared_path}/db_backup.sql" - get "#{shared_path}/db_backup.sql.gz", "#{application}-#{rails_env}-db.sql.gz" - run "rm -f #{shared_path}/db_backup.sql.gz #{shared_path}/db_backup.sql" + task :sync do + sync_db + sync_content end + end + + namespace :util do - desc <<-DESC - [capistrano-extensions] Untars the backup file downloaded from local:backup_db (specified via the FROM env - variable, which defalts to RAILS_ENV), and imports (via mysql command line tool) it back into the database - defined in the RAILS_ENV env variable. - - ToDo: implement proper rollback: currently, if the mysql import succeeds, but the rm fails, - the database won't be rolled back. Not sure this is even all that important or necessary, since - it's a local database that doesn't demand integrity (in other words, you're still going to have to - fix it, but it's not mission critical). - DESC - task :restore_db, :roles => :db do - on_rollback { "gzip #{application}-#{from}-db.sql"} - - from = ENV['FROM'] || rails_env - - env = ENV['RESTORE_ENV'] || 'development' - y = YAML.load_file(local_db_conf(env))[env] - db, user = y['database'], (y['username'] || 'root') # update me! - - pass_str = pluck_pass_str(y) - - puts "\033[1;41m Restoring database backup to #{env} environment \033[0m" - # local - system <<-CMD - gunzip #{application}-#{from}-db.sql.gz && - mysql -u#{user} #{pass_str} #{db} < #{application}-#{from}-db.sql - CMD - end - - desc <<-DESC - [capistrano-extensions]: Downloads a tarball of uploaded content (that lives in public/ - directory, as specified by the :content_directories property) from the production site - back to the local filesystem - DESC - task :backup_content do - folders = ["content"] + shared_content.keys - - run "cd #{shared_path} && tar czf #{shared_path}/content_backup.tar.gz #{folders.join(' ')}" - - #run "cd #{content_path} && tar czf #{shared_path}/content_backup.tar.gz *" - download("#{shared_path}/content_backup.tar.gz", "#{application}-#{rails_env}-content_backup.tar.gz") - run "rm -f #{shared_path}/content_backup.tar.gz" - end - - desc <<-DESC - [capistrano-extensions]: Restores the backed up content (evn var FROM specifies which environment - was backed up, defaults to RAILS_ENV) to the local development environment app - DESC - task :restore_content do - from = ENV['FROM'] || rails_env - - system "mkdir -p tmp/content-#{from}" - system "tar xzf #{application}-#{from}-content_backup.tar.gz -C tmp/content-#{from}" - system "rm -f #{application}-#{from}-content_backup.tar.gz" - - shared_content.each_pair do |remote, local| - system "rm -rf #{local} && mv tmp/content-#{from}/#{remote} #{local}" + namespace :tmp do + desc "[capistrano-extensions]: Displays warning if :tmp_dir has more than 10 files or is greater than 50MB" + task :check do + #[ 5 -le "`ls -1 tmp/cap | wc -l`" ] && echo "Display Me" + cmd = %Q{ [ 10 -le "`ls -1 #{tmp_dir} | wc -l`" ] || [ 50 -le "`du -sh #{tmp_dir} | awk '{print int($1)}'`" ] && printf "\033[1;41m Clean up #{tmp_dir} directory \033[0m\n" && du -sh #{tmp_dir}/* } + system(cmd) end - content_directories.each do |public_dir| - system "rm -rf public/#{public_dir}" - system "mv tmp/content-#{from}/content/#{public_dir} public/" + desc "[capistrano-extensions]: Remove the current remote env's backups from :tmp_dir" + task :clean_remote do + system("rm -f #{tmp_dir}/#{application}-#{rails_env}*") end - - end - desc <<-DESC - [capistrano-extensions]: Wrapper for local:backup_db and local:restore_db. - $> cap local:sync_db RAILS_ENV=production RESTORE_ENV=development - DESC - task :sync_db do - transaction do - backup_db - ENV['FROM'] = rails_env - restore_db - end + # desc "Removes all but a single backup from :tmp_dir" + # task :clean do + # + # end + # + # desc "Removes all tmp files from :tmp_dir" + # task :remove do + # + # end end - - desc <<-DESC - [capistrano-extensions]: Wrapper for local:backup_content and local:restore_content - $> cap local:sync_content RAILS_ENV=production RESTORE_ENV=development - DESC - task :sync_content do - transaction do - backup_content - restore_content - end - end - - desc <<-DESC - [capistrano-extensions]: Wrapper for local:sync_db and local:sync_content - $> cap local:sync RAILS_ENV=production RESTORE_ENV=development - DESC - task :sync do - sync_db - sync_content - end end + end def pluck_pass_str(db_config) pass_str = db_config['password'] if !pass_str.nil? pass_str = "-p#{pass_str.gsub('$', '\$')}" end pass_str || '' -end +end + +module LocalUtils + def current_timestamp + @current_timestamp ||= Time.now.to_i + end + + def local_db_backup_file(args = {}) + env = args[:env] || rails_env + timestamp = args[:timestamp] || current_timestamp + "#{tmp_dir}/#{application}-#{env}-db-#{timestamp}.sql" + end + + def local_content_backup_dir(args={}) + env = args[:env] || rails_env + timestamp = args[:timestamp] || current_timestamp + "#{tmp_dir}/#{application}-#{env}-content-#{timestamp}" + end + + def retrieve_local_files(env, type) + `ls -r #{tmp_dir} | awk -F"-" '{ if ($2 ~ /#{env}/ && $3 ~ /#{type}/) { print $4; } }'`.split(' ') + end + + def most_recent_local_backup(env, type) + retrieve_local_files(env, type).first.to_i + end +end + +module RemoteUtils + def last_mod_time(path) + capture("stat -c%Y #{path}").to_i + end + + def server_cache_valid?(path) + capture("[ -f #{path} ] || echo '1'").empty? && ((Time.now.to_i - last_mod_time(path)) <= remote_backup_expires) # two days in seconds + end +end + +include LocalUtils, RemoteUtils \ No newline at end of file