├── .travis.yml ├── Gemfile ├── .gitignore ├── lib ├── eycap │ ├── version.rb │ ├── recipes.rb │ ├── recipes │ │ ├── apache.rb │ │ ├── passenger.rb │ │ ├── monit.rb │ │ ├── tomcat.rb │ │ ├── resque.rb │ │ ├── rvm.rb │ │ ├── ssl.rb │ │ ├── juggernaut.rb │ │ ├── delayed_job.rb │ │ ├── backgroundrb.rb │ │ ├── ferret.rb │ │ ├── memcached.rb │ │ ├── slice.rb │ │ ├── bundler.rb │ │ ├── solr.rb │ │ ├── templates │ │ │ └── maintenance.rhtml │ │ ├── nginx.rb │ │ ├── unicorn.rb │ │ ├── mongrel.rb │ │ ├── sphinx.rb │ │ ├── database.rb │ │ └── deploy.rb │ └── lib │ │ ├── ey_logger_hooks.rb │ │ ├── rvm_helper.rb │ │ └── ey_logger.rb ├── capistrano │ └── recipes │ │ └── deploy │ │ └── strategy │ │ ├── unshared_remote_cache.rb │ │ └── filtered_remote_cache.rb └── eycap.rb ├── test ├── fixtures │ └── recipes │ │ └── default.rb ├── eycap_test.rb └── minitest_helper.rb ├── Rakefile ├── eycap.gemspec ├── README.markdown └── History.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | *.gem 3 | .rbenv-version 4 | .DS_Store 5 | Gemfile.lock 6 | -------------------------------------------------------------------------------- /lib/eycap/version.rb: -------------------------------------------------------------------------------- 1 | module Eycap 2 | VERSION = '0.6.12' 3 | end 4 | 5 | -------------------------------------------------------------------------------- /lib/eycap/recipes.rb: -------------------------------------------------------------------------------- 1 | # This is here to maintain backward compatibility for the require "eycap/recipe" 2 | # statement at the beginning of each deploy.rb distributed before eycap 0.6.x 3 | 4 | require 'eycap' -------------------------------------------------------------------------------- /lib/eycap/recipes/apache.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :apache do 3 | [:stop, :start, :restart, :reload].each do |action| 4 | desc "#{action.to_s.capitalize} Apache" 5 | task action, :roles => :web do 6 | sudo "/etc/init.d/apache2 #{action.to_s}" 7 | end 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /test/fixtures/recipes/default.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano' 2 | 3 | module Capistrano::Recipes 4 | module Default 5 | def self.load_into(configuration) 6 | configuration.load do 7 | task :default do 8 | set :message, 'this is a fixture class of a default Capistrano object' 9 | message 10 | end 11 | end 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/passenger.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :passenger do 3 | desc <<-DESC 4 | Restart the passenger module to reload the application after deploying. 5 | DESC 6 | task :restart, :roles => :app, :except => {:no_release => true} do 7 | sudo "touch #{current_path}/tmp/restart.txt" 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /test/eycap_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest_helper' 2 | 3 | class TestEycap < MiniTest::Unit::TestCase 4 | 5 | describe "first test" do 6 | Capistrano::Configuration.instance = Capistrano::Configuration.new 7 | load_capistrano_recipe(Capistrano::Recipes::Default) 8 | 9 | it 'loads the specified recipe into the instance configuration' do 10 | Capistrano::Configuration.instance.must_have_task "default" 11 | end 12 | 13 | end 14 | 15 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # The bundler rake tasks help automate the distribution of this gem. 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks 4 | 5 | # Define a default 'rake' alias for minitest so that when you type 6 | # rake in the root of the eycap folder you will run the tests. 7 | # https://github.com/jimweirich/rake/blob/master/lib/rake/testtask.rb 8 | 9 | require "rake/testtask" 10 | 11 | Rake::TestTask.new do |t| 12 | t.libs << "test" << "lib" 13 | t.pattern = "test/**/*_test.rb" 14 | end 15 | 16 | task :default => :test 17 | -------------------------------------------------------------------------------- /lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano/recipes/deploy/strategy/remote_cache' 2 | 3 | module Capistrano 4 | module Deploy 5 | module Strategy 6 | class UnsharedRemoteCache < RemoteCache 7 | def check! 8 | super.check do |d| 9 | d.remote.writable(repository_cache) 10 | end 11 | end 12 | 13 | private 14 | 15 | def repository_cache 16 | configuration[:repository_cache] 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/eycap/recipes/monit.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :monit do 4 | desc "Get the status of your mongrels" 5 | task :status, :roles => :app do 6 | @monit_output ||= { } 7 | sudo "/usr/bin/monit status" do |channel, stream, data| 8 | @monit_output[channel[:server].to_s] ||= [ ] 9 | @monit_output[channel[:server].to_s].push(data.chomp) 10 | end 11 | @monit_output.each do |k,v| 12 | puts "#{k} -> #{'*'*55}" 13 | puts v.join("\n") 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/tomcat.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :tomcat do 3 | desc "Start tomcat" 4 | task :start, :roles => [:app], :only => {:tomcat => true} do 5 | sudo "/etc/init.d/tomcat start" 6 | end 7 | desc "Stop tomcat" 8 | task :stop, :roles => [:app], :only => {:tomcat => true} do 9 | sudo "/etc/init.d/tomcat stop" 10 | end 11 | desc "Restart tomcat" 12 | task :restart, :roles => [:app], :only => {:tomcat => true} do 13 | sudo "/etc/init.d/tomcat restart" 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/eycap/lib/ey_logger_hooks.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "ey_logger") 2 | 3 | # These tasks are setup to use with the logger as post commit hooks. 4 | Capistrano::Configuration.instance(:must_exist).load do 5 | namespace :ey_logger do 6 | task :upload_log_to_slice, :except => { :no_release => true} do 7 | logger = Capistrano::EYLogger 8 | run "mkdir -p #{shared_path}/deploy_logs" 9 | put File.open(logger.log_file_path).read, "#{shared_path}/deploy_logs/#{logger.remote_log_file_name}" 10 | end 11 | end 12 | end 13 | 14 | Capistrano::EYLogger.post_process_hook("ey_logger:upload_log_to_slice") -------------------------------------------------------------------------------- /lib/eycap/recipes/resque.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :resque do 4 | desc "After deploy:restart we want to restart the workers" 5 | task :restart, :roles => [:app], :only => {:resque => true} do 6 | sudo "monit restart all -g resque_#{application}" 7 | end 8 | 9 | desc "After update_code we want to symlink the resque.yml" 10 | task :symlink, :roles => [:app], :only => {:resque => true} do 11 | run "if [ -f #{shared_path}/config/resque.yml ]; then ln -nfs #{shared_path}/config/resque.yml #{latest_release}/config/resque.yml; fi" 12 | end 13 | # after "deploy:symlink_configs", "resque:symlink" add this to the deploy.rb file 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/rvm.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :rvm do 3 | desc <<-DESC 4 | Create rvm wrappers for background tasks, 5 | in any script adding rvm support looks like: 6 | 7 | rvm_path=/usr/local/rvm 8 | if [[ -s "$rvm_path/environments/${application}" ]] 9 | then PATH="$rvm_path/wrappers/${application}:$PATH" 10 | fi 11 | DESC 12 | task :create_wrappers, :roles => :app, :except => {:no_bundle => true} do 13 | run_rvm_or <<-SHELL 14 | rvm alias create #{application} #{fetch(:rvm_ruby_string,nil)} 15 | rvm wrapper #{application} --no-links --all 16 | SHELL 17 | end 18 | 19 | after "bundler:bundle_gems","rvm:create_wrappers" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/eycap/recipes/ssl.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :ssl do 4 | desc "create csr and key for ssl certificates" 5 | task :create, :roles => :app, :except => {:no_release => true} do 6 | sudo "mkdir -p /data/ssl/" 7 | set(:length) { Capistrano::CLI.ui.ask("key length (1024 or 2048): ") } 8 | set(:country) { Capistrano::CLI.ui.ask("Country Code (2 letters): ") } 9 | set(:state) { Capistrano::CLI.ui.ask("State/Province: ") } 10 | set(:city) { Capistrano::CLI.ui.ask("City: ") } 11 | set(:domain) { Capistrano::CLI.ui.ask("Common Name (domain): ") } 12 | run "cd /data/ssl/ && openssl req -new -nodes -days 365 -newkey rsa:#{length} -subj '/C=#{country}/ST=#{state}/L=#{city}/CN=#{domain}' -keyout #{domain}.com.key -out #{domain}.com.csr" 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/juggernaut.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :juggernaut do 4 | desc "After update_code you want to symlink the juggernaut.yml file into place" 5 | task :symlink_configs, :roles => [:app], :except => {:no_release => true, :juggernaut => false} do 6 | run <<-CMD 7 | cd #{latest_release} && 8 | ln -nfs #{shared_path}/config/juggernaut.yml #{latest_release}/config/juggernaut.yml && 9 | ln -nfs #{shared_path}/config/juggernaut_hosts.yml #{latest_release}/config/juggernaut_hosts.yml 10 | CMD 11 | end 12 | [:start,:stop,:restart].each do |op| 13 | desc "#{op} juggernaut server" 14 | task op, :roles => [:app], :except => {:no_release => true, :juggernaut => false} do 15 | sudo "/usr/bin/monit #{op} all -g juggernaut_#{application}" 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/eycap/recipes/delayed_job.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :dj do 3 | desc <<-DESC 4 | Start the Delayed Job queue along with any in the same monit_group. 5 | DESC 6 | task :start, :roles => [:app], :only => {:dj => true} do 7 | sudo "/usr/bin/monit start all -g dj_#{monit_group}" 8 | end 9 | 10 | desc <<-DESC 11 | Restart the Delayed Job queue along with any in the same monit_group. 12 | DESC 13 | task :restart, :roles => [:app], :only => {:dj => true} do 14 | sudo "/usr/bin/monit restart all -g dj_#{monit_group}" 15 | end 16 | 17 | desc <<-DESC 18 | Stop all monit group members, of which delayed job can be a part of. 19 | DESC 20 | task :stop, :roles => [:app], :only => {:dj => true} do 21 | sudo "/usr/bin/monit stop all -g dj_#{monit_group}" 22 | end 23 | 24 | end #namespace 25 | end #Capistrano::Configuration 26 | -------------------------------------------------------------------------------- /lib/eycap/recipes/backgroundrb.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :bdrb do 4 | desc "After update_code you want to reindex" 5 | task :reindex, :roles => [:app], :only => {:backgroundrb => true} do 6 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/searchd #{application} reindex" 7 | end 8 | 9 | desc "Start Backgroundrb" 10 | task :start, :roles => [:app], :only => {:backgroundrb => true} do 11 | sudo "/usr/bin/monit start all -g backgroundrb_#{application}" 12 | end 13 | desc "Stop Backgroundrb" 14 | task :stop, :roles => [:app], :only => {:backgroundrb => true} do 15 | sudo "/usr/bin/monit stop all -g backgroundrb_#{application}" 16 | end 17 | desc "Restart Backgroundrb" 18 | task :restart, :roles => [:app], :only => {:backgroundrb => true} do 19 | sudo "/usr/bin/monit restart all -g backgroundrb_#{application}" 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/eycap/recipes/ferret.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :ferret do 4 | desc "After update_code you want to symlink the index and ferret_server.yml file into place" 5 | task :symlink_configs, :roles => [:app], :except => {:no_release => true, :ferret => false} do 6 | run <<-CMD 7 | cd #{latest_release} && 8 | ln -nfs #{shared_path}/config/ferret_server.yml #{latest_release}/config/ferret_server.yml && 9 | if [ -d #{latest_release}/index ]; then mv #{latest_release}/index #{latest_release}/index.bak; fi && 10 | ln -nfs #{shared_path}/index #{latest_release}/index 11 | CMD 12 | end 13 | [:start,:stop,:restart].each do |op| 14 | desc "#{op} ferret server" 15 | task op, :roles => [:app], :except => {:no_release => true, :ferret => false} do 16 | sudo "/usr/bin/monit #{op} all -g ferret_#{application}" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/eycap/lib/rvm_helper.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | def reformat_code(code) 4 | code.split("\n").map(&:strip).join("; ") 5 | end 6 | 7 | # Allow parallel execution of rvm and non rvm commands 8 | def run_rvm_or(command_rvm, command_else = "true") 9 | parallel do |session| 10 | command_else = reformat_code(command_else) 11 | if Capistrano.const_defined?(:RvmMethods) 12 | # command_with_shell is defined in RvmMethods so rvm/capistrano has to be required first 13 | command_rvm = command_with_shell(reformat_code(command_rvm), fetch(:rvm_shell, "bash")) 14 | rvm_role = fetch(:rvm_require_role, nil) 15 | if rvm_role # mixed 16 | session.when "in?(:#{rvm_role})", command_rvm 17 | session.else command_else 18 | else # only rvm 19 | session.else command_rvm 20 | end 21 | else # no rvm 22 | session.else command_else 23 | end 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /test/minitest_helper.rb: -------------------------------------------------------------------------------- 1 | # Let's include the eycap gem. 2 | # FIXME: when you uncomment the require to bring in eycap it starts to error: 3 | # 4 | # capistrano/lib/capistrano/configuration/loading.rb:18: 5 | # in `instance': Please require this file from within a Capistrano recipe (LoadError) 6 | # 7 | # require File.dirname(__FILE__) + '/../lib/eycap' 8 | 9 | # This is a short and sweet way to bootstrap minitest. 10 | # It also lets them keep it under a "service" if they want to make changes to 11 | # the autorun method. 12 | # https://github.com/seattlerb/minitest/blob/master/lib/minitest/autorun.rb 13 | 14 | require 'rubygems' 15 | require 'bundler/setup' 16 | 17 | require 'minitest/autorun' 18 | 19 | # This library has assertions and expectations already written that can help 20 | # to test capistrano recipes. 21 | require 'minitest-capistrano' 22 | 23 | # Let's add capistrano, since that's what we need to deploy. 24 | require 'capistrano' 25 | 26 | # Load a default fixture capistrano object. 27 | require 'fixtures/recipes/default' 28 | -------------------------------------------------------------------------------- /lib/eycap.rb: -------------------------------------------------------------------------------- 1 | require 'eycap/lib/ey_logger' 2 | require 'eycap/lib/ey_logger_hooks' 3 | require 'eycap/lib/rvm_helper' 4 | require 'eycap/recipes/apache' 5 | require 'eycap/recipes/backgroundrb' 6 | require 'eycap/recipes/bundler' 7 | require 'eycap/recipes/database' 8 | require 'eycap/recipes/delayed_job' 9 | require 'eycap/recipes/deploy' 10 | require 'eycap/recipes/ferret' 11 | require 'eycap/recipes/juggernaut' 12 | require 'eycap/recipes/memcached' 13 | require 'eycap/recipes/mongrel' 14 | require 'eycap/recipes/monit' 15 | require 'eycap/recipes/nginx' 16 | require 'eycap/recipes/passenger' 17 | require 'eycap/recipes/resque' 18 | require 'eycap/recipes/rvm' 19 | require 'eycap/recipes/slice' 20 | require 'eycap/recipes/solr' 21 | require 'eycap/recipes/sphinx' 22 | require 'eycap/recipes/ssl' 23 | require 'eycap/recipes/tomcat' 24 | require 'eycap/recipes/unicorn' 25 | 26 | Capistrano::Configuration.instance(:must_exist).load do 27 | default_run_options[:pty] = true if respond_to?(:default_run_options) 28 | set :keep_releases, 3 29 | set :runner, defer { user } 30 | end 31 | -------------------------------------------------------------------------------- /lib/eycap/recipes/memcached.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :memcached do 3 | desc "Start memcached" 4 | task :start, :roles => [:app], :only => {:memcached => true} do 5 | sudo "/etc/init.d/memcached start" 6 | end 7 | desc "Stop memcached" 8 | task :stop, :roles => [:app], :only => {:memcached => true} do 9 | sudo "/etc/init.d/memcached stop" 10 | end 11 | desc "Restart memcached" 12 | task :restart, :roles => [:app], :only => {:memcached => true} do 13 | sudo "/etc/init.d/memcached restart" 14 | end 15 | desc "Flush memcached - this assumes memcached is on port 11211" 16 | task :flush, :roles => [:app], :only => {:memcached => true} do 17 | sudo "echo 'flush_all' | nc -q 1 localhost 11211" 18 | end 19 | desc "Symlink the memcached.yml file into place if it exists" 20 | task :symlink_configs, :roles => [:app], :only => {:memcached => true }, :except => { :no_release => true } do 21 | run "if [ -f #{shared_path}/config/memcached.yml ]; then ln -nfs #{shared_path}/config/memcached.yml #{latest_release}/config/memcached.yml; fi" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /eycap.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/eycap/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Engine Yard", "Tyler Bird", "Matt Dolian", "Christopher Rigor", "Mike Riley", "Joel Watson", "Corey Donohoe", "Mutwin Kraus", "Erik Jones", "Daniel Neighman", "Dylan Egan", "Dan Peterson", "Tim Carey-Smith"] 6 | gem.email = ["appsupport@engineyard.com"] 7 | gem.description = %q{Capistrano recipes for the Engine Yard Managed platform.} 8 | gem.summary = %q{Recipes that help automate the processes of the Engine Yard stack for users of the Managed platform.} 9 | gem.homepage = "http://github.com/engineyard/eycap" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "eycap" 15 | gem.require_paths = ["lib"] 16 | gem.version = Eycap::VERSION 17 | 18 | gem.add_dependency "capistrano", ">= 2.2.0" 19 | 20 | gem.add_development_dependency "rake" 21 | gem.add_development_dependency "minitest" 22 | gem.add_development_dependency "minitest-capistrano" 23 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/slice.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :slice do 4 | desc "Tail the Rails logs for your environment" 5 | task :tail_environment_logs, :roles => :app do 6 | run "tail -f #{shared_path}/log/#{rails_env}.log" do |channel, stream, data| 7 | puts # for an extra line break before the host name 8 | puts "#{channel[:server]} -> #{data}" 9 | break if stream == :err 10 | end 11 | end 12 | desc "Tail the Mongrel logs for your environment" 13 | task :tail_mongrel_logs, :roles => :app do 14 | run "tail -f #{shared_path}/log/mongrel*.log" do |channel, stream, data| 15 | puts # for an extra line break before the host name 16 | puts "#{channel[:server]} -> #{data}" 17 | break if stream == :err 18 | end 19 | end 20 | desc "Tail the apache logs for your environment" 21 | task :tail_apache_logs, :roles => [:app, :web] do 22 | run "tail -f /var/log/apache2/#{application}.*.access.log" do |channel, stream, data| 23 | puts # line break 24 | puts "#{channel[:server]} -> #{data}" 25 | break if stream == :err 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/eycap/recipes/bundler.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | set :bundle_without, "test development" unless exists?(:bundle_without) 4 | 5 | namespace :bundler do 6 | desc "Automatically installed your bundled gems if a Gemfile exists" 7 | task :bundle_gems, :roles => :app, :except => {:no_bundle => true} do 8 | only_with_rvm = <<-SHELL 9 | if [ -f #{release_path}/Gemfile ] 10 | then cd #{release_path} && bundle install --without=#{bundle_without} --system 11 | fi 12 | SHELL 13 | only_without_rvm = <<-SHELL 14 | mkdir -p #{shared_path}/bundled_gems 15 | if [ -f #{release_path}/Gemfile ] 16 | then cd #{release_path} && bundle install --without=#{bundle_without} --binstubs #{release_path}/bin --path #{shared_path}/bundled_gems --quiet --deployment 17 | fi 18 | if [ ! -h #{release_path}/bin ] 19 | then ln -nfs #{release_path}/bin #{release_path}/ey_bundler_binstubs 20 | fi 21 | SHELL 22 | run_rvm_or only_with_rvm, only_without_rvm 23 | end 24 | task :symlink_bundle_config, :roles => :app do 25 | run_rvm_or "true", "mkdir -p #{shared_path}/bundle && ln -sf #{shared_path}/bundle #{release_path}/.bundle" 26 | end 27 | before "bundler:bundle_gems","bundler:symlink_bundle_config" 28 | after "deploy:symlink_configs","bundler:bundle_gems" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/eycap/recipes/solr.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :solr do 4 | desc "After update_code you want to symlink the index and ferret_server.yml file into place" 5 | task :symlink_configs, :roles => [:app], :except => {:no_release => true} do 6 | run <<-CMD 7 | cd #{latest_release} && ln -nfs #{shared_path}/config/solr.yml #{latest_release}/config/solr.yml 8 | CMD 9 | end 10 | 11 | [:start,:stop,:restart].each do |op| 12 | desc "#{op} ferret server" 13 | task op, :roles => [:app], :only => {:solr => true} do 14 | sudo "/usr/bin/monit #{op} all -g solr_#{application}" 15 | end 16 | end 17 | 18 | namespace :tail do 19 | desc "Tail the Solr logs this environment" 20 | task :logs, :roles => [:app], :only => {:solr => true} do 21 | run "tail -f /var/log/engineyard/solr/#{application}.log" do |channel, stream, data| 22 | puts # for an extra line break before the host name 23 | puts "#{channel[:server]} -> #{data}" 24 | break if stream == :err 25 | end 26 | end 27 | desc "Tail the Solr error logs this environment" 28 | task :errors, :roles => [:app], :only => {:solr => true} do 29 | run "tail -f /var/log/engineyard/solr/#{application}.err.log" do |channel, stream, data| 30 | puts # for an extra line break before the host name 31 | puts "#{channel[:server]} -> #{data}" 32 | break if stream == :err 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/eycap/recipes/templates/maintenance.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | System down for maintenance 10 | 11 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |

