capistrano-kitchen 0.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset.template +1 -0
  5. data/.ruby-version.template +1 -0
  6. data/.travis.yml +7 -0
  7. data/.yardopts +5 -0
  8. data/Gemfile +8 -0
  9. data/Guardfile +13 -0
  10. data/LICENSE.txt +46 -0
  11. data/Rakefile +14 -0
  12. data/capistrano-kitchen.gemspec +29 -0
  13. data/lib/capistrano-kitchen.rb +41 -0
  14. data/lib/capistrano_kitchen/dishes/aptitude/manage.rb +38 -0
  15. data/lib/capistrano_kitchen/dishes/bundler/hooks.rb +7 -0
  16. data/lib/capistrano_kitchen/dishes/bundler/install.rb +79 -0
  17. data/lib/capistrano_kitchen/dishes/git/hooks.rb +3 -0
  18. data/lib/capistrano_kitchen/dishes/git/install.rb +18 -0
  19. data/lib/capistrano_kitchen/dishes/java_7_oracle/hooks.rb +5 -0
  20. data/lib/capistrano_kitchen/dishes/java_7_oracle/install.rb +17 -0
  21. data/lib/capistrano_kitchen/dishes/nginx_unicorn/app.conf +66 -0
  22. data/lib/capistrano_kitchen/dishes/nginx_unicorn/hooks.rb +11 -0
  23. data/lib/capistrano_kitchen/dishes/nginx_unicorn/install.rb +176 -0
  24. data/lib/capistrano_kitchen/dishes/nginx_unicorn/manage.rb +1 -0
  25. data/lib/capistrano_kitchen/dishes/nginx_unicorn/mime.types.erb +79 -0
  26. data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx.conf +138 -0
  27. data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.god +47 -0
  28. data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.init +95 -0
  29. data/lib/capistrano_kitchen/dishes/nginx_unicorn/nginx_unicorn.logrotate +18 -0
  30. data/lib/capistrano_kitchen/dishes/nginx_unicorn/stub_status.conf +16 -0
  31. data/lib/capistrano_kitchen/dishes/nodejs/hooks.rb +4 -0
  32. data/lib/capistrano_kitchen/dishes/nodejs/install.rb +13 -0
  33. data/lib/capistrano_kitchen/dishes/provision/empty_roles.rb +60 -0
  34. data/lib/capistrano_kitchen/dishes/provision/manage.rb +49 -0
  35. data/lib/capistrano_kitchen/dishes/provision/task_once.rb +62 -0
  36. data/lib/capistrano_kitchen/dishes/ruby/hooks.rb +7 -0
  37. data/lib/capistrano_kitchen/dishes/ruby/install.rb +55 -0
  38. data/lib/capistrano_kitchen/dishes/teelogger/teelogger.rb +121 -0
  39. data/lib/capistrano_kitchen/dishes/unicorn/hooks.rb +9 -0
  40. data/lib/capistrano_kitchen/dishes/unicorn/install.rb +120 -0
  41. data/lib/capistrano_kitchen/dishes/unicorn/unicorn.god +71 -0
  42. data/lib/capistrano_kitchen/dishes/unicorn/unicorn.rb.erb +191 -0
  43. data/lib/capistrano_kitchen/recipes/aptitude.rb +1 -0
  44. data/lib/capistrano_kitchen/recipes/bundler.rb +1 -0
  45. data/lib/capistrano_kitchen/recipes/git.rb +1 -0
  46. data/lib/capistrano_kitchen/recipes/java_7_oracle.rb +1 -0
  47. data/lib/capistrano_kitchen/recipes/nginx_unicorn.rb +1 -0
  48. data/lib/capistrano_kitchen/recipes/nodejs.rb +1 -0
  49. data/lib/capistrano_kitchen/recipes/provision.rb +1 -0
  50. data/lib/capistrano_kitchen/recipes/ruby.rb +1 -0
  51. data/lib/capistrano_kitchen/recipes/teelogger.rb +1 -0
  52. data/lib/capistrano_kitchen/recipes/unicorn.rb +1 -0
  53. data/lib/capistrano_kitchen/recipes/utilities.rb +442 -0
  54. data/lib/capistrano_kitchen/version.rb +3 -0
  55. data/spec/capistrano_kitchen_spec.rb +5 -0
  56. data/spec/spec_helper.rb +21 -0
  57. 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