capistrano-kitchen 0.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.ruby-gemset.template +1 -0
- data/.ruby-version.template +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +5 -0
- data/Gemfile +8 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +46 -0
- data/Rakefile +14 -0
- data/capistrano-kitchen.gemspec +29 -0
- data/lib/capistrano-kitchen.rb +41 -0
- data/lib/capistrano_kitchen/dishes/aptitude/manage.rb +38 -0
- data/lib/capistrano_kitchen/dishes/bundler/hooks.rb +7 -0
- data/lib/capistrano_kitchen/dishes/bundler/install.rb +79 -0
- data/lib/capistrano_kitchen/dishes/git/hooks.rb +3 -0
- data/lib/capistrano_kitchen/dishes/git/install.rb +18 -0
- data/lib/capistrano_kitchen/dishes/java_7_oracle/hooks.rb +5 -0
- data/lib/capistrano_kitchen/dishes/java_7_oracle/install.rb +17 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/app.conf +66 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/hooks.rb +11 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/install.rb +176 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/manage.rb +1 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/mime.types.erb +79 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx.conf +138 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.god +47 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.init +95 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.logrotate +18 -0
- data/lib/capistrano_kitchen/dishes/nginx_unicorn/stub_status.conf +16 -0
- data/lib/capistrano_kitchen/dishes/nodejs/hooks.rb +4 -0
- data/lib/capistrano_kitchen/dishes/nodejs/install.rb +13 -0
- data/lib/capistrano_kitchen/dishes/provision/empty_roles.rb +60 -0
- data/lib/capistrano_kitchen/dishes/provision/manage.rb +49 -0
- data/lib/capistrano_kitchen/dishes/provision/task_once.rb +62 -0
- data/lib/capistrano_kitchen/dishes/ruby/hooks.rb +7 -0
- data/lib/capistrano_kitchen/dishes/ruby/install.rb +55 -0
- data/lib/capistrano_kitchen/dishes/teelogger/teelogger.rb +121 -0
- data/lib/capistrano_kitchen/dishes/unicorn/hooks.rb +9 -0
- data/lib/capistrano_kitchen/dishes/unicorn/install.rb +120 -0
- data/lib/capistrano_kitchen/dishes/unicorn/unicorn.god +71 -0
- data/lib/capistrano_kitchen/dishes/unicorn/unicorn.rb.erb +191 -0
- data/lib/capistrano_kitchen/recipes/aptitude.rb +1 -0
- data/lib/capistrano_kitchen/recipes/bundler.rb +1 -0
- data/lib/capistrano_kitchen/recipes/git.rb +1 -0
- data/lib/capistrano_kitchen/recipes/java_7_oracle.rb +1 -0
- data/lib/capistrano_kitchen/recipes/nginx_unicorn.rb +1 -0
- data/lib/capistrano_kitchen/recipes/nodejs.rb +1 -0
- data/lib/capistrano_kitchen/recipes/provision.rb +1 -0
- data/lib/capistrano_kitchen/recipes/ruby.rb +1 -0
- data/lib/capistrano_kitchen/recipes/teelogger.rb +1 -0
- data/lib/capistrano_kitchen/recipes/unicorn.rb +1 -0
- data/lib/capistrano_kitchen/recipes/utilities.rb +442 -0
- data/lib/capistrano_kitchen/version.rb +3 -0
- data/spec/capistrano_kitchen_spec.rb +5 -0
- data/spec/spec_helper.rb +21 -0
- metadata +200 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
# @author Donovan Bray <donnoman@donovanbray.com>
|
2
|
+
Capistrano::Configuration.instance(true).load do
|
3
|
+
before "deploy:start", "unicorn:configure"
|
4
|
+
before "deploy:restart", "unicorn:configure"
|
5
|
+
after "deploy:stop", "unicorn:stop"
|
6
|
+
after "deploy:restart", "unicorn:restart"
|
7
|
+
before "deploy:finalize_update", "unicorn:finalize_update"
|
8
|
+
on :load, "unicorn:watcher"
|
9
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../utilities')
|
2
|
+
|
3
|
+
Capistrano::Configuration.instance(true).load do
|
4
|
+
namespace :unicorn do
|
5
|
+
|
6
|
+
set :unicorn_template_path, File.join(File.dirname(__FILE__),'unicorn.rb.erb')
|
7
|
+
set :unicorn_god_path, File.join(File.dirname(__FILE__),'unicorn.god')
|
8
|
+
set(:unicorn_user) {user}
|
9
|
+
set(:unicorn_group) {user}
|
10
|
+
set :unicorn_workers, 3
|
11
|
+
set :unicorn_backlog, 128
|
12
|
+
set :unicorn_tries, -1
|
13
|
+
set :unicorn_timeout, 120
|
14
|
+
set(:unicorn_root) { current_path }
|
15
|
+
set :unicorn_socket_location, %q{File.expand_path('../../../../shared/sockets/unicorn.sock', __FILE__)} #this IS CORRECTLY a non-interpolated string, to be evaled later.
|
16
|
+
set :unicorn_backup_socket_location, %q{File.expand_path('../../tmp/sockets/unicorn.sock', __FILE__)} #this IS CORRECTLY a non-interpolated string, to be evaled later.
|
17
|
+
set :unicorn_relative_socket_location, 'tmp/sockets'
|
18
|
+
set :unicorn_watcher, nil
|
19
|
+
set :unicorn_suppress_runner, false
|
20
|
+
set :unicorn_suppress_configure, false
|
21
|
+
set :unicorn_init_name, "unicorn"
|
22
|
+
set(:unicorn_god_group_name) { "unicorns" } #must not equal the unicorn_init_name, god will fail to load complaining.
|
23
|
+
set(:unicorn_god_name) { unicorn_init_name } #name for original compatability.
|
24
|
+
set :unicorn_use_syslogger, false
|
25
|
+
set :unicorn_god_start_grace, 30
|
26
|
+
set :unicorn_god_restart_grace, 30
|
27
|
+
set :unicorn_god_stop_grace, 30
|
28
|
+
set :unicorn_god_stop_timeout, 120
|
29
|
+
set :unicorn_disable_rack_attack, false
|
30
|
+
|
31
|
+
desc "select watcher"
|
32
|
+
task :watcher do
|
33
|
+
unicorn.send("watch_with_#{unicorn_watcher}".to_sym) unless unicorn_watcher.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Use GOD as unicorn's runner"
|
37
|
+
task :watch_with_god do
|
38
|
+
#This is a test pattern, and may not be the best way to handle diverging
|
39
|
+
#maintenance tasks based on which watcher is used but here goes:
|
40
|
+
#rejigger the maintenance tasks to use god when god is in play
|
41
|
+
%w(start stop restart).each do |t|
|
42
|
+
task t.to_sym, :roles => :app do
|
43
|
+
god.cmd "#{t} #{unicorn_god_name}" unless unicorn_suppress_runner
|
44
|
+
end
|
45
|
+
end
|
46
|
+
after "god:setup", "unicorn:setup_god"
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "setup god to watch unicorn"
|
50
|
+
task :setup_god, :roles => :app do
|
51
|
+
god.upload unicorn_god_path, "#{unicorn_init_name}.god"
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Installs unicorn'
|
55
|
+
task :install, :roles => :app do
|
56
|
+
logger.info "unicorn install doesn't do anything, make sure your Gemfile specifies a version of unicorn"
|
57
|
+
end
|
58
|
+
|
59
|
+
task :configure, :roles => :app do
|
60
|
+
# if you check in your unicorn.rb you can enable supressing this configure step
|
61
|
+
unless unicorn_suppress_configure
|
62
|
+
utilities.upload_template unicorn_template_path, "#{latest_release}/config/unicorn.rb"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "decrement the number of unicorn worker processes by one"
|
67
|
+
task :ttou, :roles => :app do
|
68
|
+
run "pkill -TTOU -f 'unicorn master'"
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "increment the number of unicorn worker processes by one"
|
72
|
+
task :ttin, :roles => :app do
|
73
|
+
run "pkill -TTIN -f 'unicorn master';true"
|
74
|
+
end
|
75
|
+
|
76
|
+
task :workers, :roles => :app do
|
77
|
+
run "ps aux | grep -c '[u]nicorn worker';true"
|
78
|
+
end
|
79
|
+
|
80
|
+
task :stop, :roles => :app do
|
81
|
+
run "cd #{latest_release} && kill -QUIT `cat tmp/pids/unicorn.pid`;true"
|
82
|
+
end
|
83
|
+
|
84
|
+
task :start, :roles => :app do
|
85
|
+
run "cd #{latest_release} && #{base_ruby_path}/bin/unicorn_rails -c config/unicorn.rb -E #{rails_env} -D"
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "restart unicorn"
|
89
|
+
task :restart, :roles => :app do
|
90
|
+
run "cd #{latest_release}; [ -f tmp/pids/unicorn.pid ] && kill -USR2 `cat tmp/pids/unicorn.pid` || #{base_ruby_path}/bin/unicorn_rails -c config/unicorn.rb -E #{rails_env} -D"
|
91
|
+
end
|
92
|
+
|
93
|
+
task :deprovision, :roles => :app do
|
94
|
+
unicorn.stop
|
95
|
+
god.remove "#{unicorn_init_name}.god"
|
96
|
+
god.restart
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "unicorn finalize_update hook to ensure sockets directory is symlinked without using shared_children"
|
100
|
+
task :finalize_update, :roles => :app do
|
101
|
+
escaped_release = latest_release.to_s.shellescape
|
102
|
+
commands = []
|
103
|
+
commands << "chmod -R -- g+w #{escaped_release}" if fetch(:group_writable, true)
|
104
|
+
[unicorn_relative_socket_location].map do |dir|
|
105
|
+
d = dir.shellescape
|
106
|
+
if (dir.rindex('/')) then
|
107
|
+
commands += ["rm -rf -- #{escaped_release}/#{d}",
|
108
|
+
"mkdir -p -- #{escaped_release}/#{dir.slice(0..(dir.rindex('/'))).shellescape}"]
|
109
|
+
else
|
110
|
+
commands << "rm -rf -- #{escaped_release}/#{d}"
|
111
|
+
end
|
112
|
+
commands << "mkdir -p -- #{shared_path}/#{dir.split('/').last.shellescape}"
|
113
|
+
commands << "ln -s -- #{shared_path}/#{dir.split('/').last.shellescape} #{escaped_release}/#{d}"
|
114
|
+
end
|
115
|
+
|
116
|
+
run commands.join(' && ') if commands.any?
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# http://unicorn.bogomips.org/SIGNALS.html
|
2
|
+
|
3
|
+
rails_env = "<%=rails_env%>"
|
4
|
+
rails_root = "<%=unicorn_root%>"
|
5
|
+
group_name = "<%=unicorn_god_group_name%>"
|
6
|
+
|
7
|
+
God.watch do |w|
|
8
|
+
w.group = group_name
|
9
|
+
w.name = "<%=unicorn_god_name%>"
|
10
|
+
w.interval = 10.seconds # 30 default
|
11
|
+
w.env = {
|
12
|
+
'UNICORN_WORKERS' => '<%=unicorn_workers%>'
|
13
|
+
}
|
14
|
+
|
15
|
+
# unicorn needs to be run from the rails root
|
16
|
+
w.start = "cd #{rails_root} && <%=base_ruby_path%>/bin/bundle exec unicorn -c #{rails_root}/config/unicorn.rb -E #{rails_env} -D"
|
17
|
+
|
18
|
+
# QUIT gracefully shuts down workers
|
19
|
+
w.stop = "kill -QUIT `cat #{rails_root}/tmp/pids/unicorn.pid`"
|
20
|
+
|
21
|
+
# USR2 causes the master to re-create itself and spawn a new worker pool
|
22
|
+
w.restart = "kill -USR2 `cat #{rails_root}/tmp/pids/unicorn.pid`"
|
23
|
+
|
24
|
+
w.start_grace = <%=unicorn_god_start_grace%>.seconds
|
25
|
+
w.restart_grace = <%=unicorn_god_restart_grace%>.seconds
|
26
|
+
w.stop_grace = <%=unicorn_god_stop_grace%>.seconds
|
27
|
+
w.stop_timeout = <%=unicorn_god_stop_timeout%>.seconds
|
28
|
+
|
29
|
+
w.pid_file = "#{rails_root}/tmp/pids/unicorn.pid"
|
30
|
+
|
31
|
+
w.uid = '<%=unicorn_user%>'
|
32
|
+
w.gid = '<%=unicorn_group%>'
|
33
|
+
|
34
|
+
# clean pid files before start if necessary
|
35
|
+
w.behavior(:clean_pid_file)
|
36
|
+
|
37
|
+
# determine the state on startup
|
38
|
+
w.transition(:init, { true => :up, false => :start }) do |on|
|
39
|
+
on.condition(:process_running) do |c|
|
40
|
+
c.running = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# determine when process has finished starting
|
45
|
+
w.transition([:start, :restart], :up) do |on|
|
46
|
+
on.condition(:process_running) do |c|
|
47
|
+
c.running = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# start if process is not running
|
52
|
+
w.transition(:up, :start) do |on|
|
53
|
+
on.condition(:process_exits) do |c|
|
54
|
+
c.notify = %w[ <%=god_notify_list%> ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# lifecycle
|
59
|
+
w.lifecycle do |on|
|
60
|
+
on.condition(:flapping) do |c|
|
61
|
+
c.to_state = [:start, :restart]
|
62
|
+
c.times = 5
|
63
|
+
c.within = 5.minute
|
64
|
+
c.transition = :unmonitored
|
65
|
+
c.retry_in = 10.minutes
|
66
|
+
c.retry_times = 5
|
67
|
+
c.retry_within = 2.hours
|
68
|
+
c.notify = %w[ <%=god_notify_list%> ]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# Sample verbose configuration file for Unicorn (not Rack)
|
2
|
+
#
|
3
|
+
# This configuration file documents many features of Unicorn
|
4
|
+
# that may not be needed for some applications. See
|
5
|
+
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
|
6
|
+
# for a much simpler configuration file.
|
7
|
+
#
|
8
|
+
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
|
9
|
+
# documentation.
|
10
|
+
|
11
|
+
<% if unicorn_use_syslogger %>
|
12
|
+
# Redirect Log to Syslog
|
13
|
+
# https://gist.github.com/531749
|
14
|
+
# add to your Gemfile:
|
15
|
+
# gem 'SyslogLogger', :git => 'git@github.com:castaclip/sysloglogger.git', :require => 'syslog_logger'
|
16
|
+
#
|
17
|
+
class Syslogger
|
18
|
+
def initialize
|
19
|
+
read, @write = IO.pipe
|
20
|
+
fork do
|
21
|
+
@write.close
|
22
|
+
$stdin.reopen read
|
23
|
+
exec *%w(logger -trails)
|
24
|
+
end
|
25
|
+
read.close
|
26
|
+
@write.sync = true
|
27
|
+
end
|
28
|
+
def write(progname=nil, &block)
|
29
|
+
@write.puts "#{progname} #{block.call if block_given?}"
|
30
|
+
end
|
31
|
+
alias :debug :write
|
32
|
+
alias :info :write
|
33
|
+
alias :warn :write
|
34
|
+
alias :error :write
|
35
|
+
alias :fatal :write
|
36
|
+
end
|
37
|
+
logger Syslogger.new
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<% if unicorn_disable_rack_attack %>
|
41
|
+
# This isn't a built-in to rack attack.
|
42
|
+
# Use something like this in your config.ru to take advantage of this parameter.
|
43
|
+
#
|
44
|
+
# Rack::Attack.throttle('req/ip', :limit => 15, :period => 3.second) do |req|
|
45
|
+
# req.ip unless ENV['DISABLE_RACK_ATTACK'].present?
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
ENV['DISABLE_RACK_ATTACK'] = '1'
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
# unicorn will stash its original command-line at startup for the USR2 upgrades, and cleaning up old revisions will cause revision-specific installations of unicorn to go missing and upgrades to fail. If you find yourself in this situation and can't afford downtime, you can override the existing unicorn executable path in the config file like this:
|
52
|
+
# Then use HUP to reload, and then continue with the USR2+QUIT upgrade sequence.
|
53
|
+
Unicorn::HttpServer::START_CTX[0] = "<%=current_path%>/bin/unicorn"
|
54
|
+
|
55
|
+
# Use at least one worker per core if you're on a dedicated server,
|
56
|
+
# more will usually help for _short_ waits on databases/caches.
|
57
|
+
|
58
|
+
worker_processes(( workers = ENV['UNICORN_WORKERS'].to_i ) > 0 ? workers : 4)
|
59
|
+
|
60
|
+
# Help ensure your application will always spawn in the symlinked
|
61
|
+
# "current" directory that Capistrano sets up.
|
62
|
+
# working_directory "/path/to/app/current" # available in 0.94.0+
|
63
|
+
|
64
|
+
# listen on both a Unix domain socket and a TCP port,
|
65
|
+
# we use a shorter backlog for quicker failover when busy
|
66
|
+
# listen "/tmp/.sock", :backlog => 64
|
67
|
+
# listen 3000, :tcp_nopush => true
|
68
|
+
|
69
|
+
if File.exists?( shared_socket = <%=unicorn_socket_location%> )
|
70
|
+
listen shared_socket, :backlog => <%=unicorn_backlog%>, :tries => <%=unicorn_tries%>
|
71
|
+
else
|
72
|
+
listen <%=unicorn_backup_socket_location%>, :backlog => <%=unicorn_backlog%>, :tries => <%=unicorn_tries%>
|
73
|
+
end
|
74
|
+
|
75
|
+
timeout <%=unicorn_timeout%>
|
76
|
+
|
77
|
+
# feel free to point this anywhere accessible on the filesystem
|
78
|
+
# pid "/path/to/app/shared/pids/unicorn.pid"
|
79
|
+
pid File.expand_path('../../tmp/pids/unicorn.pid', __FILE__)
|
80
|
+
|
81
|
+
# By default, the Unicorn logger will write to stderr.
|
82
|
+
# Additionally, ome applications/frameworks log to stderr or stdout,
|
83
|
+
# so prevent them from going to /dev/null when daemonized here:
|
84
|
+
# stderr_path "/path/to/app/shared/log/unicorn.stderr.log"
|
85
|
+
# stdout_path "/path/to/app/shared/log/unicorn.stdout.log"
|
86
|
+
stderr_path File.expand_path('../../log/unicorn.log', __FILE__)
|
87
|
+
stdout_path File.expand_path('../../log/unicorn.log', __FILE__)
|
88
|
+
|
89
|
+
# combine REE with "preload_app true" for memory savings
|
90
|
+
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
91
|
+
preload_app true
|
92
|
+
GC.respond_to?(:copy_on_write_friendly=) and
|
93
|
+
GC.copy_on_write_friendly = true
|
94
|
+
|
95
|
+
# sets the working directory for Unicorn. This ensures SIGUSR2 will
|
96
|
+
# start a new instance of Unicorn in this directory. This may be
|
97
|
+
# a symlink, a common scenario for Capistrano users. Unlike
|
98
|
+
# all other Unicorn configuration directives, this binds immediately
|
99
|
+
# for error checking and cannot be undone by unsetting it in the
|
100
|
+
# configuration file and reloading.
|
101
|
+
working_directory "<%=current_path%>"
|
102
|
+
|
103
|
+
# sets the before_exec hook to a given Proc object. This
|
104
|
+
# Proc object will be called by the master process right
|
105
|
+
# before exec()-ing the new unicorn binary. This is useful
|
106
|
+
# for freeing certain OS resources that you do NOT wish to
|
107
|
+
# share with the reexeced child process.
|
108
|
+
# There is no corresponding after_exec hook (for obvious reasons).
|
109
|
+
before_exec do |server|
|
110
|
+
ENV.each do |key,value|
|
111
|
+
server.logger.warn "EXEC ENV['#{key}']=#{value}" if %w(BUNDLE_BIN_PATH GEM_PATH GEM_HOME RACK_ENV PATH BUNDLE_GEMFILE USER PWD).include?(key)
|
112
|
+
end
|
113
|
+
ENV["BUNDLE_GEMFILE"] = "<%=current_path%>/Gemfile"
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# sets before_fork got be a given Proc object. This Proc
|
118
|
+
# object will be called by the master process before forking
|
119
|
+
# each worker.
|
120
|
+
before_fork do |server, worker|
|
121
|
+
#Disconnections go in the before_fork, Reconnections should happen in the after_fork
|
122
|
+
|
123
|
+
# the following is highly recomended for Rails + "preload_app true"
|
124
|
+
# as there's no need for the master process to hold a connection
|
125
|
+
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
|
126
|
+
|
127
|
+
# Sequel contstants
|
128
|
+
defined?(DB) and DB.disconnect
|
129
|
+
defined?(SDB) and SDB.disconnect
|
130
|
+
# Riak connection via Ripple
|
131
|
+
Thread.current[:ripple_clent] = nil if Thread.current[:ripple_client]
|
132
|
+
|
133
|
+
#
|
134
|
+
# # *optionally* throttle the master from forking too quickly by sleeping
|
135
|
+
# sleep 1
|
136
|
+
end
|
137
|
+
|
138
|
+
# sets after_fork hook to a given block. This block will be called by
|
139
|
+
# the worker after forking. The following is an example hook which adds
|
140
|
+
# a per-process listener to every worker:
|
141
|
+
#
|
142
|
+
# after_fork do |server,worker|
|
143
|
+
# # per-process listener ports for debugging/admin:
|
144
|
+
# addr = "127.0.0.1:#{9293 + worker.nr}"
|
145
|
+
# # the negative :tries parameter indicates we will retry forever
|
146
|
+
# # waiting on the existing process to exit with a 5 second :delay
|
147
|
+
# # Existing options for Unicorn::Configurator#listen such as
|
148
|
+
# # :backlog, :rcvbuf, :sndbuf are available here as well.
|
149
|
+
# server.listen(addr, :tries => -1, :delay => 5, :backlog => 128)
|
150
|
+
# end
|
151
|
+
after_fork do |server, worker|
|
152
|
+
ENV.each do |key,value|
|
153
|
+
server.logger.info "AFTER ENV['#{key}']=#{value}" if %w(BUNDLE_BIN_PATH GEM_PATH GEM_HOME RACK_ENV PATH BUNDLE_GEMFILE PWD).include?(key)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Redis reconnection via the Redis gem
|
157
|
+
# Guard against all failures including the redis server not being present at the default 127.0.0.1:6379
|
158
|
+
begin
|
159
|
+
Redis.current.client.reconnect
|
160
|
+
rescue
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
|
165
|
+
# immediately start loading up a new version of itself (loaded with a new
|
166
|
+
# version of our app). When this new Unicorn is completely loaded
|
167
|
+
# it will begin spawning workers. The first worker spawned will check to
|
168
|
+
# see if an .oldbin pidfile exists. If so, this means we've just booted up
|
169
|
+
# a new Unicorn and need to tell the old one that it can now die. To do so
|
170
|
+
# we send it a QUIT.
|
171
|
+
#
|
172
|
+
# Using this method we get 0 downtime deploys.
|
173
|
+
|
174
|
+
# wait until last worker boots to send QUIT signal
|
175
|
+
# next if worker.nr != (server.worker_processes - 1)
|
176
|
+
# wait for only half of them to ease memory constraints
|
177
|
+
next if worker.nr != (( server.worker_processes / 2 ) > 0 ? ( server.worker_processes / 2 ) : 1 )
|
178
|
+
|
179
|
+
old_pid = "#{server.config[:pid]}.oldbin"
|
180
|
+
if File.exists?(old_pid) && server.pid != old_pid
|
181
|
+
begin
|
182
|
+
Process.kill("QUIT", old_pid_number = File.read(old_pid).to_i)
|
183
|
+
server.logger.info "I slayed the Unicorn! with PID #{old_pid_number}"
|
184
|
+
rescue Errno::ENOENT, Errno::ESRCH
|
185
|
+
server.logger.info "Darn, PID #{old_pid_number} was already dead."
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/aptitude/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/bundler/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/git/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/java_7_oracle/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/nginx_unicorn/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/nodejs/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/provision/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/ruby/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/teelogger/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '../dishes/unicorn/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
require 'json'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module Utilities
|
7
|
+
# utilities.config_gsub('/etc/example', /(.*)/im, "\\1")
|
8
|
+
def config_gsub(file, find, replace)
|
9
|
+
tmp="/tmp/#{File.basename(file)}"
|
10
|
+
get file, tmp
|
11
|
+
content=File.open(tmp).read
|
12
|
+
content.gsub!(find,replace)
|
13
|
+
put content, tmp
|
14
|
+
sudo "mv #{tmp} #{file}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# utilities.ask('What is your name?', 'John')
|
18
|
+
def ask(question, default='')
|
19
|
+
question = "\n" + question.join("\n") if question.respond_to?(:uniq)
|
20
|
+
answer = Capistrano::CLI.ui.ask(space(question)).strip
|
21
|
+
answer.empty? ? default : answer
|
22
|
+
end
|
23
|
+
|
24
|
+
# utilities.suggest_version(:ruby_ver, 'ruby-2.1.0')
|
25
|
+
def suggest_version(config_var,suggestion)
|
26
|
+
ver = utilities.ask("#{config_var}: [#{suggestion}] ?",suggestion)
|
27
|
+
logger.info %Q{*** To pin your provision to this version you should add "set :#{config_var}, '#{ver}'" to your deploy.rb}
|
28
|
+
ver
|
29
|
+
end
|
30
|
+
|
31
|
+
# utilities.yes?('Proceed with install?')
|
32
|
+
def yes?(question)
|
33
|
+
question = "\n" + question.join("\n") if question.respond_to?(:uniq)
|
34
|
+
question += ' (y/n)'
|
35
|
+
ask(question).downcase.include? 'y'
|
36
|
+
end
|
37
|
+
|
38
|
+
def gem_install_preamble
|
39
|
+
"#{base_ruby_path}/bin/gem install #{capture('gem -v').chomp[0] < "2" ? '-y' : ''} --no-rdoc --no-ri"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Uses the base ruby path to install gem(s), avoids installing the gem if it's already installed.
|
43
|
+
# Installs the gems detailed in +package+, selecting version +version+ if
|
44
|
+
# specified.
|
45
|
+
def gem_install(package, version=nil)
|
46
|
+
tries = 3
|
47
|
+
begin
|
48
|
+
cmd = "#{sudo} #{gem_install_preamble} #{version ? '-v '+version.to_s : ''} #{package}"
|
49
|
+
wrapped_cmd = "if ! #{base_ruby_path}/bin/gem list '#{package}' | grep --silent -e '#{package}.*#{version}'; then #{cmd}; fi"
|
50
|
+
run wrapped_cmd
|
51
|
+
#send(run_method,wrapped_cmd)
|
52
|
+
rescue Capistrano::Error
|
53
|
+
tries -= 1
|
54
|
+
retry if tries > 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Installs the gems detailed in +package+, selecting version +version+ if
|
59
|
+
# specified, after uninstalling all versions of previous gems of +package+
|
60
|
+
def gem_install_only(package, version=nil)
|
61
|
+
tries = 3
|
62
|
+
begin
|
63
|
+
run "if ! #{base_ruby_path}/bin/gem list '#{package}' | grep --silent -e '#{package} \(#{version}\)'; then #{sudo} #{base_ruby_path}/bin/gem uninstall --ignore-dependencies --executables --all #{package}; #{sudo} #{gem_install_preamble} #{version ? '-v '+version.to_s : ''} #{package}; fi"
|
64
|
+
rescue Capistrano::Error
|
65
|
+
tries -= 1
|
66
|
+
retry if tries > 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# uninstalls the gems detailed in +package+, selecting version +version+ if
|
71
|
+
# specified, otherwise all.
|
72
|
+
def gem_uninstall(package, version=nil)
|
73
|
+
cmd = "#{sudo} #{base_ruby_path}/bin/gem uninstall --ignore-dependencies --executables #{version ? '-v '+version.to_s : '--all'} #{package}"
|
74
|
+
run "if #{base_ruby_path}/bin/gem list '#{package}' | grep --silent -e '#{package}.*#{version}'; then #{cmd}; fi"
|
75
|
+
end
|
76
|
+
|
77
|
+
def aptitude_safe_upgrade
|
78
|
+
cmd = "#{sudo} dpkg --configure -a" #recover from previous failures.
|
79
|
+
run_with_input(cmd, input_query=/(<No>|\?)/, "N\n")
|
80
|
+
run "#{sudo} aptitude update"
|
81
|
+
#using every trick in the book to attempt to force it to not prompt.
|
82
|
+
cmd = "#{sudo} DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive aptitude safe-upgrade -o Aptitude::Delete-Unused=false -o Aptitude::CmdLine::Fix-Broken=true --quiet --assume-yes --target-release `lsb_release -cs`"
|
83
|
+
run_with_input(cmd, input_query=/(<No>|\?)/, "N\n") #attempt to answer ncurses overwrite popup like when libpam complains about local modifications.
|
84
|
+
end
|
85
|
+
|
86
|
+
# 10.04 <=> 12.04 naming compatability layer, find packages by executable name instead of package name.
|
87
|
+
# like add-apt-repository is in python-software-properties for 10.04 but software-properties-common in 12.04.
|
88
|
+
#
|
89
|
+
# utilities.apt_install_by_command('add-apt-repository')
|
90
|
+
def apt_install_by_command(command)
|
91
|
+
sudo_run_compressed %Q{
|
92
|
+
#{apt_get_preamble} install apt-file;
|
93
|
+
apt-file update;
|
94
|
+
#{apt_get_preamble} install `apt-file --non-interactive --package-only search #{command}`
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Install a package from a ppa utilizing add-apt-repository syntax
|
100
|
+
#
|
101
|
+
# utilities.apt_install_from_ppa("ppa:git-core/ppa","git-core")
|
102
|
+
def apt_install_from_ppa(ppa,package)
|
103
|
+
apt_install_by_command('add-apt-repository')
|
104
|
+
# 12.04 has a -y, 10.04 doesn't. (the check assumes all boxes are the same)
|
105
|
+
run "#{sudo} add-apt-repository #{capture("lsb_release -rs") < "12.04" ? "" : "-y" } #{ppa}"
|
106
|
+
apt_update
|
107
|
+
apt_install package
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
# utilities.apt_install %w[package1 package2]
|
113
|
+
# utilities.apt_install "package1 package2"
|
114
|
+
def apt_install(packages)
|
115
|
+
packages = packages.split(/\s+/) if packages.respond_to?(:split)
|
116
|
+
packages = Array(packages)
|
117
|
+
sudo "#{apt_get_preamble} install #{packages.join(" ")}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# utilities.apt_reinstall %w[package1 package2]
|
121
|
+
# utilities.apt_reinstall "package1 package2"
|
122
|
+
def apt_reinstall(packages)
|
123
|
+
packages = packages.split(/\s+/) if packages.respond_to?(:split)
|
124
|
+
packages = Array(packages)
|
125
|
+
sudo "#{apt_get_preamble} --reinstall install #{packages.join(" ")}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# remove is identical to install except that packages are removed instead of installed. Note the
|
129
|
+
# removing a package leaves its configuration files in system. If a plus sign is appended to the
|
130
|
+
# package name (with no intervening space), the identified package will be installed instead of
|
131
|
+
# removed.
|
132
|
+
def apt_remove(packages)
|
133
|
+
packages = packages.split(/\s+/) if packages.respond_to?(:split)
|
134
|
+
packages = Array(packages)
|
135
|
+
sudo "#{apt_get_preamble} remove #{packages.join(" ")}"
|
136
|
+
end
|
137
|
+
|
138
|
+
#purge is identical to remove except that packages are removed and purged (any configuration files are deleted too).
|
139
|
+
def apt_purge(packages)
|
140
|
+
packages = packages.split(/\s+/) if packages.respond_to?(:split)
|
141
|
+
packages = Array(packages)
|
142
|
+
sudo "#{apt_get_preamble} purge #{packages.join(" ")}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def apt_autoremove
|
146
|
+
sudo "#{apt_get} -qy autoremove"
|
147
|
+
end
|
148
|
+
|
149
|
+
def apt_fix_missing
|
150
|
+
sudo "#{apt_get} -qy update --fix-missing"
|
151
|
+
end
|
152
|
+
|
153
|
+
def apt_update
|
154
|
+
sudo "#{apt_get} -qy update"
|
155
|
+
end
|
156
|
+
|
157
|
+
def apt_upgrade
|
158
|
+
sudo_with_input "dpkg --configure -a", /\?/, "\n" #recover from failed dpkg
|
159
|
+
sudo "#{apt_get} -qy update"
|
160
|
+
sudo_with_input "#{apt_get_preamble} upgrade", /\?/, "\n" #answer the default if any package pops up a warning
|
161
|
+
end
|
162
|
+
|
163
|
+
def apt_get
|
164
|
+
"DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive apt-get"
|
165
|
+
end
|
166
|
+
|
167
|
+
def apt_get_preamble
|
168
|
+
"#{apt_get} -qyu --force-yes"
|
169
|
+
end
|
170
|
+
|
171
|
+
# utilities.sudo_upload('/local/path/to/file', '/remote/path/to/destination', options)
|
172
|
+
def sudo_upload(from, to, options={}, &block)
|
173
|
+
top.upload from, "/tmp/#{File.basename(to)}", options, &block
|
174
|
+
sudo "mv /tmp/#{File.basename(to)} #{to}", options
|
175
|
+
sudo "chmod #{options[:mode]} #{to}", options if options[:mode]
|
176
|
+
sudo "chown #{options[:owner]} #{to}", options if options[:owner]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Upload a file, running it through ERB
|
180
|
+
# utilities.sudo_upload_template('/local/path/to/file','remote/path/to/destination', options)
|
181
|
+
def sudo_upload_template(src,dst,options = {})
|
182
|
+
raise Capistrano::Error, "sudo_upload_template requires Source and Destination" if src.nil? or dst.nil?
|
183
|
+
put ERB.new(File.read(src),nil,'-').result(binding), "/tmp/#{File.basename(dst)}", options
|
184
|
+
sudo "mv /tmp/#{File.basename(dst)} #{dst}", options
|
185
|
+
sudo "chmod #{options[:mode]} #{dst}", options if options[:mode]
|
186
|
+
sudo "chown #{options[:owner]} #{dst}", options if options[:owner]
|
187
|
+
end
|
188
|
+
|
189
|
+
# Upload a file running it through ERB
|
190
|
+
def upload_template(src,dst,options = {})
|
191
|
+
raise Capistrano::Error, "put_template requires Source and Destination" if src.nil? or dst.nil?
|
192
|
+
put ERB.new(File.read(src)).result(binding), dst, options
|
193
|
+
end
|
194
|
+
|
195
|
+
# utilities.adduser('deploy')
|
196
|
+
def adduser(user, options={})
|
197
|
+
options[:shell] ||= '/bin/bash' # new accounts on ubuntu 6.06.1 have been getting /bin/sh
|
198
|
+
switches = ""
|
199
|
+
switches += " --system" if options[:system]
|
200
|
+
switches += ' --disabled-password --gecos ""'
|
201
|
+
switches += " --home #{options[:home]}" if options[:home]
|
202
|
+
switches += " --disabled-login" if options[:disabled_login]
|
203
|
+
switches += " --shell=#{options[:shell]} " if options[:shell] && !options[:system]
|
204
|
+
switches += ' --no-create-home ' if options[:nohome]
|
205
|
+
switches += " --uid #{options[:uid]} " if options[:uid]
|
206
|
+
switches += " --gid #{options[:gid]} " if options[:gid]
|
207
|
+
switches += " --ingroup #{options[:group]} " unless options[:group].nil?
|
208
|
+
invoke_command "grep '^#{user}:' /etc/passwd || sudo /usr/sbin/adduser #{switches} #{user}",
|
209
|
+
:via => run_method
|
210
|
+
end
|
211
|
+
|
212
|
+
# utilities.deluser('deploy')
|
213
|
+
def deluser(user, options={})
|
214
|
+
switches = '--force'
|
215
|
+
switches += " --backup" if options[:backup]
|
216
|
+
switches += " --quiet" if options[:quiet]
|
217
|
+
switches += " --remove-home" if options[:removehome]
|
218
|
+
switches += " --group #{options[:group]} " unless options[:group].nil?
|
219
|
+
invoke_command "sudo /usr/sbin/deluser #{switches} #{user}",
|
220
|
+
:via => run_method
|
221
|
+
end
|
222
|
+
|
223
|
+
#utilities.addgroup('deploy')
|
224
|
+
def addgroup(group,options={})
|
225
|
+
switches = ''
|
226
|
+
switches += " --system" if options[:system]
|
227
|
+
switches += " --gid #{options[:gid]} " if options[:gid]
|
228
|
+
invoke_command "/usr/sbin/addgroup #{switches} #{group}", :via => run_method
|
229
|
+
end
|
230
|
+
|
231
|
+
#utilities.delgroup('deploy')
|
232
|
+
def delgroup(group,options={})
|
233
|
+
switches = '--force'
|
234
|
+
switches += " --only-if-empty" if options[:ifempty]
|
235
|
+
invoke_command "/usr/sbin/delgroup #{switches} #{group}", :via => run_method
|
236
|
+
end
|
237
|
+
|
238
|
+
# role = :app
|
239
|
+
def with_role(role, &block)
|
240
|
+
original, ENV['HOSTS'] = ENV['HOSTS'], find_servers(:role => role).map{|d| d.host}.join(",")
|
241
|
+
begin
|
242
|
+
yield
|
243
|
+
ensure
|
244
|
+
ENV['HOSTS'] = original
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# role = :app
|
249
|
+
def without_role(role, &block)
|
250
|
+
original, ENV['HOSTS'] = ENV['HOSTS'], (find_servers() - find_servers(:roles => role)).map{|d| d.host}.join(",")
|
251
|
+
begin
|
252
|
+
yield
|
253
|
+
ensure
|
254
|
+
ENV['HOSTS'] = original
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# utilities.with_credentials(:user => 'xxxx', :password => 'secret')
|
259
|
+
# options = { :user => 'xxxxx', :password => 'xxxxx' }
|
260
|
+
def with_credentials(options={}, &block)
|
261
|
+
original_username, original_password = user, password
|
262
|
+
begin
|
263
|
+
set :user, options[:user] || original_username
|
264
|
+
set :password, options[:password] || original_password
|
265
|
+
yield
|
266
|
+
ensure
|
267
|
+
set :user, original_username
|
268
|
+
set :password, original_password
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def space(str)
|
273
|
+
"\n#{'=' * 80}\n#{str}"
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Run a command and ask for input when input_query is seen.
|
278
|
+
# Sends the response back to the server.
|
279
|
+
#
|
280
|
+
# +input_query+ is a regular expression that defaults to /^Password/.
|
281
|
+
# Can be used where +run+ would otherwise be used.
|
282
|
+
# run_with_input 'ssh-keygen ...', /^Are you sure you want to overwrite\?/
|
283
|
+
def run_with_input(shell_command, input_query=/^Password/, response=nil)
|
284
|
+
handle_command_with_input(:run, shell_command, input_query, response)
|
285
|
+
end
|
286
|
+
|
287
|
+
##
|
288
|
+
# Run a command using sudo and ask for input when a regular expression is seen.
|
289
|
+
# Sends the response back to the server.
|
290
|
+
#
|
291
|
+
# See also +run_with_input+
|
292
|
+
# +input_query+ is a regular expression
|
293
|
+
def sudo_with_input(shell_command, input_query=/^Password/, response=nil)
|
294
|
+
handle_command_with_input(:sudo, shell_command, input_query, response)
|
295
|
+
end
|
296
|
+
|
297
|
+
def invoke_with_input(shell_command, input_query=/^Password/, response=nil)
|
298
|
+
handle_command_with_input(run_method, shell_command, input_query, response)
|
299
|
+
end
|
300
|
+
|
301
|
+
##
|
302
|
+
# Run a long bash command thats indented with appropriate ';' that allow the linefeeds to be stripped and make a single concise shell command
|
303
|
+
#
|
304
|
+
# utilities.run_compressed %Q{
|
305
|
+
# cd /usr/local/src;
|
306
|
+
# if [ -d "#{mysql_tuner_name}" ]; then
|
307
|
+
# git pull;
|
308
|
+
# else
|
309
|
+
# git clone #{mysql_tuner_src_url} #{mysql_tuner_name};
|
310
|
+
# fi
|
311
|
+
# }
|
312
|
+
def run_compressed(cmd)
|
313
|
+
run cmd.split("\n").reject(&:empty?).map(&:strip).join(' ')
|
314
|
+
end
|
315
|
+
|
316
|
+
def sudo_run_compressed(cmd)
|
317
|
+
sudo compressed_join(cmd)
|
318
|
+
end
|
319
|
+
|
320
|
+
def compressed_join(cmd)
|
321
|
+
%Q{sh -c "#{cmd.split("\n").reject(&:empty?).map(&:strip).join(' ')}"}
|
322
|
+
end
|
323
|
+
|
324
|
+
##
|
325
|
+
# Checkout something from a git repo, update it if it's already checked out, and checkout the right ref.
|
326
|
+
# This will leave the checkout on the 'deploy' branch.
|
327
|
+
#
|
328
|
+
# utilities.sudo_git_clone_or_pull "git://github.com/scalarium/server-density-plugins.git", "/usr/local/src/scalarium"
|
329
|
+
#
|
330
|
+
# Had to change from using sudo to the deploying user because
|
331
|
+
def git_clone_or_pull(repo,dest,ref="master")
|
332
|
+
run "#{sudo} mkdir -p #{File.dirname(dest)}; #{sudo} chown -R #{user} #{File.dirname(dest)}"
|
333
|
+
cmd = compressed_join %Q{
|
334
|
+
if [ -d #{dest} ]; then
|
335
|
+
cd #{dest};
|
336
|
+
git fetch;
|
337
|
+
else
|
338
|
+
git clone #{repo} #{dest};
|
339
|
+
cd #{dest};
|
340
|
+
git checkout -b deploy;
|
341
|
+
fi
|
342
|
+
}
|
343
|
+
run_with_input(cmd,%r{\(yes/no\)}, "yes\n")
|
344
|
+
run_compressed %Q{
|
345
|
+
if [ `cd #{dest} && git tag | grep -c #{ref}` = '1' ]; then
|
346
|
+
cd #{dest}; git reset --hard #{ref};
|
347
|
+
else
|
348
|
+
cd #{dest}; git reset --hard origin/#{ref};
|
349
|
+
fi
|
350
|
+
}
|
351
|
+
end
|
352
|
+
|
353
|
+
##
|
354
|
+
# return the directory that holds the capfile
|
355
|
+
def caproot
|
356
|
+
File.dirname(capfile)
|
357
|
+
end
|
358
|
+
|
359
|
+
# logs the command then executes it locally.
|
360
|
+
# streams the command output
|
361
|
+
def stream_locally(cmd,opts={})
|
362
|
+
shell = opts[:shell] || 'bash'
|
363
|
+
tee = opts[:tee]
|
364
|
+
redact = opts[:redact]
|
365
|
+
redact_replacement = opts[:redact_replacment] || '-REDACTED-'
|
366
|
+
cmd = [shell,'-c "',cmd.gsub(/"/,'\"'),'" 2>&1'].join(' ')
|
367
|
+
cmd_text = redact ? redact.inject(cmd.inspect){|ct,r| ct.gsub(r,redact_replacement)} : cmd.inspect
|
368
|
+
logger.trace %Q{executing locally: #{cmd_text}} if logger
|
369
|
+
$stdout.sync = true
|
370
|
+
elapsed = Benchmark.realtime do
|
371
|
+
Open3.popen3(cmd) do |stdin, out, err, external|
|
372
|
+
# Create a thread to read from each stream
|
373
|
+
{ :out => out, :err => err }.each do |key, stream|
|
374
|
+
Thread.new do
|
375
|
+
until (line = stream.gets).nil? do
|
376
|
+
redact.each {|r| line.gsub!(r,redact_replacement)} if redact
|
377
|
+
$stdout << line
|
378
|
+
File.open(tee,'a') {|f| f.write(line) } if tee
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
# Don't exit until the external process is done
|
383
|
+
external.join
|
384
|
+
end
|
385
|
+
if $?.to_i > 0 # $? is command exit code (posix style)
|
386
|
+
raise Capistrano::LocalArgumentError, "Command #{cmd_text} returned status code #{$?}"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
$stdout.sync = false
|
390
|
+
logger.trace "\ncommand finished in #{(elapsed * 1000).round}ms" if logger
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
##
|
396
|
+
# Find the location of the capfile you can use this to identify a path relative to the capfile.
|
397
|
+
def capfile
|
398
|
+
previous = nil
|
399
|
+
current = File.expand_path(Dir.pwd)
|
400
|
+
|
401
|
+
until !File.directory?(current) || current == previous
|
402
|
+
filename = File.join(current, 'Capfile')
|
403
|
+
return filename if File.file?(filename)
|
404
|
+
current, previous = File.expand_path("..", current), current
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
##
|
409
|
+
# Does the actual capturing of the input and streaming of the output.
|
410
|
+
#
|
411
|
+
# local_run_method: run or sudo
|
412
|
+
# shell_command: The command to run
|
413
|
+
# input_query: A regular expression matching a request for input: /^Please enter your password/
|
414
|
+
def handle_command_with_input(local_run_method, shell_command, input_query, response=nil)
|
415
|
+
send(local_run_method, shell_command, {:pty => true}) do |channel, stream, data|
|
416
|
+
if data =~ input_query
|
417
|
+
if response
|
418
|
+
logger.info "#{data} #{"*"*(rand(10)+5)}", channel[:host]
|
419
|
+
channel.send_data "#{response}\n"
|
420
|
+
else
|
421
|
+
logger.info data, channel[:host]
|
422
|
+
response = ::Capistrano::CLI.password_prompt "#{data}"
|
423
|
+
channel.send_data "#{response}\n"
|
424
|
+
end
|
425
|
+
else
|
426
|
+
logger.info data, channel[:host]
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
##
|
432
|
+
# Use to raise warnings about deprecated items
|
433
|
+
# set(:var_no_longer_used) {utilities.deprecated(:var_no_longer_used,:var_that_should_be_used)}
|
434
|
+
def deprecated(name,replacement=nil)
|
435
|
+
raise Capistrano::Error, "#{name} is deprecated, #{replacement ? "see: #{replacment}" : "no replacement" }."
|
436
|
+
end
|
437
|
+
|
438
|
+
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
Capistrano.plugin :utilities, Utilities
|