42 | The system is down for <%= reason ? reason : "maintenance" %> 43 | as of <%= Time.now.strftime("%H:%M %Z") %>. 44 |

45 |

46 | It'll be back <%= deadline ? deadline : "shortly" %>. 47 |

48 |
49 |
50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/eycap/recipes/nginx.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :nginx do 4 | desc "Start Nginx on the app slices." 5 | task :start, :roles => :app, :except => {:nginx => false} do 6 | sudo "nohup /etc/init.d/nginx start 2>&1 | cat" 7 | end 8 | 9 | desc "Restart the Nginx processes on the app slices." 10 | task :restart , :roles => :app, :except => {:nginx => false} do 11 | sudo "nohup /etc/init.d/nginx restart 2>&1 | cat" 12 | end 13 | 14 | desc "Stop the Nginx processes on the app slices." 15 | task :stop , :roles => :app, :except => {:nginx => false} do 16 | sudo "/etc/init.d/nginx stop" 17 | end 18 | 19 | desc "Reload the Nginx config on the app slices." 20 | task :reload , :roles => :app, :except => {:nginx => false} do 21 | sudo "/etc/init.d/nginx reload" 22 | end 23 | 24 | desc "Upgrade the Nginx processes on the app slices." 25 | task :upgrade , :roles => :app, :except => {:nginx => false} do 26 | sudo "/etc/init.d/nginx upgrade" 27 | end 28 | 29 | desc "Test the Nginx config on the app slices." 30 | task :configtest , :roles => :app, :except => {:nginx => false} do 31 | sudo "/etc/init.d/nginx configtest" 32 | end 33 | 34 | desc "Tail the nginx error logs on the app slices" 35 | task :tail_error, :roles => :app, :except => {:nginx => false} do 36 | run "tail -f /var/log/engineyard/nginx/error.log" do |channel, stream, data| 37 | puts "#{channel[:server]}: #{data}" unless data =~ /^10\.[01]\.0/ # skips lb pull pages 38 | break if stream == :err 39 | end 40 | end 41 | 42 | end 43 | end -------------------------------------------------------------------------------- /lib/eycap/recipes/unicorn.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :unicorn do 3 | desc <<-DESC 4 | Start the Unicorn Master. This uses the :use_sudo variable to determine whether to use sudo or not. By default, :use_sudo is set to true. 5 | DESC 6 | task :start, :roles => [:app], :except => {:unicorn => false} do 7 | sudo "/usr/bin/monit start all -g #{monit_group}" 8 | end 9 | 10 | desc <<-DESC 11 | Restart the Unicorn processes on the app server by starting and stopping the master. This uses the :use_sudo variable to determine whether to use sudo or not. By default, :use_sudo is set to true. 12 | DESC 13 | task :restart, :roles => [:app], :except => {:unicorn => false} do 14 | sudo "/usr/bin/monit restart all -g #{monit_group}" 15 | end 16 | 17 | desc <<-DESC 18 | Stop the Unicorn processes on the app server. This uses the :use_sudo 19 | variable to determine whether to use sudo or not. By default, :use_sudo is 20 | set to true. 21 | DESC 22 | task :stop, :roles => [:app], :except => {:unicorn => false} do 23 | sudo "/usr/bin/monit stop all -g #{monit_group}" 24 | end 25 | 26 | desc <<-DESC 27 | Reloads the unicorn works gracefully - Use deploy task for deploys 28 | DESC 29 | task :reload, :roles => [:app], :except => {:unicorn => false} do 30 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/unicorn #{application} reload" 31 | end 32 | 33 | desc <<-DESC 34 | Adds a Unicorn worker - Beware of causing your host to swap, this setting isn't permanent 35 | DESC 36 | task :aworker, :roles => [:app], :except => {:unicorn => false} do 37 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/unicorn #{application} aworker" 38 | end 39 | 40 | desc <<-DESC 41 | Removes a unicorn worker (gracefully) 42 | DESC 43 | task :rworker, :roles => [:app], :except => {:unicorn => false} do 44 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/unicorn #{application} rworker" 45 | end 46 | 47 | desc <<-DESC 48 | Deploys app gracefully with USR2 and unicorn.rb combo 49 | DESC 50 | task :deploy, :roles => [:app], :except => {:unicorn => false} do 51 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/unicorn #{application} deploy" 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/capistrano/recipes/deploy/strategy/filtered_remote_cache.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano/recipes/deploy/scm/base' 2 | require 'capistrano/recipes/deploy/strategy/remote' 3 | 4 | module Capistrano 5 | module Deploy 6 | module SCM 7 | class Subversion < Base 8 | def switch(revision, checkout) 9 | "cd #{checkout} && #{scm :switch, verbose, authentication, "-r#{revision}", repository}" 10 | end 11 | end 12 | end 13 | 14 | module Strategy 15 | 16 | # Implements the deployment strategy that keeps a cached checkout of 17 | # the source code on each remote server. Each deploy simply updates the 18 | # cached checkout, and then filters a copy through tar to remove unwanted .svn directories, 19 | # finally leaving a pristeen, export-like copy at the destination. 20 | class FilteredRemoteCache < Remote 21 | # Executes the SCM command for this strategy and writes the REVISION 22 | # mark file to each host. 23 | def deploy! 24 | update_repository_cache 25 | tar_copy_repository_cache 26 | end 27 | 28 | def check! 29 | super.check do |d| 30 | d.remote.writable(shared_path) 31 | end 32 | end 33 | 34 | private 35 | 36 | def repository_cache 37 | configuration[:repository_cache] || "/var/cache/engineyard/#{configuration[:application]}" 38 | end 39 | 40 | def update_repository_cache 41 | logger.trace "checking if the cached copy repository root matches this deploy, then updating it" 42 | command = "if [ -d #{repository_cache} ] && ! echo '#{configuration[:repository]}' | grep -q `svn info #{repository_cache} | grep 'Repository Root' | awk '{print $3}'`; then " + 43 | "rm -rf #{repository_cache} && #{source.checkout(revision, repository_cache)}; " + 44 | "elif [ -d #{repository_cache} ]; then #{source.switch(revision, repository_cache)}; " + 45 | "else #{source.checkout(revision, repository_cache)}; fi" 46 | scm_run(command) 47 | end 48 | 49 | def tar_copy_repository_cache 50 | logger.trace "copying and filtering .svn via tar from cached version to #{configuration[:release_path]}" 51 | run "mkdir #{configuration[:release_path]} && tar c --exclude=#{configuration[:filter_spec] || ".svn"} -C #{repository_cache} . | tar xC #{configuration[:release_path]} && #{mark}" 52 | end 53 | end 54 | 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/eycap/recipes/mongrel.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | namespace :mongrel do 3 | desc <<-DESC 4 | Start Mongrel processes on the app server. This uses the :use_sudo variable to determine whether to use sudo or not. By default, :use_sudo is 5 | set to true. 6 | DESC 7 | task :start, :roles => [:app], :except => {:mongrel => false} do 8 | sudo "/usr/bin/monit start all -g #{monit_group}" 9 | end 10 | 11 | desc <<-DESC 12 | Restart the Mongrel processes on the app server by starting and stopping the cluster. This uses the :use_sudo 13 | variable to determine whether to use sudo or not. By default, :use_sudo is set to true. 14 | DESC 15 | task :restart, :roles => [:app], :except => {:mongrel => false} do 16 | sudo "/usr/bin/monit restart all -g #{monit_group}" 17 | end 18 | 19 | desc <<-DESC 20 | Stop the Mongrel processes on the app server. This uses the :use_sudo 21 | variable to determine whether to use sudo or not. By default, :use_sudo is 22 | set to true. 23 | DESC 24 | task :stop, :roles => [:app], :except => {:mongrel => false} do 25 | sudo "/usr/bin/monit stop all -g #{monit_group}" 26 | end 27 | 28 | desc <<-DESC 29 | Start mongrels in a loop, with a defer of [default] 30 seconds between each single mongrel restart. 30 | DESC 31 | task :rolling_restart, :roles => [:app], :except => {:mongrel => false} do 32 | 33 | set :mongrel_restart_delay, 30 34 | 35 | # need a script due to weird escapes run by sudo "X". 36 | script = File.open("/tmp/rolling.reboot", 'w+') 37 | script.puts "#!/bin/bash" 38 | script.puts "export monit_group=#{monit_group}" 39 | script.puts "export mongrel_restart_delay=#{mongrel_restart_delay}" 40 | # here's the need for single quoted - sed ? - (no escaping). 41 | script.puts 'for port in $(monit summary | grep mongrel | sed -r \'s/[^0-9]*([0-9]+).*/\1/\'); do echo "Executing monit restart mongrel_${monit_group}_${port}"; /usr/bin/monit restart mongrel_${monit_group}_${port}; echo "sleeping $mongrel_restart_delay"; sleep ${mongrel_restart_delay}; done' 42 | script.close 43 | 44 | upload(script.path, script.path, :via=> :scp) 45 | 46 | #it's in the script, on the remote server, execute it. 47 | sudo "chmod +x #{script.path}" 48 | sudo "#{script.path}" 49 | #cleanup 50 | sudo "rm #{script.path}" 51 | require 'fileutils' ; FileUtils.rm(script.path) 52 | puts "Done." 53 | end 54 | 55 | end #namespace 56 | end #Capistrano::Configuration 57 | -------------------------------------------------------------------------------- /lib/eycap/recipes/sphinx.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | 3 | namespace :sphinx do 4 | desc "After update_code you want to configure, then reindex" 5 | task :configure, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 6 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/searchd #{application} configure" 7 | end 8 | 9 | desc "After configure you want to reindex" 10 | task :reindex, :roles => [:app], :only => {:sphinx => true} do 11 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/searchd #{application} reindex" 12 | end 13 | 14 | desc "Start Sphinx Searchd" 15 | task :start, :roles => [:app], :only => {:sphinx => true} do 16 | sudo "/usr/bin/monit start all -g sphinx_#{application}" 17 | end 18 | 19 | desc "Stop Sphinx Searchd" 20 | task :stop, :roles => [:app], :only => {:sphinx => true} do 21 | sudo "/usr/bin/monit stop all -g sphinx_#{application}" 22 | end 23 | 24 | desc "Restart Sphinx Searchd" 25 | task :restart, :roles => [:app], :only => {:sphinx => true} do 26 | sudo "/usr/bin/monit restart all -g sphinx_#{application}" 27 | end 28 | 29 | task :symlink, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 30 | run "if [ -d #{latest_release}/config/ultrasphinx ]; then mv #{latest_release}/config/ultrasphinx #{latest_release}/config/ultrasphinx.bak; fi" 31 | run "ln -nfs #{shared_path}/config/ultrasphinx #{latest_release}/config/ultrasphinx" 32 | end 33 | end 34 | 35 | namespace :acts_as_sphinx do 36 | desc "After update_code you to to reindex" 37 | task :reindex, :roles => [:app], :only => {:sphinx => true} do 38 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/acts_as_sphinx_searchd #{application} reindex" 39 | end 40 | end 41 | 42 | namespace :thinking_sphinx do 43 | desc "After update_code you want to configure, then reindex" 44 | task :configure, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 45 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/thinking_sphinx_searchd #{application} configure #{rails_env}" 46 | end 47 | 48 | desc "After configure you want to reindex" 49 | task :reindex, :roles => [:app], :only => {:sphinx => true} do 50 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/thinking_sphinx_searchd #{application} reindex #{rails_env}" 51 | end 52 | 53 | task :symlink, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 54 | run "if [ -d #{latest_release}/config/thinkingsphinx ]; then mv #{latest_release}/config/thinkingsphinx #{latest_release}/config/thinkingsphinx.bak; fi" 55 | run "ln -nfs #{shared_path}/config/thinkingsphinx #{latest_release}/config/thinkingsphinx" 56 | run "ln -nfs #{shared_path}/config/sphinx.yml #{latest_release}/config/sphinx.yml" 57 | end 58 | end 59 | 60 | namespace :ultrasphinx do 61 | desc "After update_code you want to configure, then reindex" 62 | task :configure, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 63 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/ultrasphinx_searchd #{application} configure" 64 | end 65 | 66 | desc "After configure you want to reindex" 67 | task :reindex, :roles => [:app], :only => {:sphinx => true} do 68 | run "#{fetch(:engineyard_bin, "/engineyard/bin")}/ultrasphinx_searchd #{application} reindex" 69 | end 70 | 71 | task :symlink, :roles => [:app], :only => {:sphinx => true}, :except => {:no_release => true} do 72 | run "if [ -d #{latest_release}/config/ultrasphinx ]; then mv #{latest_release}/config/ultrasphinx #{latest_release}/config/ultrasphinx.bak; fi" 73 | run "ln -nfs #{shared_path}/config/ultrasphinx #{latest_release}/config/ultrasphinx" 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/eycap/recipes/database.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | Capistrano::Configuration.instance(:must_exist).load do 4 | 5 | namespace :db do 6 | task :backup_name, :roles => :db, :only => { :primary => true } do 7 | now = Time.now 8 | run "mkdir -p #{shared_path}/db_backups" 9 | backup_time = [now.year,now.month,now.day,now.hour,now.min,now.sec].join('-') 10 | set :backup_file, "#{shared_path}/db_backups/#{environment_database}-snapshot-#{backup_time}.sql" 11 | end 12 | 13 | desc "Clone Production Database to Staging Database." 14 | task :clone_prod_to_staging, :roles => :db, :only => { :primary => true } do 15 | 16 | # This task currently runs only on traditional EY offerings. 17 | # You need to have both a production and staging environment defined in 18 | # your deploy.rb file. 19 | 20 | backup_name unless exists?(:backup_file) 21 | run("cat #{shared_path}/config/database.yml") { |channel, stream, data| @environment_info = YAML.load(data)[rails_env] } 22 | dump 23 | 24 | if ['mysql', 'mysql2'].include? @environment_info['adapter'] 25 | run "gunzip < #{backup_file}.gz | mysql -u #{dbuser} -p -h #{staging_dbhost} #{staging_database}" do |ch, stream, out| 26 | ch.send_data "#{dbpass}\n" if out=~ /^Enter password:/ 27 | end 28 | else 29 | run "gunzip < #{backup_file}.gz | psql -W -U #{dbuser} -h #{staging_dbhost} #{staging_database}" do |ch, stream, out| 30 | ch.send_data "#{dbpass}\n" if out=~ /^Password/ 31 | end 32 | end 33 | run "rm -f #{backup_file}.gz" 34 | end 35 | 36 | desc "Backup your MySQL or PostgreSQL database to shared_path+/db_backups" 37 | task :dump, :roles => :db, :only => {:primary => true} do 38 | backup_name unless exists?(:backup_file) 39 | on_rollback { run "rm -f #{backup_file}" } 40 | run("cat #{shared_path}/config/database.yml") { |channel, stream, data| @environment_info = YAML.load(data)[rails_env] } 41 | 42 | if ['mysql', 'mysql2'].include? @environment_info['adapter'] 43 | dbhost = @environment_info['host'] 44 | if rails_env == "production" 45 | dbhost = environment_dbhost.sub('-master', '') + '-replica' if dbhost != 'localhost' # added for Solo offering, which uses localhost 46 | end 47 | run "mysqldump --add-drop-table -u #{dbuser} -h #{dbhost} -p #{environment_database} | gzip -c > #{backup_file}.gz" do |ch, stream, out | 48 | ch.send_data "#{dbpass}\n" if out=~ /^Enter password:/ 49 | end 50 | else 51 | run "pg_dump -W -c -U #{dbuser} -h #{environment_dbhost} #{environment_database} | gzip -c > #{backup_file}.gz" do |ch, stream, out | 52 | ch.send_data "#{dbpass}\n" if out=~ /^Password:/ 53 | end 54 | end 55 | end 56 | 57 | desc "Sync your production database to your local workstation" 58 | task :clone_to_local, :roles => :db, :only => {:primary => true} do 59 | backup_name unless exists?(:backup_file) 60 | dump 61 | get "#{backup_file}.gz", "/tmp/#{application}.sql.gz" 62 | development_info = YAML.load(ERB.new(File.read('config/database.yml')).result)['development'] 63 | 64 | if ['mysql', 'mysql2'].include? development_info['adapter'] 65 | run_str = "gunzip < /tmp/#{application}.sql.gz | mysql -u #{development_info['username']} --password='#{development_info['password']}' -h #{development_info['host']} #{development_info['database']}" 66 | else 67 | run_str = "" 68 | run_str += "PGPASSWORD=#{development_info['password']} " if development_info['password'] 69 | run_str += "gunzip < /tmp/#{application}.sql.gz | psql -U #{development_info['username']} " 70 | run_str += "-h #{development_info['host']} " if development_info['host'] 71 | run_str += development_info['database'] 72 | end 73 | %x!#{run_str}! 74 | run "rm -f #{backup_file}.gz" 75 | end 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /lib/eycap/lib/ey_logger.rb: -------------------------------------------------------------------------------- 1 | require 'tmpdir' 2 | require 'fileutils' 3 | module Capistrano 4 | 5 | class Logger 6 | 7 | def ey_log(level, message, line_prefix = nil) 8 | EYLogger.log(level, message, line_prefix) if EYLogger.setup? 9 | log_without_ey_logging(level, message, line_prefix) 10 | end 11 | 12 | unless method_defined?(:log_without_ey_logging) 13 | alias_method :log_without_ey_logging, :log 14 | alias_method :log, :ey_log 15 | end 16 | 17 | def close 18 | device.close if @needs_close 19 | EYLogger.close if EYLogger.setup? 20 | end 21 | end 22 | 23 | class EYLogger 24 | 25 | # Sets up the EYLogger to beging capturing capistrano's logging. You should pass the capistrno configuration 26 | # and the deploy type as a string. The deploy type is for reporting purposes only but must be included. 27 | def self.setup(configuration, deploy_type, options = {}) 28 | @_configuration = configuration 29 | @_deploy_type = deploy_type.gsub(/:/, "_") 30 | @_log_path = options[:deploy_log_path] || Dir.tmpdir 31 | @_log_path << "/" unless @_log_path =~ /\/$/ 32 | FileUtils.mkdir_p(@_log_path) 33 | @_setup = true 34 | @_success = true 35 | end 36 | 37 | def self.log(level, message, line_prefix=nil) 38 | return nil unless setup? 39 | @release_name = @_configuration[:release_name] if @release_name.nil? 40 | @_log_file_path = @_log_path + @release_name + ".log" unless @_log_file_path 41 | @_deploy_log_file = File.open(@_log_file_path, "w") if @_deploy_log_file.nil? 42 | 43 | indent = "%*s" % [Logger::MAX_LEVEL, "*" * (Logger::MAX_LEVEL - level)] 44 | message.each_line do |line| 45 | if line_prefix 46 | @_deploy_log_file << "#{indent} [#{line_prefix}] #{line.strip}\n" 47 | else 48 | @_deploy_log_file << "#{indent} #{line.strip}\n" 49 | end 50 | end 51 | end 52 | 53 | def self.post_process 54 | unless ::Interrupt === $! 55 | puts "\n\nPlease wait while the log file is processed\n" 56 | # Should dump the stack trace of an exception if there is one 57 | error = $! 58 | unless error.nil? 59 | @_deploy_log_file << error.message << "\n" 60 | @_deploy_log_file << error.backtrace.join("\n") 61 | @_success = false 62 | end 63 | self.close 64 | 65 | hooks = [:any] 66 | hooks << (self.successful? ? :success : :failure) 67 | puts "Executing Post Processing Hooks" 68 | hooks.each do |h| 69 | @_post_process_hooks[h].each do |key| 70 | @_configuration.parent.find_and_execute_task(key) 71 | end 72 | end 73 | puts "Finished Post Processing Hooks" 74 | end 75 | end 76 | 77 | # Adds a post processing hook. 78 | # 79 | # Provide a task name to execute. These tasks are executed after capistrano has actually run its course. 80 | # 81 | # Takes a key to control when the hook is executed.' 82 | # :any - always executed 83 | # :success - only execute on success 84 | # :failure - only execute on failure 85 | # 86 | # ==== Example 87 | # Capistrano::EYLogger.post_process_hook( "ey_logger:upload_log_to_slice", :any) 88 | # 89 | def self.post_process_hook(task, key = :any) 90 | @_post_process_hooks ||= Hash.new{|h,k| h[k] = []} 91 | @_post_process_hooks[key] << task 92 | end 93 | 94 | def self.setup? 95 | !!@_setup 96 | end 97 | 98 | def self.deploy_type 99 | @_deploy_type 100 | end 101 | 102 | def self.successful? 103 | !!@_success 104 | end 105 | 106 | def self.failure? 107 | !@_success 108 | end 109 | 110 | def self.log_file_path 111 | @_log_file_path 112 | end 113 | 114 | def self.remote_log_file_name 115 | @_log_file_name ||= "#{@_configuration[:release_name]}-#{@_deploy_type}-#{self.successful? ? "SUCCESS" : "FAILURE"}.log" 116 | end 117 | 118 | def self.close 119 | @_deploy_log_file.flush unless @_deploy_log_file.nil? 120 | @_deploy_log_file.close unless @_deploy_log_file.nil? 121 | @_setup = false 122 | end 123 | 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/eycap/recipes/deploy.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "..", "lib", "ey_logger.rb") 2 | Capistrano::Configuration.instance(:must_exist).load do 3 | 4 | namespace :deploy do 5 | # This is here to hook into the logger for deployment tasks 6 | ["deploy", "deploy:long", "deploy:migrations", "deploy:migrate", "deploy:update_code"].each do |tsk| 7 | before(tsk) do 8 | Capistrano::EYLogger.setup( self, tsk ) 9 | at_exit{ Capistrano::EYLogger.post_process if Capistrano::EYLogger.setup? } 10 | end 11 | end 12 | 13 | desc "Link the database.yml and mongrel_cluster.yml files into the current release path." 14 | task :symlink_configs, :roles => :app, :except => {:no_release => true} do 15 | run <<-CMD 16 | cd #{latest_release} && 17 | ln -nfs #{shared_path}/config/database.yml #{latest_release}/config/database.yml && 18 | ln -nfs #{shared_path}/config/mongrel_cluster.yml #{latest_release}/config/mongrel_cluster.yml 19 | CMD 20 | end 21 | 22 | desc <<-DESC 23 | Run the migrate rake task. By default, it runs this in most recently \ 24 | deployed version of the app. However, you can specify a different release \ 25 | via the migrate_target variable, which must be one of :latest (for the \ 26 | default behavior), or :current (for the release indicated by the \ 27 | `current' symlink). Strings will work for those values instead of symbols, \ 28 | too. You can also specify additional environment variables to pass to rake \ 29 | via the migrate_env variable. Finally, you can specify the full path to the \ 30 | rake executable by setting the rake variable. The defaults are: 31 | 32 | set :rake, "rake" 33 | set :framework, "merb" 34 | set :merb_env, "production" 35 | set :migrate_env, "" 36 | set :migrate_target, :latest 37 | DESC 38 | task :migrate, :roles => :db, :only => { :primary => true } do 39 | rake = fetch(:rake, "rake") 40 | 41 | framework = fetch(:framework, "rails") 42 | if framework.match(/^rails$/i) 43 | app_env = fetch(:rails_env, "production") 44 | else 45 | app_env = fetch("#{framework.downcase}_env".to_sym, "production") 46 | end 47 | 48 | migrate_env = fetch(:migrate_env, "") 49 | migrate_target = fetch(:migrate_target, :latest) 50 | 51 | directory = case migrate_target.to_sym 52 | when :current then current_path 53 | when :latest then current_release 54 | else raise ArgumentError, "unknown migration target #{migrate_target.inspect}" 55 | end 56 | 57 | run "cd #{directory}; #{rake} #{framework.upcase}_ENV=#{app_env} #{migrate_env} db:migrate ;" 58 | end 59 | 60 | desc "Display the maintenance.html page while deploying with migrations. Then it restarts and enables the site again." 61 | task :long do 62 | transaction do 63 | update_code 64 | web.disable 65 | symlink 66 | migrate 67 | end 68 | 69 | restart 70 | web.enable 71 | end 72 | 73 | desc "Restart the Mongrel processes on the app slices." 74 | task :restart, :roles => :app do 75 | mongrel.restart 76 | end 77 | 78 | desc "Start the Mongrel processes on the app slices." 79 | task :spinner, :roles => :app do 80 | mongrel.start 81 | end 82 | 83 | desc "Start the Mongrel processes on the app slices." 84 | task :start, :roles => :app do 85 | mongrel.start 86 | end 87 | 88 | desc "Stop the Mongrel processes on the app slices." 89 | task :stop, :roles => :app do 90 | mongrel.stop 91 | end 92 | 93 | namespace :web do 94 | desc <<-DESC 95 | Present a maintenance page to visitors. Disables your application's web \ 96 | interface by writing a "maintenance.html" file to each web server. The \ 97 | servers must be configured to detect the presence of this file, and if \ 98 | it is present, always display it instead of performing the request. 99 | 100 | By default, the maintenance page will just say the site is down for \ 101 | "maintenance", and will be back "shortly", but you can customize the \ 102 | page by specifying the REASON and UNTIL environment variables: 103 | 104 | $ cap deploy:web:disable \\ 105 | REASON="hardware upgrade" \\ 106 | UNTIL="12pm Central Time" 107 | 108 | Further customization copy your html file to shared_path+'/system/maintenance.html.custom'. 109 | If this file exists it will be used instead of the default capistrano ugly page 110 | DESC 111 | task :disable, :roles => :web, :except => { :no_release => true } do 112 | maint_file = "#{shared_path}/system/maintenance.html" 113 | require 'erb' 114 | on_rollback { run "rm #{shared_path}/system/maintenance.html" } 115 | 116 | reason = ENV['REASON'] 117 | deadline = ENV['UNTIL'] 118 | 119 | template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml")) 120 | result = ERB.new(template).result(binding) 121 | 122 | put result, "#{shared_path}/system/maintenance.html.tmp", :mode => 0644 123 | run "if [ -f #{shared_path}/system/maintenance.html.custom ]; then cp #{shared_path}/system/maintenance.html.custom #{maint_file}; else cp #{shared_path}/system/maintenance.html.tmp #{maint_file}; fi" 124 | end 125 | end 126 | end 127 | 128 | end 129 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # eycap [![Build Status](https://secure.travis-ci.org/engineyard/eycap.png)](http://travis-ci.org/engineyard/eycap) 2 | 3 | ## Description 4 | 5 | The Engine Yard capistrano tasks are for use specifically with Engine Yard Managed services. But can be used as examples for building other tasks as well. 6 | 7 | ## Requirements 8 | 9 | * [Capistrano](https://github.com/capistrano/capistrano) >= 2.2.0 10 | 11 | * NOTE: When using a git repository use Capistrano >= 2.5.3. 12 | 13 | ## Install 14 | 15 | Use your `Gemfile` and `bundler` to both document and install the `eycap` gem to your application. We also recommend the following gems to be configured along side `eycap`. Add these to your `Gemfile`: 16 | 17 | ```ruby 18 | group :development, :test do 19 | gem 'eycap', :require => false 20 | gem 'capistrano', '~> 2.15' 21 | gem 'net-ssh', '~> 2.7.0' 22 | end 23 | ``` 24 | 25 | Then run bundle install to install the `eycap` and other gem(s). 26 | 27 | $ bundle install 28 | 29 | Then in your `deploy.rb` file you'll need to add the following require statement to the begininng of the file: 30 | 31 | ``` 32 | require "eycap/recipes" 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Configuration 38 | 39 | Your initial deploy.rb will be provided for you when your servers are provisioned on Engine Yard Managed. In order to deploy your application, you can go to the `RAILS_ROOT` folder and run: 40 | 41 | $ capify . 42 | 43 | This generates the `Capfile` and the `config/deploy.rb` file for you. You'll replace the `config/deploy.rb` file with the `deploy.rb` given to you by Engine Yard. 44 | 45 | For deploying Rails 3.1 or greater apps using the [asset pipeline](https://github.com/engineyard/eycap/wiki/Asset-Pipeline) read more on the linked page. 46 | 47 | ### Setup restart server 48 | 49 | Mongrel is the default server, to override this default you'll need to define the following in your `deploy.rb` file: 50 | 51 | ```ruby 52 | namespace :deploy do 53 | 54 | task :restart, :roles => :app do 55 | # mongrel.restart 56 | end 57 | 58 | 59 | task :spinner, :roles => :app do 60 | # mongrel.start 61 | end 62 | 63 | 64 | task :start, :roles => :app do 65 | # mongrel.start 66 | end 67 | 68 | 69 | task :stop, :roles => :app do 70 | # mongrel.stop 71 | end 72 | 73 | end 74 | ``` 75 | 76 | Replace the commented out with your server (passenger, unicorn, thin, puma, etc.) and then it will override the default of mongrel. 77 | 78 | ### Deploying to Environment 79 | 80 | To ensure your environments are ready to deploy, check on staging. 81 | 82 | $ cap staging deploy:check 83 | 84 | This will determine if all requirements are met to deploy. Sometimes if the default folders are not setup you may be able to repair by running: 85 | 86 | $ cap staging deploy:setup 87 | 88 | If you cannot get `deploy:check` to pass, please open a [new support ticket](https://support.cloud.engineyard.com/tickets/new) and let us know. 89 | 90 | Now you're ready to do a test deploy. 91 | 92 | Optionally, `cap deploy:cold` will run your migrations and start (instead of restart) your app server. 93 | 94 | $ cap staging deploy:cold 95 | 96 | Or if you have already dumped a copy of your data to staging or do not want to run migrations you can simply do a deploy. 97 | 98 | $ cap staging deploy 99 | 100 | And to do all this on production, just change the environment name and you'll be all set. 101 | 102 | $ cap production deploy 103 | 104 | ## Eycap Commands 105 | 106 | For a list of all available commands, run: 107 | 108 | $ cap -T 109 | 110 | This will show you not only the default capistrano commands but also the ones you get by including the eycap gem. 111 | 112 | ## Custom binaries path 113 | 114 | In rare cases (`unicorn` / `sphinx`) it is required to set custom path for binaries when using 115 | development versions of scripts. It is as easy as: 116 | 117 | ```ruby 118 | set :engineyard_bin, "/engineyard/custom" 119 | ``` 120 | 121 | The default is `/engineyard/bin` and is just fine in normal deployment. 122 | 123 | ## Pull Requests 124 | 125 | If you'd like to contribute to the eycap gem please create a fork, then send a pull request and a member of the eycap team will review it. 126 | 127 | ## Issues 128 | 129 | When you run into a problem please check the [issues](/issues) to see if one has been reported. If not, please report the issue and we'll get to work on fixing it. 130 | 131 | ## License 132 | 133 | Copyright (c) Engine Yard 134 | 135 | Permission is hereby granted, free of charge, to any person obtaining 136 | a copy of this software and associated documentation files (the 137 | "Software"), to deal in the Software without restriction, including 138 | without limitation the rights to use, copy, modify, merge, publish, 139 | distribute, sublicense, and/or sell copies of the Software, and to 140 | permit persons to whom the Software is furnished to do so, subject to 141 | the following conditions: 142 | 143 | The above copyright notice and this permission notice shall be 144 | included in all copies or substantial portions of the Software. 145 | 146 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 147 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 148 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 149 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 150 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 151 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 152 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 153 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.6.12/ 2014-06-14 2 | * #44 Share the bundle config instead of creating it on multiple slices - dholdren 3 | 4 | == 0.6.11/ 2014-02-21 5 | * #46 Fix path issue in bundle_config - krutten. 6 | 7 | == 0.6.10 / 2014-02-20 8 | * #45 Bundler will sometimes try and verify the dependancy tree - jcstringer. 9 | * This adds the ``--deployment`` flag to the ``bundle`` command. 10 | 11 | == 0.6.9 / 2013-05-29 12 | * #43 eycap v0.6.8 fails to detect rvm gem path with rvm-capistrano - Michal Papis. 13 | 14 | == 0.6.8 / 2013-05-29 15 | * #42 allow custom path for ey binaries - Michal Papis. 16 | * Added bundle_config task for bundler - Tyler Bird. 17 | * Change default output of bundle install to --quiet for speed increase - Tyler Bird. 18 | 19 | == 0.6.7 / 2013-03-25 20 | * follow up to #37, pull #40 works to ensure rvm and non-rvm work properly - Michal Papis. 21 | * #39 Joe does some good house cleaning, thanks! 22 | * and a Delayed Job tweak by Kevin on #41, thank you. 23 | 24 | == 0.6.6 / 2013-03-15 25 | * adding rvm support to eycap #37 - thank you Michal Papis! 26 | 27 | == 0.6.5 / 2012-12-07 28 | * removed unnecessary if/then statement see closed pull request #36 29 | * Merged pull request #34 - include rails_env in Thinking Sphinx recipe 30 | 31 | == 0.6.4 / 2012-10-04 32 | * Fix the incorrect pluralization of "eycap/recipes" file because eycap would not include itself anymore. 33 | 34 | == 0.6.3 / 2012-09-21 35 | * Changing all /engineyard/bin/unicorn tasks in unicorn recipe to run. 36 | 37 | == 0.6.2 / 2012-09-21 38 | * Added minitest framework. 39 | * Change deploy user task in unicorn recipe from sudo to run. 40 | 41 | == 0.6.1 / 2012-09-06 42 | * The Yes We Can patch, which maintains backwards compatibility for the eycap require statement. 43 | 44 | == 0.6.0 / 2012-09-06 45 | * Bundler and a clean gemspec now manage the release of eycap. 46 | * Use the :bundle_without to optionally specify groups more than 'test and development' only. 47 | 48 | == 0.5.23 / 2012-05-31 49 | * Changed README to markdown format. 50 | * Improved README to give instructions on general setup and usage. 51 | * Refactored default bundle install behavior, adding :no_bundle flag. 52 | * Merged pull request #32 - log more deploy commands. 53 | 54 | == 0.5.22 / 2011-12-02 55 | * merged pull request #28 - only perform the bundler.bundle_gems task on app servers that handle releases 56 | * merged pull request #30 - fix for race condition where binstubs is on an NFS volume 57 | 58 | == 0.5.21 / 2011-11-29 59 | * merged pull request #26 - symlink ey_bundler_binstubs to bin 60 | 61 | == 0.5.20 / 2011-07-21 62 | * merged pull request #25 - added :except clause for non-nginx app servers 63 | * merged pull request #24 - exclude 'test' and 'development' in bundler 64 | 65 | == 0.5.19 / 2011-07-19 66 | * Removed redundant bundle exec from rake db:migrate task as bundler will inject 'bundle exec rake' it since 1.0.8. 67 | 68 | == 0.5.18 / 2011-05-24 69 | * Added task to tail apache logs. (lightcap) 70 | * Using --binstubs to put executables in app_root/bin. (lightcap) 71 | * Added unicorn recipe to stop/start/restart. (timo3377) 72 | * Fix for eylogger and ruby 1.9.x. (atavistock) 73 | * Changed resque restart to sudo instead of run. (mdolian) 74 | 75 | == 0.5.17 / 2011-04-13 76 | * Fixed it so resque:restart is not called for any deploy. 77 | 78 | == 0.5.16 / 2011-04-08 79 | * Added improvement for postgres db:clone_to_local recipe. 80 | * Added correct requirement and symlink changes for resque recipe. 81 | 82 | == 0.5.15 / 2011-04-08 83 | * Added mysql2 support for database clone recipes. 84 | 85 | == 0.5.14 / 2011-04-06 86 | * Added resque recipe. 87 | 88 | == 0.5.13 / 2011-03-07 89 | * turned off EY weather notification. 90 | 91 | == 0.5.12 / 2011-03-07 92 | * turned off EY weather notification. 93 | 94 | == 0.5.11 / 2011-02-16 95 | * changed the exception variable for DJ recipe 96 | 97 | == 0.5.10 / 2011-02-16 98 | * added Delayed Job monit restart commands 99 | 100 | == 0.5.9 / 2011-01-28 101 | * bug fix for bundle install command 102 | 103 | == 0.5.8 / 2009-12-21 104 | * updated database clone to local task - adding ability to use mysql2 adapter. 105 | 106 | == 0.5.7 / 2009-12-21 107 | * updated database tasks - new xCloud infrastructure requires staging databases to use master not replica for tasks 108 | 109 | == 0.5.6 / 2009-6-17 110 | * updated bundler task so it won't install test or development gems 111 | * updated database tasks - based on Tyler Poland's update 112 | 113 | == 0.5.5 / 2009-3-16 114 | * fixed 2 bugs that are in 0.5.4 with the SSL and bundler recipes 115 | * use this version with bundler version 0.9.2 116 | 117 | == 0.5.4 / 2009-3-16 118 | * fixed gem bundler issue 119 | 120 | == 0.5.3 / 2009-1-27 121 | * created task cap ssl:create 122 | * use this version with bundler version 0.8 123 | 124 | == 0.5.2 / 2009-12-17 125 | * renamed task cap slice:tail_production_logs to cap slice:tail_environment_logs 126 | 127 | == 0.5.1 / 2009-11-12 128 | * using bundler's cache instead of symlinking on each deploy. 129 | 130 | == 0.5.0 / 2009-10-07 131 | * moved from github to gemcutter for hosting. 132 | 133 | == 0.4.16 / 2009-09-30 134 | * apps ping weather app before and after each deploy 135 | 136 | == 0.4.15 / 2009-09-02 137 | * added include in gemspec for bundler file 138 | 139 | == 0.4.14 / 2009-08-31 140 | * restored functionality to remove temporary compressed sql file after db:clone_prod_to_staging 141 | * renamed gz extension files to bz2 in db:clone_to_local 142 | * memcached: fix netcat not hanging up on a flush 143 | 144 | == 0.4.13 / 2009-07-30 145 | * changed Gemfile to absolute path 146 | * merged changes from square/master for unshared_remote_cache cached deploy strategy 147 | 148 | == 0.4.12 / 2009-06-26 149 | * removed condition for dbhost that was useless and tested db:clone_prod_to_staging 150 | 151 | == 0.4.11 / 2009-06-25 152 | * changed nginx start and restart to give output to cap 153 | * fixed db:clone_to_local task 154 | 155 | == 0.4.10 / 2009-06-24 156 | * using nohup on nginx start and restart 157 | 158 | == 0.4.9 / 2009-06-22 159 | * added nginx reload, upgrade and configtest - thanks Randy (ydnar) 160 | 161 | == 0.4.8 / there is no 0.4.8, just like there is no spoon. 162 | 163 | == 0.4.7 / 2009-05-12 164 | * fixed bug in clone_prod_to_staging and clone_to_local db tasks for postgres 165 | where the regex matching the password prompt for the restore was wrong 166 | 167 | == 0.4.6 / 2009-03-24 168 | * fixed bug to restore clone_prod_to_staging using the compressed file 169 | 170 | == 0.4.5 / 2009-03-03 171 | * happy square root day! 172 | * added the staging restore to db:clone_prod_to_staging 173 | 174 | == 0.4.4 / 2009-03-03 175 | * happy square root day! 176 | * fixed the *correct* database.rb file for the db:clone_prod_to_staging 177 | 178 | == 0.4.3 / 2009-02-11 179 | * updated db:dump command for Engine Yard Solo offering, fixing a bug where 180 | the dbname wasn't included. 181 | 182 | == 0.4.2 / 2009-02-11 183 | * added condition to determine if the db:dump command is run against our 184 | traditional offering or the new cloud offering 185 | * fixed bug where if a production db host name doesn't have a -master at the 186 | end it won't run the db:clone_prod_to_staging correctly 187 | 188 | == 0.4.1 / 2009-01-09 189 | * fixed bug for passenger:restart 190 | 191 | == 0.4.0 / 2009-01-09 192 | * add passenger:restart task 193 | * add apache stop, start, restart and reload tasks 194 | * Don't display database passwords in the logs, output, etc. use stdin instead 195 | 196 | == 0.3.11 / 2008-11-09 197 | * filtered_remote_cache uses svn switch 198 | 199 | == 0.3.9 / 2008-09-29 200 | * add reindex task for /engineyard/bin/acts_as_sphinx_searchd 201 | * add reindex and configure task for /engineyard/bin/ultrasphinx_searchd 202 | * add reindex and configure task for /engineyard/bin/thinking_sphinx_searchd 203 | 204 | == 0.3.8 / 2008-09-22 205 | * add PostgreSQL support to the database.rb recipe. 206 | 207 | == 0.3.7 / 2008-08-23 208 | * fix from customer for filtered_remote_cache to just use plain grep. 209 | 210 | == 0.3.6 / 2008-07-17 211 | * features updated VERSION 212 | 213 | == 0.3.5 / 2008-07-17 214 | * filtered_remote_cache uses cached checkout's repository root for comparison for speedier tagged/branched deploys 215 | 216 | == 0.3.4 / 2008-06-17 217 | * rake install_gem_no_doc for faster install 218 | * set role to db for backup_name task 219 | * add migration support for merb 220 | 221 | == 0.3.3 / 2008-05-07 222 | * add ey_logger to log deploys to server 223 | 224 | == 0.3.2 / 2008-04-29 225 | * adding db:clone_to_local task to take a dump on the slice, fetch it, and load it into your dev db on your workstation 226 | * only clone from replica database 227 | * remove call to variable in task desc 228 | * fix ferret symlinking 229 | * gemspec for github 230 | 231 | == 0.3.1 / 2008-03-17 232 | * Make the custom maintenance pages actually work! 233 | 234 | == 0.3.0 / 2008-03-05 235 | * Adding custom maintenance pages is now as easy as copying a custom html file to #{shared_path}/system/maintenance.html.custom 236 | 237 | == 0.2.10 / 2008-03-02 238 | * Symlink memcached.yml only on :memcached => true, except :no_release => true 239 | 240 | == 0.2.9 / 2008-02-27 241 | * Fix a bug with ultrasphinx:configure running on multiple hosts 242 | 243 | == 0.2.8 / 2008-02-20 244 | * Add tasks for solr starting,stopping, and log tailing. 245 | * Add monit namespace and make appservers php/merb compatible 246 | * Make ultrasphinx symlinking a little smarter 247 | * Add tomcat tasks for spongecell 248 | 249 | == 0.2.6 / 2008-02-14 250 | * Make mongrel restarts only apply to mongrel slices 251 | 252 | == 0.2.5 / 2008-02-11 253 | * Added db cloning task 254 | 255 | == 0.2.4 / 2008-02-06 256 | * Symlink memcached.yml in on deploy if you enable the callback 257 | 258 | == 0.2.3 / 2008-02-01 259 | * Make log tailing environmentally aware. Also add tasks to tail mongrel logs 260 | 261 | == 0.2.2 / 2008-01-25 262 | * sphinx:configure ultrasphinx configuration task 263 | 264 | == 0.2.1 / 2008-01-25 265 | * override default deploy recipe start/stop tasks 266 | 267 | == 0.2.0 / 2008-01-23 268 | * sphinx:symlink ultrasphinx configuration directory 269 | 270 | == 0.1.9 / 2008-01-21 271 | * Correct memcached tasks. 272 | 273 | == 0.1.8 / 2008-01-20 274 | * Add memcached tasks 275 | 276 | == 0.1.7 / 2008-01-19 277 | * fix symlink_configs task 278 | 279 | == 0.1.6 / 2008-01-19 280 | * add restart tasks for backgroundrb 281 | 282 | == 0.1.5 / 2008-01-18 283 | * fixed bug in filtered_remote_cache that prevented a changed checkout URL from taking over the cache 284 | 285 | == 0.1.4 / 2008-01-17 286 | * added sphinx:[reindex|start|stop|restart] matches only app servers with :sphinx => true 287 | 288 | == 0.1.3 / 2008-01-17 289 | * filtered_remote_cache to removes the cached copy of the source URL changed 290 | 291 | == 0.1.2 / 2008-01-15 292 | * added filtered_remote_cache capistrano deployment strategy 293 | 294 | == 0.1.1 / 2008-01-15 295 | * removed database tasks until problem with 'defer' is solved 296 | 297 | == 0.1.0 / 2008-01-14 298 | * Bugfix for empty :application variable 299 | 300 | == 0.0.1 / 2008-01-14 301 | * Initial release 302 | --------------------------------------------------------------------------------