sepastian-capistrano3-unicorn 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ !.gitignore
2
+ *.gem
3
+ *.rbc
4
+ *.sw[a-p]
5
+ *.tmproj
6
+ *.tmproject
7
+ *.un~
8
+ *~
9
+ .DS_Store
10
+ .Spotlight-V100
11
+ .Trashes
12
+ ._*
13
+ .bundle
14
+ .config
15
+ .directory
16
+ .elc
17
+ .redcar
18
+ .yardoc
19
+ /.emacs.desktop
20
+ /.emacs.desktop.lock
21
+ Desktop.ini
22
+ Gemfile.lock
23
+ Icon?
24
+ InstalledFiles
25
+ Session.vim
26
+ Thumbs.db
27
+ \#*\#
28
+ _yardoc
29
+ auto-save-list
30
+ coverage
31
+ doc/
32
+ lib/bundler/man
33
+ pkg
34
+ pkg/*
35
+ rdoc
36
+ spec/reports
37
+ test/tmp
38
+ test/version_tmp
39
+ tmp
40
+ tmtags
41
+ tramp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'capistrano-spec'
8
+ gem 'pry-debugger'
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2011-2013 Dan Sosedoff.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to
8
+ do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
15
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
16
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS.md ADDED
@@ -0,0 +1,27 @@
1
+ # Capistrano Unicorn NEWS
2
+
3
+ ## 0.2.0
4
+
5
+ Significant changes since 0.1.10 are as follows. Backwards-incompatible changes are **in bold**.
6
+
7
+ * Ensured `RAILS_ENV` is set correctly.
8
+ * Embedded multistage docs directly within the [README](README.md) (the wiki page was version-specific and misleading).
9
+ * Significantly tidied up the usage and documentation of configuration variables:
10
+ * **In most cases, it should now be sufficient to simply set `rails_env` correctly,
11
+ and other variables should assume the correct value by default.**
12
+ * **Make `unicorn_env` default to `rails_env` or `'production'`.**
13
+ * **Rename `app_env` to `unicorn_rack_env` and fix default value.**
14
+ * Add `unicorn_options` variable which allows passing of arbitrary options to unicorn.
15
+ * Added `app_subdir` to support app running in a subdirectory.
16
+ * Updated documentation in [README](README.md) to fix inaccuracies and ambiguities.
17
+ * `unicorn_pid` defaults to attempting to auto-detect from unicorn config file.
18
+ This avoids having to keep two paths in sync.
19
+ https://github.com/sosedoff/capistrano-unicorn/issues/7
20
+ * Also added the `unicorn:show_vars` task to make it easier to debug
21
+ config variable values client-side.
22
+ * Defer calculation of `unicorn-roles`.
23
+
24
+ It was noticed that there are a
25
+ [huge number of unmerged forks on github](https://github.com/sosedoff/capistrano-unicorn/issues/45),
26
+ so we also updated the [README](README.md) asking the community to
27
+ contribute back any useful changes they make.
data/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # Capistrano Unicorn
2
+
3
+ Capistrano plugin that integrates Unicorn tasks into capistrano deployment script.
4
+
5
+ **Developers:** Please consider contributing your forked changes, or opening an
6
+ issue if there is no existing relevant one. There are a lot of forks--we'd love
7
+ to reabsorb some of the issues/solutions the community has encountered.
8
+
9
+ [![Build Status](https://travis-ci.org/sosedoff/capistrano-unicorn.png?branch=master)](https://travis-ci.org/sosedoff/capistrano-unicorn)
10
+ [![Gem Version](https://badge.fury.io/rb/capistrano-unicorn.png)](http://badge.fury.io/rb/capistrano-unicorn)
11
+ [![Code Climate](https://codeclimate.com/github/sosedoff/capistrano-unicorn.png)](https://codeclimate.com/github/sosedoff/capistrano-unicorn)
12
+
13
+ ## Usage
14
+
15
+ If you are upgrading from a previous version, please see the [NEWS file](NEWS.md).
16
+
17
+ ### Setup
18
+
19
+ Add the library to your `Gemfile`:
20
+
21
+ ```ruby
22
+ group :development do
23
+ gem 'capistrano-unicorn', :require => false
24
+ end
25
+ ```
26
+
27
+ And load it into your deployment script `config/deploy.rb`:
28
+
29
+ ```ruby
30
+ require 'capistrano-unicorn'
31
+ ```
32
+
33
+ Add unicorn restart task hook:
34
+
35
+ ```ruby
36
+ after 'deploy:restart', 'unicorn:reload' # app IS NOT preloaded
37
+ after 'deploy:restart', 'unicorn:restart' # app preloaded
38
+ after 'deploy:restart', 'unicorn:duplicate' # before_fork hook implemented (zero downtime deployments)
39
+ ```
40
+
41
+ Create a new configuration file `config/unicorn.rb` or `config/unicorn/STAGE.rb`,
42
+ where stage is your deployment environment.
43
+
44
+ Example config - [examples/rails3.rb](https://github.com/sosedoff/capistrano-unicorn/blob/master/examples/rails3.rb).
45
+ Please refer to Unicorn documentation for more examples and configuration options.
46
+
47
+ ### Deploy
48
+
49
+ First, make sure you're running the latest release:
50
+
51
+ ```
52
+ cap deploy
53
+ ```
54
+
55
+ Then you can test each individual task:
56
+
57
+ ```
58
+ cap unicorn:start
59
+ cap unicorn:stop
60
+ cap unicorn:reload
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ You can modify any of the following Capistrano variables in your `deploy.rb` config.
66
+ You can use the `unicorn:show_vars` task to check that the values you have specified
67
+ are set correctly.
68
+
69
+ ### Environment parameters
70
+
71
+ - `unicorn_env` - Set basename of unicorn config `.rb` file to be used loaded from `unicorn_config_path`. Defaults to `rails_env` variable if set, otherwise `production`.
72
+ - `unicorn_rack_env` - Set the value which will be passed to unicorn via [the `-E` parameter as the Rack environment](http://unicorn.bogomips.org/unicorn_1.html). Valid values are `development`, `deployment`, and `none`. Defaults to `development` if `rails_env` is `development`, otherwise `deployment`.
73
+
74
+ ### Execution parameters
75
+
76
+ - `unicorn_user` - Launch unicorn master as the specified user via `sudo`. Defaults to `nil`, which means no use of `sudo`, i.e. run as the user defined by the `user` variable.
77
+ - `unicorn_roles` - Define which roles to perform unicorn recipes on. Defaults to `:app`.
78
+ - `unicorn_bundle` - Set bundler command for unicorn. Defaults to `bundle`.
79
+ - `unicorn_bin` - Set unicorn executable file. Defaults to `unicorn`.
80
+ - `unicorn_options` - Set any additional options to be passed to unicorn on startup.
81
+ - `unicorn_restart_sleep_time` - Number of seconds to wait for (old) pidfile to show up when restarting unicorn. Defaults to 2.
82
+
83
+ ### Relative path parameters
84
+
85
+ - `app_subdir` - If your app lives in a subdirectory 'rails' (say) of your repository, set this to `/rails` (the leading slash is required).
86
+ - `unicorn_config_rel_path` - Set the directory path (relative to `app_path` - see below) where unicorn config files reside. Defaults to `config`.
87
+ - `unicorn_config_filename` - Set the filename of the unicorn config file loaded from `unicorn_config_path`. Should not be present in multistage installations. Defaults to `unicorn.rb`.
88
+
89
+ ### Absolute path parameters
90
+
91
+ - `app_path` - Set path to app root. Defaults to `current_path + app_subdir`.
92
+ - `unicorn_pid` - Set unicorn PID file path. By default, attempts to auto-detect from unicorn config file. On failure, falls back to value in `unicorn_default_pid`
93
+ - `unicorn_default_pid` - See above. Defaults to `#{current_path}/tmp/pids/unicorn.pid`
94
+ - `bundle_gemfile` - Set path to Gemfile. Defaults to `#{app_path}/Gemfile`
95
+ - `unicorn_config_path` - Set the directory where unicorn config files reside. Defaults to `#{current_path}/config`.
96
+
97
+ ### Zero Downtime Deployment Options
98
+
99
+ * `unicorn:restart`: :-1: This can sort of support it with a configurable timeout, which may not be reliable.
100
+ * `unicorn:reload`: :question: Can anyone testify to its zero-downtime support?
101
+ * `unicorn:duplicate`: :+1: If you install the Unicorn `before_fork` hook, then yes! See: https://github.com/sosedoff/capistrano-unicorn/issues/40#issuecomment-16011353
102
+
103
+ ## Available Tasks
104
+
105
+ To get a list of all capistrano tasks, run `cap -T`:
106
+
107
+ ```
108
+ cap unicorn:add_worker # Add a new worker
109
+ cap unicorn:remove_worker # Remove amount of workers
110
+ cap unicorn:reload # Reload Unicorn
111
+ cap unicorn:restart # Restart Unicorn
112
+ cap unicorn:show_vars # Debug Unicorn variables
113
+ cap unicorn:shutdown # Immediately shutdown Unicorn
114
+ cap unicorn:start # Start Unicorn master process
115
+ cap unicorn:stop # Stop Unicorn
116
+ ```
117
+
118
+ ## Tests
119
+
120
+ To execute test suite run:
121
+
122
+ ```
123
+ bundle exec rake test
124
+ ```
125
+
126
+ ### Multistage
127
+
128
+ The issue here is that capistrano loads default configuration and then
129
+ executes your staging task and overrides previously defined
130
+ variables. The default environment before executing your stage task is
131
+ set to `:production`, so it will use a wrong environment unless you
132
+ take steps to ensure that `:rails_env` and `:unicorn_env` are
133
+ set correctly.
134
+
135
+ Let's say you have a scenario involving two deployment stages: staging
136
+ and production. You’ll need to add `config/deploy/staging.rb` and
137
+ `config/deploy/production.rb` files. However, it makes sense to
138
+ adhere to DRY and avoid duplicating lines between the two files. So
139
+ it would be nicer to keep common settings in `config/deploy.rb`, and
140
+ only put stuff in each staging definition file which is really
141
+ specific to that staging environment. Fortunately this can be done
142
+ using the [lazy evaluation form of `set`](https://github.com/capistrano/capistrano/wiki/2.x-DSL-Configuration-Variables-Set).
143
+
144
+ So `config/deploy.rb` file would contain something like:
145
+
146
+ ```ruby
147
+ set :stages, %w(production staging)
148
+ set :default_stage, "staging"
149
+ require 'capistrano/ext/multistage'
150
+
151
+ role(:web) { domain }
152
+ role(:app) { domain }
153
+ role(:db, :primary => true) { domain }
154
+
155
+ set(:deploy_to) { "/home/#{user}/#{application}/#{fetch :rails_env}" }
156
+ set(:current_path) { File.join(deploy_to, current_dir) }
157
+ ```
158
+
159
+ Then `config/deploy/production.rb` would contain something like:
160
+
161
+ ```ruby
162
+ set :domain, "app.mydomain.com"
163
+ set :rails_env, "production"
164
+ ```
165
+
166
+ and `config/deploy/staging.rb` would only need to contain something like:
167
+
168
+ ```ruby
169
+ set :domain, "app.staging.mydomain.com"
170
+ set :rails_env, "staging"
171
+ ```
172
+
173
+ Nice and clean!
174
+
175
+ ## License
176
+
177
+ See LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+
6
+ RSpec::Core::RakeTask.new(:test) do |spec|
7
+ spec.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/capistrano/unicorn/version", __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "sepastian-capistrano3-unicorn"
6
+ spec.version = CapistranoUnicorn::VERSION.dup
7
+ spec.author = "Sebastian Gassner, Dan Sosedoff"
8
+ spec.email = "sebastian.gassner@gmail.com"
9
+ spec.homepage = "https://github.com/sepastian/capistrano-unicorn"
10
+ spec.summary = %q{Unicorn integration for Capistrano 3.x}
11
+ spec.description = %q{Capistrano 3.x plugin that integrates Unicorn server tasks.}
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files`.split("\n")
15
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "rake"
20
+ spec.add_development_dependency "unicorn"
21
+ spec.add_runtime_dependency "capistrano", "~> 3.0.0"
22
+ end
@@ -0,0 +1,45 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Sample rails 3 config
3
+ # ------------------------------------------------------------------------------
4
+
5
+ # Set your full path to application.
6
+ app_path = "/path/to/app"
7
+
8
+ # Set unicorn options
9
+ worker_processes 1
10
+ preload_app true
11
+ timeout 180
12
+ listen "127.0.0.1:9000"
13
+
14
+ # Spawn unicorn master worker for user apps (group: apps)
15
+ user 'apps', 'apps'
16
+
17
+ # Fill path to your app
18
+ working_directory app_path
19
+
20
+ # Should be 'production' by default, otherwise use other env
21
+ rails_env = ENV['RAILS_ENV'] || 'production'
22
+
23
+ # Log everything to one file
24
+ stderr_path "log/unicorn.log"
25
+ stdout_path "log/unicorn.log"
26
+
27
+ # Set master PID location
28
+ pid "#{app_path}/tmp/pids/unicorn.pid"
29
+
30
+ before_fork do |server, worker|
31
+ ActiveRecord::Base.connection.disconnect!
32
+
33
+ old_pid = "#{server.config[:pid]}.oldbin"
34
+ if File.exists?(old_pid) && server.pid != old_pid
35
+ begin
36
+ Process.kill("QUIT", File.read(old_pid).to_i)
37
+ rescue Errno::ENOENT, Errno::ESRCH
38
+ # someone else did our job for us
39
+ end
40
+ end
41
+ end
42
+
43
+ after_fork do |server, worker|
44
+ ActiveRecord::Base.establish_connection
45
+ end
File without changes
@@ -0,0 +1,170 @@
1
+ require 'capistrano'
2
+ require 'capistrano/unicorn/utility'
3
+
4
+ include CapistranoUnicorn::Utility
5
+
6
+ # Load default values the capistrano 3.x way.
7
+ # See https://github.com/capistrano/capistrano/pull/605
8
+ namespace :load do
9
+ task :defaults do
10
+
11
+ # Environments
12
+ set :unicorn_env , Proc.new{ fetch(:rails_env, 'production') }
13
+ # Following recommendations from http://unicorn.bogomips.org/unicorn_1.html
14
+ set :unicorn_rack_env, Proc.new{ fetch(:rails_env) == 'development' ? 'development' : 'deployment' }
15
+
16
+ # Execution
17
+ set :unicorn_user , nil
18
+ set :unicorn_bundle , Proc.new{ fetch(:bundle_cmd, "bundle") }
19
+ set :unicorn_bin , "unicorn"
20
+ set :unicorn_options , ''
21
+ set :unicorn_restart_sleep_time, 2
22
+
23
+ # Relative paths
24
+ set :app_subdir , ''
25
+ set :unicorn_config_rel_path , "config"
26
+ set :unicorn_config_filename , "unicorn.rb"
27
+ set :unicorn_config_rel_file_path , Proc.new{ File.join(fetch(:unicorn_config_rel_path), fetch(:unicorn_config_filename)) }
28
+ set :unicorn_config_stage_rel_file_path, Proc.new{ File.join(fetch(:unicorn_config_rel_path), 'unicorn', "#{fetch(:unicorn_env)}.rb") }
29
+
30
+ # Absolute paths
31
+ # If you find the following confusing, try running 'cap unicorn:show_vars' -
32
+ # it might help :-)
33
+ set :app_path , Proc.new{ File.join(current_path, fetch(:app_subdir)) }
34
+ set :bundle_gemfile , Proc.new{ File.join(fetch(:app_path), 'Gemfile') }
35
+ set :unicorn_config_path , Proc.new{ File.join(fetch(:app_path), fetch(:unicorn_config_rel_path)) }
36
+ set :unicorn_config_file_path , Proc.new{ File.join(fetch(:app_path), fetch(:unicorn_config_rel_file_path)) }
37
+ set :unicorn_config_stage_file_path, Proc.new{ File.join(fetch(:app_path), fetch(:unicorn_config_stage_rel_file_path)) }
38
+ set :unicorn_default_pid , Proc.new{ File.join(fetch(:app_path), 'tmp', 'pids', 'unicorn.pid') }
39
+ set :unicorn_pid, Proc.new{
40
+ extracted_pid = extract_pid_file
41
+ if extracted_pid
42
+ extracted_pid
43
+ else
44
+ # TODO logger is not defined
45
+ #logger.important "err :: failed to auto-detect pid from #{local_unicorn_config}"
46
+ #logger.important "err :: falling back to default: #{unicorn_default_pid}"
47
+ fetch :unicorn_default_pid
48
+ end
49
+ }
50
+ end
51
+ end
52
+
53
+ namespace :unicorn do
54
+ desc 'Debug Unicorn variables'
55
+ task :show_vars do
56
+ on roles :app do
57
+ puts <<-EOF.gsub(/^ +/, '')
58
+ # Environments
59
+ rails_env #{fetch :rails_env}
60
+ unicorn_env #{fetch :unicorn_env}
61
+ unicorn_rack_env #{fetch :unicorn_rack_env}
62
+
63
+ # Execution
64
+ unicorn_user #{fetch(:unicorn_user).inspect}
65
+ unicorn_bundle #{fetch :unicorn_bundle}
66
+ unicorn_bin #{fetch :unicorn_bin}
67
+ unicorn_options #{fetch :unicorn_options}
68
+ unicorn_restart_sleep_time #{fetch :unicorn_restart_sleep_time}
69
+
70
+ # Relative paths
71
+ app_subdir #{fetch :app_subdir}
72
+ unicorn_config_rel_path #{fetch :unicorn_config_rel_path}
73
+ unicorn_config_filename #{fetch :unicorn_config_filename}
74
+ unicorn_config_rel_file_path #{fetch :unicorn_config_rel_file_path}
75
+ unicorn_config_stage_rel_file_path #{fetch :unicorn_config_stage_rel_file_path}
76
+
77
+ # Absolute paths
78
+ app_path #{fetch :app_path}
79
+ unicorn_pid #{fetch :unicorn_pid}
80
+ bundle_gemfile #{fetch :bundle_gemfile}
81
+ unicorn_config_path #{fetch :unicorn_config_path}
82
+ unicorn_config_file_path #{fetch :unicorn_config_file_path}
83
+ unicorn_config_stage_file_path
84
+ -> "#{fetch :unicorn_config_stage_file_path}
85
+ EOF
86
+ end
87
+ end
88
+
89
+ desc 'Start Unicorn master process'
90
+ task :start do
91
+ on roles unicorn_roles, :except => {:no_release => true} do
92
+ execute start_unicorn
93
+ end
94
+ end
95
+
96
+ desc 'Stop Unicorn'
97
+ task :stop do
98
+ on roles unicorn_roles, :except => {:no_release => true} do
99
+ execute kill_unicorn('QUIT')
100
+ end
101
+ end
102
+
103
+ desc 'Immediately shutdown Unicorn'
104
+ task :shutdown do
105
+ on roles unicorn_roles, :except => {:no_release => true} do
106
+ execute kill_unicorn('TERM')
107
+ end
108
+ end
109
+
110
+ desc 'Restart Unicorn'
111
+ task :restart do
112
+ on roles unicorn_roles, :except => {:no_release => true} do
113
+ execute duplicate_unicorn
114
+ execute :sleep, fetch(:unicorn_restart_sleep_time)
115
+ execute "if #{old_unicorn_is_running?}; then #{unicorn_send_signal('QUIT', get_old_unicorn_pid)}; fi;"
116
+ end
117
+ end
118
+
119
+ desc 'Duplicate Unicorn'
120
+ task :duplicate do
121
+ on roles unicorn_roles, :except => {:no_release => true} do
122
+ execute duplicate_unicorn()
123
+ end
124
+ end
125
+
126
+ desc 'Reload Unicorn'
127
+ task :reload do
128
+ on roles => unicorn_roles, :except => {:no_release => true} do
129
+ execute "if #{unicorn_is_running?}; then #{unicorn_send_signal('HUP')}; else #{start_unicorn}; fi;"
130
+ # run <<-END
131
+ # if #{unicorn_is_running?}; then
132
+ # echo "Reloading Unicorn...";
133
+ # #{unicorn_send_signal('HUP')};
134
+ # else
135
+ # #{start_unicorn}
136
+ # fi;
137
+ # END
138
+ end
139
+ end
140
+
141
+ desc 'Add a new worker'
142
+ task :add_worker do
143
+ on roles => unicorn_roles, :except => {:no_release => true} do
144
+ execute "if #{unicorn_is_running?}; then #{unicorn_send_signal('TTIN')}; fi;"
145
+ # run <<-END
146
+ # if #{unicorn_is_running?}; then
147
+ # echo "Adding a new Unicorn worker...";
148
+ # #{unicorn_send_signal('TTIN')};
149
+ # else
150
+ # echo "Unicorn is not running.";
151
+ # fi;
152
+ # END
153
+ end
154
+ end
155
+
156
+ desc 'Remove amount of workers'
157
+ task :remove_worker do
158
+ on roles => unicorn_roles, :except => {:no_release => true} do
159
+ execute "if #{unicorn_is_running?}; then #{unicorn_send_signal('TTOU')}; fi;"
160
+ # run <<-END
161
+ # if #{unicorn_is_running?}; then
162
+ # echo "Removing a Unicorn worker...";
163
+ # #{unicorn_send_signal('TTOU')};
164
+ # else
165
+ # echo "Unicorn is not running.";
166
+ # fi;
167
+ # END
168
+ end
169
+ end
170
+ end
@@ -0,0 +1 @@
1
+ load File.expand_path(File.join('..', 'tasks', 'unicorn.cap'), __FILE__)
@@ -0,0 +1,166 @@
1
+ require 'tempfile'
2
+
3
+ module CapistranoUnicorn
4
+ module Utility
5
+ # In Capistrano 3, shell scripts must be invoked with SSHKit's execute, instead of run.
6
+ # SSHKit will "sanitize" all multi-line commands (here docs), replacing "\n" with ";".
7
+ # Sanitizing renders some shell scripts illegal, for instance:
8
+ #
9
+ # if [ -e FILE ]; then
10
+ # echo "Found."
11
+ # fi
12
+ #
13
+ # This would become
14
+ #
15
+ # if [ -e FILE ]; then; echo "Found."; fi;
16
+ #
17
+ # which is illegal because of the ';' after 'then'.
18
+ #
19
+ # To avoid errors, replace all "\n" with " " in shell scripts,
20
+ # before SSHKit gets a chance to replace "\n" with ";"
21
+ def local_unicorn_config
22
+ if File.exist? fetch(:unicorn_config_rel_file_path)
23
+ fetch(:unicorn_config_rel_file_path)
24
+ else
25
+ fetch(:unicorn_config_stage_rel_file_path)
26
+ end
27
+ end
28
+
29
+ def extract_pid_file
30
+ tmp = Tempfile.new('unicorn.rb')
31
+ begin
32
+ conf = local_unicorn_config
33
+ tmp.write <<-EOF.gsub(/^ */, '')
34
+ config_file = "#{conf}"
35
+
36
+ # stub working_directory to avoid chdir failure since this will
37
+ # run client-side:
38
+ def working_directory(path); end
39
+
40
+ instance_eval(File.read(config_file), config_file) if config_file
41
+ puts set[:pid]
42
+ exit 0
43
+ EOF
44
+ tmp.close
45
+ extracted_pid = `unicorn -c "#{tmp.path}"`
46
+ $?.success? ? extracted_pid.rstrip : nil
47
+ rescue StandardError => e
48
+ return nil
49
+ ensure
50
+ tmp.close
51
+ tmp.unlink
52
+ end
53
+ end
54
+
55
+ # Check if a remote process exists using its pid file
56
+ #
57
+ def remote_process_exists?(pid_file)
58
+ "[ -e #{pid_file} ] && #{try_unicorn_user} kill -0 `cat #{pid_file}` > /dev/null 2>&1"
59
+ end
60
+
61
+ # Stale Unicorn process pid file
62
+ #
63
+ def old_unicorn_pid
64
+ "#{fetch :unicorn_pid}.oldbin"
65
+ end
66
+
67
+ # Command to check if Unicorn is running
68
+ #
69
+ def unicorn_is_running?
70
+ remote_process_exists?(fetch :unicorn_pid)
71
+ end
72
+
73
+ # Command to check if stale Unicorn is running
74
+ #
75
+ def old_unicorn_is_running?
76
+ remote_process_exists?(old_unicorn_pid)
77
+ end
78
+
79
+ # Get unicorn master process PID (using the shell)
80
+ #
81
+ def get_unicorn_pid(pid_file=fetch(:unicorn_pid))
82
+ "`cat #{pid_file}`"
83
+ end
84
+
85
+ # Get unicorn master (old) process PID
86
+ #
87
+ def get_old_unicorn_pid
88
+ get_unicorn_pid(old_unicorn_pid)
89
+ end
90
+
91
+ # Send a signal to a unicorn master processes
92
+ #
93
+ def unicorn_send_signal(signal, pid=get_unicorn_pid)
94
+ "#{try_unicorn_user} kill -s #{signal} #{pid}"
95
+ end
96
+
97
+ # Run a command as the :unicorn_user user if :unicorn_user is a string.
98
+ # Otherwise run as default (:user) user.
99
+ #
100
+ def try_unicorn_user
101
+ "#{sudo :as => unicorn_user.to_s}" if fetch(:unicorn_user).kind_of?(String)
102
+ end
103
+
104
+ # Kill Unicorns in multiple ways O_O
105
+ #
106
+ def kill_unicorn(signal)
107
+ script = <<-END
108
+ if #{unicorn_is_running?}; then
109
+ echo "Stopping Unicorn...";
110
+ #{unicorn_send_signal(signal)};
111
+ else
112
+ echo "Unicorn is not running.";
113
+ fi;
114
+ END
115
+ script.split.join(' ')
116
+ end
117
+
118
+ # Start the Unicorn server
119
+ #
120
+ def start_unicorn
121
+ %Q%
122
+ if [ -e "#{fetch :unicorn_config_file_path}" ]; then
123
+ UNICORN_CONFIG_PATH=#{fetch :unicorn_config_file_path};
124
+ else
125
+ if [ -e "#{fetch :unicorn_config_stage_file_path}" ]; then
126
+ UNICORN_CONFIG_PATH=#{fetch :unicorn_config_stage_file_path};
127
+ else
128
+ echo "Config file for "#{fetch :unicorn_env}" environment was not found at either "#{fetch :unicorn_config_file_path}" or "#{fetch :unicorn_config_stage_file_path}"";
129
+ exit 1;
130
+ fi;
131
+ fi;
132
+
133
+ if [ -e "#{fetch :unicorn_pid}" ]; then
134
+ if #{try_unicorn_user} kill -0 `cat #{fetch :unicorn_pid}` > /dev/null 2>&1; then
135
+ echo "Unicorn is already running!";
136
+ exit 0;
137
+ fi;
138
+
139
+ #{try_unicorn_user} rm #{fetch :unicorn_pid};
140
+ fi;
141
+
142
+ echo "Starting Unicorn...";
143
+ cd #{fetch :app_path} && #{try_unicorn_user} RAILS_ENV=#{fetch :rails_env} BUNDLE_GEMFILE=#{fetch :bundle_gemfile} #{fetch :unicorn_bundle} exec #{fetch :unicorn_bin} -c $UNICORN_CONFIG_PATH -E #{fetch :unicorn_rack_env} -D #{fetch :unicorn_options};
144
+ %.split.join(' ')
145
+ end
146
+
147
+ def duplicate_unicorn
148
+ script = <<-END
149
+ if #{unicorn_is_running?}; then
150
+ echo "Duplicating Unicorn...";
151
+ #{unicorn_send_signal('USR2')};
152
+ else
153
+ #{start_unicorn}
154
+ fi;
155
+ END
156
+ script.split.join(' ')
157
+ end
158
+
159
+ def unicorn_roles
160
+ # TODO proc necessary here?
161
+ Proc.new{ fetch(:unicorn_roles, :app) }.call
162
+ #defer{ fetch(:unicorn_roles, :app) }
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,5 @@
1
+ module CapistranoUnicorn
2
+ unless defined?(::CapistranoUnicorn::VERSION)
3
+ VERSION = "0.3.0".freeze
4
+ end
5
+ end
data/lib/unicorn.rb ADDED
File without changes
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ describe CapistranoUnicorn::CapistranoIntegration, "loaded tasks into capistrano" do
4
+ before do
5
+ @configuration = Capistrano::Configuration.new
6
+ @configuration.extend(Capistrano::Spec::ConfigurationExtension)
7
+ CapistranoUnicorn::CapistranoIntegration.load_into(@configuration)
8
+ end
9
+
10
+ shared_examples_for "a task" do |task_name|
11
+ it "sets attributes in before_task hook" do
12
+ # Environments
13
+ @configuration.should_receive(:_cset).with(:unicorn_env)
14
+ @configuration.should_receive(:_cset).with(:unicorn_rack_env)
15
+
16
+ # Execution
17
+ @configuration.should_receive(:_cset).with(:unicorn_user)
18
+ @configuration.should_receive(:_cset).with(:unicorn_bundle)
19
+ @configuration.should_receive(:_cset).with(:unicorn_bin)
20
+ @configuration.should_receive(:_cset).with(:unicorn_options)
21
+ @configuration.should_receive(:_cset).with(:unicorn_restart_sleep_time)
22
+
23
+ # Relative paths
24
+ @configuration.should_receive(:_cset).with(:app_subdir)
25
+ @configuration.should_receive(:_cset).with(:unicorn_config_rel_path)
26
+ @configuration.should_receive(:_cset).with(:unicorn_config_filename)
27
+ @configuration.should_receive(:_cset).with(:unicorn_config_rel_file_path)
28
+ @configuration.should_receive(:_cset).with(:unicorn_config_stage_rel_file_path)
29
+
30
+ # Absolute paths
31
+ @configuration.should_receive(:_cset).with(:app_path)
32
+ @configuration.should_receive(:_cset).with(:unicorn_pid)
33
+ @configuration.should_receive(:_cset).with(:bundle_gemfile)
34
+ @configuration.should_receive(:_cset).with(:unicorn_config_path)
35
+ @configuration.should_receive(:_cset).with(:unicorn_config_file_path)
36
+ @configuration.should_receive(:_cset).with(:unicorn_config_stage_file_path)
37
+
38
+ @configuration.find_and_execute_task(task_name)
39
+ end
40
+ end
41
+
42
+ describe "task" do
43
+ describe 'unicorn:start' do
44
+ before do
45
+ @configuration.stub(:start_unicorn)
46
+ @configuration.stub(:_cset)
47
+ end
48
+
49
+ it_behaves_like "a task", 'unicorn:start'
50
+
51
+ it "runs start_unicorn command" do
52
+ @configuration.should_receive(:start_unicorn).and_return("start unicorn command")
53
+ @configuration.find_and_execute_task('unicorn:start')
54
+ @configuration.should have_run("start unicorn command")
55
+ end
56
+ end
57
+
58
+ describe 'unicorn:stop' do
59
+ before do
60
+ @configuration.stub(:kill_unicorn)
61
+ @configuration.stub(:_cset)
62
+ end
63
+
64
+ it_behaves_like "a task", 'unicorn:stop'
65
+
66
+ it "runs kill_unicorn command" do
67
+ @configuration.should_receive(:kill_unicorn).with('QUIT').and_return("kill unicorn command")
68
+ @configuration.find_and_execute_task('unicorn:stop')
69
+ @configuration.should have_run("kill unicorn command")
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "#kill_unicorn" do
75
+ before do
76
+ @configuration.stub(:unicorn_pid).and_return(999)
77
+ @configuration.stub(:unicorn_user).and_return("deploy_user")
78
+ end
79
+
80
+ it "generates the kill unicorn command" do
81
+ @configuration.kill_unicorn('QUIT').should match /-u deploy_user kill -s QUIT `cat 999`;/
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+ describe CapistranoUnicorn::Config, "loaded into a configuration" do
3
+ before do
4
+ @configuration = Capistrano::Configuration.new
5
+ @configuration.extend(Capistrano::Spec::ConfigurationExtension)
6
+ CapistranoUnicorn::CapistranoIntegration.load_into(@configuration)
7
+ end
8
+
9
+ context "testing variables" do
10
+ before do
11
+ # define _cset etc. from capistrano
12
+ @configuration.load 'deploy'
13
+
14
+ # capistrano-unicorn variables are set during a 'before'
15
+ # callback, so in order to be able to test the result, we need
16
+ # to ensure the callback is triggered.
17
+ @configuration.trigger :before
18
+ end
19
+
20
+ describe "app paths" do
21
+ cur_path = '/path/to/myapp'
22
+
23
+ before do
24
+ @configuration.set(:current_path, cur_path)
25
+ end
26
+
27
+ shared_examples_for "an app in path" do |app_path|
28
+ let(:shell) { :` } # ` } work around confused emacs ruby-mode
29
+
30
+ specify "app_path should default to #{app_path}" do
31
+ @configuration.fetch(:app_path).should == app_path
32
+ end
33
+
34
+ it "should default to a sensible pid file when auto-detection failed" do
35
+ @configuration.should_receive(shell).with(/unicorn -c /).and_return('') do |cmd|
36
+ `false` # Simulate failure by setting $?
37
+ end
38
+ @configuration.logger.stub(:important)
39
+ @configuration.fetch(:unicorn_pid).should == app_path + "/tmp/pids/unicorn.pid"
40
+ end
41
+
42
+ shared_examples "auto-detect pid file from unicorn config" do
43
+ |pid_file, primary_exists, config_file|
44
+ which_config = primary_exists ? 'primary' : 'stage'
45
+ it "should auto-detect pid file from #{which_config} unicorn config" do
46
+ # Tempfile.new in Ruby 1.9.2 will call File.exist?
47
+ allow(File).to receive(:exist?).with(/tmp/)
48
+
49
+ File.should_receive(:exist?).with('config/unicorn.rb').and_return(primary_exists)
50
+ tmpfile = nil
51
+ @configuration.should_receive(shell).with(/unicorn -c /) do |cmd|
52
+ (cmd =~ /^unicorn -c "(.+)"$/).should be_true
53
+ tmpfile = $~[1]
54
+ tmpfile.should include("tmp")
55
+ File.read(tmpfile).should include(%!config_file = "#{config_file}"!)
56
+ `true` # Simulate success by setting $?
57
+ pid_file
58
+ end
59
+ @configuration.fetch(:unicorn_pid).should == pid_file
60
+ end
61
+ end
62
+
63
+ include_examples "auto-detect pid file from unicorn config", \
64
+ '/path/to/pid/from/config/file', true, "config/unicorn.rb"
65
+
66
+ include_examples "auto-detect pid file from unicorn config", \
67
+ '/path/to/pid/from/stage/config/file', false, "config/unicorn/production.rb"
68
+
69
+ specify "Gemfile should default correctly" do
70
+ @configuration.fetch(:bundle_gemfile).should == app_path + "/Gemfile"
71
+ end
72
+
73
+ specify "config/ directory should default correctly" do
74
+ @configuration.fetch(:unicorn_config_path).should == app_path + "/config"
75
+ end
76
+
77
+ specify "config file should default correctly" do
78
+ @configuration.fetch(:unicorn_config_file_path).should == app_path + "/config/unicorn.rb"
79
+ end
80
+
81
+ specify "per-stage config file should default correctly" do
82
+ @configuration.fetch(:unicorn_config_stage_file_path).should == app_path + "/config/unicorn/production.rb"
83
+ end
84
+
85
+ specify "per-stage config file should be set correctly for different environment" do
86
+ @configuration.set(:rails_env, 'staging')
87
+ @configuration.fetch(:unicorn_config_stage_file_path).should == app_path + "/config/unicorn/staging.rb"
88
+ end
89
+ end
90
+
91
+ context "app in current_path" do
92
+ it_should_behave_like "an app in path", cur_path
93
+ end
94
+
95
+ context "app in a subdirectory" do
96
+ subdir = 'mysubdir'
97
+
98
+ before do
99
+ @configuration.set(:app_subdir, '/' + subdir)
100
+ end
101
+
102
+ it_should_behave_like "an app in path", cur_path + '/' + subdir
103
+ end
104
+ end
105
+
106
+ describe "unicorn_env" do
107
+ it "should default to value of rails_env if set" do
108
+ @configuration.set(:rails_env, 'staging')
109
+ @configuration.fetch(:unicorn_env).should == \
110
+ @configuration.fetch(:rails_env)
111
+ end
112
+
113
+ it "should default to production if rails_env not set" do
114
+ @configuration.fetch(:unicorn_env).should == 'production'
115
+ end
116
+ end
117
+
118
+ describe "unicorn_rack_env" do
119
+ it "should default to deployment if rails_env not set" do
120
+ @configuration.fetch(:unicorn_rack_env).should == 'deployment'
121
+ end
122
+
123
+ it "should default to development if rails_env set to development" do
124
+ @configuration.set(:rails_env, 'development')
125
+ @configuration.fetch(:unicorn_rack_env).should == 'development'
126
+ end
127
+
128
+ it "should default to deployment if rails_env set to anything else" do
129
+ @configuration.set(:rails_env, 'staging')
130
+ @configuration.fetch(:unicorn_rack_env).should == 'deployment'
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,7 @@
1
+ require 'capistrano-spec'
2
+ require 'capistrano-unicorn'
3
+
4
+ RSpec.configure do |config|
5
+ config.include Capistrano::Spec::Matchers
6
+ config.include Capistrano::Spec::Helpers
7
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sepastian-capistrano3-unicorn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sebastian Gassner, Dan Sosedoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: unicorn
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: capistrano
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 3.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
62
+ description: Capistrano 3.x plugin that integrates Unicorn server tasks.
63
+ email: sebastian.gassner@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - .gitignore
69
+ - .rspec
70
+ - .travis.yml
71
+ - Gemfile
72
+ - LICENSE
73
+ - NEWS.md
74
+ - README.md
75
+ - Rakefile
76
+ - capistrano-unicorn.gemspec
77
+ - examples/rails3.rb
78
+ - lib/capistrano-unicorn.rb
79
+ - lib/capistrano/tasks/unicorn.cap
80
+ - lib/capistrano/unicorn.rb
81
+ - lib/capistrano/unicorn/utility.rb
82
+ - lib/capistrano/unicorn/version.rb
83
+ - lib/unicorn.rb
84
+ - spec/capistrano_integration_spec.rb
85
+ - spec/config_spec.rb
86
+ - spec/spec_helper.rb
87
+ homepage: https://github.com/sepastian/capistrano-unicorn
88
+ licenses:
89
+ - MIT
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.23
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Unicorn integration for Capistrano 3.x
112
+ test_files: []
113
+ has_rdoc: