unicorn-ctl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +67 -0
  4. data/bin/unicornctl +480 -0
  5. metadata +83 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## UnicornCtl Changelog
2
+
3
+ ### 0.1.0 / 2013-09-11
4
+
5
+ * Initial open-source release of unicornctl.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Oleksiy Kovyrin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ ## unicornctl - unicorn/rainbows control script
2
+
3
+ `unicornctl` is a simple and easy to use console tool for managing ruby applications using unicorn or
4
+ rainbows application server. The tool provides a set of reliable commands to start/stop/restart
5
+ unicorn instances or upgrade them without or with minimal downtime. `unicornctl` script could easily
6
+ be used as a base for a startup script for unix operating systems (see examples directory for a
7
+ Redhat-style startup script example).
8
+
9
+ Please note, that this is still an alpha-quality software and it could have many issues. If you try it and
10
+ find any problems, feel free to report them using [Github Issues page](https://github.com/swiftype/unicorn-ctl/issues).
11
+ Pull requests are welcome too!
12
+
13
+ ### Installation
14
+
15
+ The script comes packaged as a rubygem, so installation procedure is as simple as running the
16
+ following command:
17
+
18
+ sudo gem install unicorn-ctl
19
+
20
+ After installation you should have an access to the `unicornctl` console command.
21
+
22
+ ### Usage Instructions
23
+
24
+ Here is the help output from the `unicornctl` command:
25
+
26
+ ```
27
+ Usage: unicornctl [options] <command>
28
+ Valid commands: start, stop, force-stop, restart, force-restart, upgrade
29
+ Options:
30
+ --app-dir=dir | -d dir Base directory for the application (required)
31
+ --environment=name | -e name RACK_ENV to use for the app (default: development)
32
+ --health-check-url=url | -H url Health check URL used to make sure the app has started
33
+ --health-check-content=string | -C string Health check expected content (default: just check for HTTP 200 OK)
34
+ --health-check-timeout=sec | -T sec Individual health check timeout (default: 5 sec)
35
+ --timeout=sec | -t sec Operation (start/stop/etc) timeout (default: 30 sec)
36
+ --unicorn-config=file | -c file Unicorn config file to use, absolute or relative path (default: shared/unicorn.rb)
37
+ --rackup-config=file | -r file Rackup config file to use, absolute or relative path (default: current/config.ru)
38
+ --pid-file=file | -p file PID-file unicorn is configured to use (default: shared/pids/unicorn.pid)
39
+ --rainbows | -R Use rainbows to start the app (default: use unicorn)
40
+ --help | -h This help
41
+ ```
42
+
43
+ The following command are supported at the moment:
44
+
45
+ * `start` - starts a unicorn application and performs a health check if health check URL is available.
46
+ If the server is already running, only the health check would be performed.
47
+ * `stop` - gracefully stops a unicorn application by sending a `QUIT` signal to the master process
48
+ and waiting for a specified amount of time (30 sec by default) for the process and all of its
49
+ children to shut down properly. If the process fails to stop in a given time, it is killed
50
+ with a `KILL` signal.
51
+ * `force-stop` - forcefully shuts down a unicorn application by sending a `TERM` signal to it and
52
+ waiting for the process to shut down quickly. If the process fails to stop in a given time, it is
53
+ killed with a `KILL` signal.
54
+ * `restart` - gracefully shuts a unicorn application down and then starts it back up (performing a
55
+ helth check if possible).
56
+ * `force-restart` - forcefully shuts a unicorn application down and then starts it back up
57
+ (performing a helth check if possible).
58
+ * `upgrade` - zero or minimal downtime restart option for a unicorn application. Performs a set of
59
+ steps to start a new copy of the application, test it and then gracefully shut down the old copy.
60
+ If the graceful restart fails for any reason, the application is forcefully restarted.
61
+
62
+ For more information on unicorn procss management you could check their
63
+ [official documentation page](http://unicorn.bogomips.org/SIGNALS.html).
64
+
65
+ ### License
66
+
67
+ The code is distributed under the MIT license. For more details, see the LICENSE.txt file.
data/bin/unicornctl ADDED
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'fileutils'
5
+ require 'shellwords'
6
+ require 'getoptlong'
7
+ require 'httparty'
8
+
9
+ VALID_COMMANDS = %w[
10
+ start
11
+ stop
12
+ force-stop
13
+ restart
14
+ force-restart
15
+ upgrade
16
+ ]
17
+
18
+ #---------------------------------------------------------------------------------------------------
19
+ # Make sure our console output is synchronous
20
+ STDOUT.sync = true
21
+ STDERR.sync = true
22
+
23
+ # Trap interrupts to quit cleanly. See
24
+ # https://twitter.com/mitchellh/status/283014103189053442
25
+ Signal.trap("INT") { exit 1 }
26
+
27
+ #---------------------------------------------------------------------------------------------------
28
+ def app_file_path(app_dir, config_name)
29
+ return config_name if config_name =~ /^\//
30
+ return File.join(app_dir, config_name)
31
+ end
32
+
33
+ def unicorn_pid_file(options)
34
+ if options[:pid_file]
35
+ app_file_path(options[:app_dir], options[:pid_file])
36
+ else
37
+ File.join(options[:app_dir], 'shared', 'pids', "#{options[:unicorn_bin]}.pid")
38
+ end
39
+ end
40
+
41
+ def unicorn_config_file(options)
42
+ if options[:unicorn_config]
43
+ app_file_path(options[:app_dir], options[:unicorn_config])
44
+ else
45
+ File.join(options[:app_dir], 'shared', "#{options[:unicorn_bin]}.rb")
46
+ end
47
+ end
48
+
49
+ def rackup_config_file(options)
50
+ if options[:rackup_config]
51
+ app_file_path(options[:app_dir], options[:rackup_config])
52
+ else
53
+ File.join(options[:app_dir], 'current', 'config.ru')
54
+ end
55
+ end
56
+
57
+ def escape(param)
58
+ Shellwords.escape(param)
59
+ end
60
+
61
+ #-------------------------------------------------------------------------------------------
62
+ # Check if process is still running
63
+ def pid_running?(pid)
64
+ begin
65
+ Process.kill(0, pid)
66
+ return true
67
+ rescue Errno::ESRCH
68
+ return false
69
+ rescue ::Exception # for example on EPERM (process exists but does not belong to us)
70
+ return true
71
+ end
72
+ end
73
+
74
+ def send_signal(signal, pid)
75
+ Process.kill(signal, pid)
76
+ rescue => e
77
+ puts "WARNING: Failed to send signal #{signal} to #{pid}: #{e}"
78
+ end
79
+
80
+ def read_pid(pid_file)
81
+ File.read(pid_file).strip.to_i
82
+ end
83
+
84
+ def wait_for_pid_to_die(pid, timeout)
85
+ print "Waiting for the process to stop: "
86
+ start_time = Time.now
87
+ while Time.now - start_time < timeout
88
+ print "."
89
+ break unless pid_running?(pid)
90
+ sleep(1)
91
+ end
92
+ end
93
+
94
+ def stop_unicorn_process(pid, timeout, graceful)
95
+ signal = graceful ? 'QUIT' : 'TERM'
96
+ puts "Sending #{signal} signal to process with pid=#{pid}..."
97
+ send_signal(signal, pid)
98
+
99
+ wait_for_pid_to_die(pid, timeout)
100
+
101
+ if pid_running?(pid)
102
+ puts " Failed to stop, killing!"
103
+ kill_tree(pid)
104
+ else
105
+ puts " Done!"
106
+ end
107
+ end
108
+
109
+ #---------------------------------------------------------------------------------------------------
110
+ # Kills a process and all of its descendants
111
+ def kill_tree(pid)
112
+ # FIXME: Implrement a real killtree
113
+ send_signal(9, pid)
114
+ end
115
+
116
+ #---------------------------------------------------------------------------------------------------
117
+ # Performs a health check on an http endpoint
118
+ def check_app_health(options)
119
+ puts "Checking service health with URL: #{options[:check_url]}"
120
+
121
+ start_time = Time.now
122
+ while Time.now - start_time < options[:timeout]
123
+ sleep(1)
124
+
125
+ response = begin
126
+ HTTParty.get(options[:check_url], :timeout => options[:check_timeout])
127
+ rescue Timeout::Error => e
128
+ puts "Health check timed out after #{options[:check_timeout]} seconds. Retrying..."
129
+ next
130
+ end
131
+
132
+ if response.code.to_i / 100 == 2
133
+ puts "Health check succeeded with code: #{response.code}"
134
+ if options[:check_content]
135
+ if response.body.match(options[:check_content])
136
+ puts "Content check succeeded, found content in response body: #{options[:check_content]}"
137
+ return true
138
+ else
139
+ puts "ERROR: Could not find content in response body: #{options[:check_content]}. Retrying."
140
+ next
141
+ end
142
+ end
143
+ return true
144
+ end
145
+
146
+ puts "Health check failed with status #{response.code}. Retrying..."
147
+ end
148
+
149
+ puts "ERROR: Health check has been failing for #{Time.now - start_time} seconds, giving up now!"
150
+ return false
151
+ end
152
+
153
+ #---------------------------------------------------------------------------------------------------
154
+ def start_application!(options)
155
+ # Check pid file
156
+ pid_file = unicorn_pid_file(options)
157
+ if File.exists?(pid_file)
158
+ pid = read_pid(pid_file)
159
+
160
+ if pid_running?(pid)
161
+ puts "OK: The app is already running"
162
+
163
+ # If we have a health check url, let's check it
164
+ if options[:check_url]
165
+ exit(1) unless check_app_health(options)
166
+ end
167
+
168
+ # Done
169
+ exit(0)
170
+ else
171
+ puts "WARNING: Slate pid file found, removing it: #{pid_file}"
172
+ FileUtils.rm(pid_file)
173
+ end
174
+ end
175
+
176
+ # Get unicorn config
177
+ unicorn_config = unicorn_config_file(options)
178
+ unless File.readable?(unicorn_config)
179
+ puts "ERROR: Could not find unicorn config: #{unicorn_config}"
180
+ exit(1)
181
+ end
182
+
183
+ # Get rackup config
184
+ rackup_config = rackup_config_file(options)
185
+ unless File.readable?(rackup_config)
186
+ puts "ERROR: Could not find rackup config: #{rackup_config}"
187
+ exit(1)
188
+ end
189
+
190
+ # Compose unicorn startup command
191
+ command = "cd #{escape options[:app_dir]}/current && bundle exec #{options[:unicorn_bin]} " <<
192
+ "--env #{escape options[:env]} --daemonize --config-file #{escape unicorn_config} "
193
+ "#{escape rackup_config}"
194
+
195
+ # Run startup command
196
+ puts "Starting unicorn..."
197
+ res = system(command)
198
+ unless res
199
+ puts "ERROR: Failed to start unicorn command: #{command}"
200
+ exit(1)
201
+ end
202
+
203
+ # Wait for a few seconds...
204
+ sleep(2)
205
+
206
+ # Check pid file
207
+ unless File.exists?(pid_file)
208
+ puts "ERROR: Even though startup command succeeded, there is no pid file: #{pid_file}"
209
+ exit(1)
210
+ end
211
+
212
+ # Check to make sure the process exists
213
+ pid = File.read(pid_file).strip
214
+ unless pid_running?(pid)
215
+ puts "ERROR: Even though startup command succeeded and pid file exists, there is no process with pid=#{pid}"
216
+ exit(1)
217
+ end
218
+
219
+ # If we have a health check url, let's check it
220
+ if options[:check_url]
221
+ exit(1) unless check_app_health(options)
222
+ end
223
+
224
+ # Ok, we're good
225
+ puts "Started! PID=#{pid}"
226
+ exit(0)
227
+ end
228
+
229
+ #---------------------------------------------------------------------------------------------------
230
+ def stop_application!(options, graceful = false)
231
+ # Check pid file
232
+ pid_file = unicorn_pid_file(options)
233
+ unless File.exists?(pid_file)
234
+ puts "OK: The process is not running"
235
+ exit(0)
236
+ end
237
+
238
+ pid = read_pid(pid_file)
239
+ if pid_running?(pid)
240
+ stop_unicorn_process(pid, options[:timeout], graceful)
241
+ else
242
+ puts "WARNING: Slate pid file found, removing it: #{pid_file}"
243
+ FileUtils.rm(pid_file)
244
+ end
245
+
246
+ puts "Stopped!"
247
+ exit(0)
248
+ end
249
+
250
+ #---------------------------------------------------------------------------------------------------
251
+ def restart_application!(options, graceful)
252
+ # Check if the app is running and stop it if needed
253
+ pid_file = unicorn_pid_file(options)
254
+ if File.exists?(pid_file)
255
+ pid = read_pid(pid_file)
256
+ if pid_running?(pid)
257
+ stop_unicorn_process(pid, options[:timeout], graceful)
258
+ else
259
+ puts "WARNING: Slate pid file found, removing it: #{pid_file}"
260
+ FileUtils.rm(pid_file)
261
+ end
262
+
263
+ puts "Stopped!"
264
+ else
265
+ puts "The process is not running"
266
+ end
267
+
268
+ # Start the app
269
+ start_application!(options)
270
+ end
271
+
272
+ #---------------------------------------------------------------------------------------------------
273
+ def upgrade_application!(options)
274
+ pid_file = unicorn_pid_file(options)
275
+ old_pid_file = unicorn_pid_file(options) + '.oldbin'
276
+
277
+ # Make sure there is no old pid file (which we could have if an upgrade failed mid-way)
278
+ if File.exists?(old_pid_file)
279
+ puts "WARNING: Old pid file exists: #{old_pid_file}"
280
+
281
+ pid = read_pid(old_pid_file)
282
+ if pid_running?(pid)
283
+ puts "WARNING: Old binary is still running, shutting it down"
284
+ stop_unicorn_process(pid, options[:timeout], false)
285
+ else
286
+ puts "WARNING: Removing stale pid file: #{old_pid_file}"
287
+ FileUtils.rm(old_pid_file)
288
+ end
289
+ end
290
+
291
+ # Now let's see if the app is actually running
292
+ unless File.exists?(pid_file)
293
+ puts "WARNING: No pid file found: #{pid_file}. Trying to do a cold startup procedure..."
294
+ start_application!(options)
295
+ end
296
+
297
+ # Get current pid and check if it is up
298
+ old_pid = read_pid(pid_file)
299
+
300
+ # If the app is down, just do a normal cold startup procedure
301
+ unless pid_running?(old_pid)
302
+ puts "WARNING: Stale pid file found: #{pid_file}. Trying to do a cold startup procedure..."
303
+ start_application!(options)
304
+ end
305
+
306
+ # The app is running, let's try to do the upgrade:
307
+ # Ask old master to start a new binary and move itself into the old state
308
+ puts "Sending USR2 signal to old master: #{old_pid}..."
309
+ send_signal('USR2', old_pid)
310
+
311
+ puts "Waiting for the new master to replace the old one..."
312
+
313
+ # Wait for the new master to start
314
+ start_time = Time.now
315
+ new_started = false
316
+ while Time.now - start_time < options[:timeout]
317
+ sleep(1)
318
+ new_pid = File.exists?(pid_file) ? read_pid(pid_file) : nil
319
+ if new_pid != old_pid
320
+ new_started = true
321
+ break
322
+ end
323
+ end
324
+
325
+ # If we failed to see the new master started, let's try to do a cold restart
326
+ unless new_started
327
+ puts "WARNING: New master didn't start in #{options[:timeout]} seconds, trying to do a cold restart..."
328
+ stop_unicorn_process(old_pid, options[:timeout], false)
329
+ start_application!(options)
330
+ end
331
+
332
+ # We have the new master
333
+ new_pid = read_pid(pid_file)
334
+ puts "New master detected with pid=#{new_pid}"
335
+
336
+ # If we have a health check url, let's check it
337
+ if options[:check_url]
338
+ if check_app_health(options)
339
+ puts "Health check succeeded on the new master!"
340
+ else
341
+ puts "ERROR: Failed to verify health of the new master, nuking everything and trying a cold start..."
342
+ stop_unicorn_process(new_pid, 1, false)
343
+ stop_unicorn_process(old_pid, 1, false)
344
+ start_application!(options)
345
+ end
346
+ end
347
+
348
+ # Now let's shut down the old master
349
+ puts "Stopping old unicorn master: #{old_pid}"
350
+ stop_unicorn_process(old_pid, options[:timeout], true)
351
+
352
+ # All done!
353
+ puts "OK: Upgrade is done successfully!"
354
+ end
355
+
356
+ #---------------------------------------------------------------------------------------------------
357
+ def show_help(error = nil)
358
+ puts "ERROR: #{error}\n\n" if error
359
+
360
+ puts "Usage: #{$0} [options] <command>"
361
+ puts "Valid commands: #{VALID_COMMANDS.join(', ')}"
362
+ puts 'Options:'
363
+ puts ' --app-dir=dir | -d dir Base directory for the application (required)'
364
+ puts ' --environment=name | -e name RACK_ENV to use for the app (default: development)'
365
+ puts ' --health-check-url=url | -H url Health check URL used to make sure the app has started'
366
+ puts ' --health-check-content=string | -C string Health check expected content (default: just check for HTTP 200 OK)'
367
+ puts ' --health-check-timeout=sec | -T sec Individual health check timeout (default: 5 sec)'
368
+ puts ' --timeout=sec | -t sec Operation (start/stop/etc) timeout (default: 30 sec)'
369
+ puts ' --unicorn-config=file | -c file Unicorn config file to use, absolute or relative path (default: shared/unicorn.rb)'
370
+ puts ' --rackup-config=file | -r file Rackup config file to use, absolute or relative path (default: current/config.ru)'
371
+ puts ' --pid-file=file | -p file PID-file unicorn is configured to use (default: shared/pids/unicorn.pid)'
372
+ puts ' --rainbows | -R Use rainbows to start the app (default: use unicorn)'
373
+ puts ' --help | -h This help'
374
+ puts
375
+ exit(error ? 1 : 0)
376
+ end
377
+
378
+ #---------------------------------------------------------------------------------------------------
379
+ # Parse options
380
+ opts = GetoptLong.new(
381
+ [ '--app-dir', '-d', GetoptLong::REQUIRED_ARGUMENT ],
382
+ [ '--environment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
383
+ [ '--health-check-url', '-U', GetoptLong::REQUIRED_ARGUMENT ],
384
+ [ '--health-check-content', '-C', GetoptLong::REQUIRED_ARGUMENT ],
385
+ [ '--health-check-timeout', '-T', GetoptLong::REQUIRED_ARGUMENT ],
386
+ [ '--timeout', '-t', GetoptLong::REQUIRED_ARGUMENT ],
387
+ [ '--unicorn-config=file', '-c', GetoptLong::REQUIRED_ARGUMENT ],
388
+ [ '--rackup-config=file', '-r', GetoptLong::REQUIRED_ARGUMENT ],
389
+ [ '--pid-file=file', '-p', GetoptLong::REQUIRED_ARGUMENT ],
390
+ [ '--rainbows', '-R', GetoptLong::NO_ARGUMENT ],
391
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ]
392
+ )
393
+
394
+ # Default settings
395
+ options = {
396
+ :app_dir => nil,
397
+ :check_url => nil,
398
+ :check_content => nil,
399
+ :env => 'development',
400
+ :timeout => 30,
401
+ :check_timeout => 5,
402
+ :unicorn_bin => 'unicorn',
403
+ :pid_file => nil,
404
+ :rackup_config => nil,
405
+ :unicorn_config => nil
406
+ }
407
+
408
+ # Process options
409
+ opts.each do |opt, arg|
410
+ case opt
411
+ when "--app-dir"
412
+ options[:app_dir] = arg.strip
413
+
414
+ when "--environment"
415
+ options[:env] = arg.strip
416
+
417
+ when "--timeout"
418
+ options[:timeout] = arg.to_i
419
+
420
+ when "--health-check-url"
421
+ options[:check_url] = arg.strip
422
+
423
+ when "--health-check-content"
424
+ options[:check_content] = arg.strip
425
+
426
+ when "--health-check-timeout"
427
+ options[:check_timeout] = arg.to_i
428
+
429
+ when "--unicorn-config"
430
+ options[:unicorn_config] = arg.strip
431
+
432
+ when "--rackup-config"
433
+ options[:rackup_config] = arg.strip
434
+
435
+ when "--pid-file"
436
+ options[:pid_file] = arg.strip
437
+
438
+ when "--rainbows"
439
+ options[:unicorn_bin] = 'rainbows'
440
+
441
+ when "--help"
442
+ show_help
443
+ end
444
+ end
445
+
446
+ # Get command
447
+ command = ARGV.first
448
+
449
+ # Make sure we have the command
450
+ show_help("Please specify one of valid commands: #{VALID_COMMANDS.join(', ')}") unless command
451
+
452
+ # Check app directory
453
+ show_help("Please specify application directory!") unless options[:app_dir]
454
+ show_help("Please specify a valid application directory!") unless File.directory?(options[:app_dir])
455
+ options[:app_dir] = File.realpath(options[:app_dir])
456
+
457
+ #---------------------------------------------------------------------------------------------------
458
+ # Run commands
459
+ case command
460
+ when 'start'
461
+ start_application!(options)
462
+
463
+ when 'stop'
464
+ stop_application!(options, true)
465
+
466
+ when 'force-stop'
467
+ stop_application!(options, false)
468
+
469
+ when 'restart'
470
+ restart_application!(options, true)
471
+
472
+ when 'force-restart'
473
+ restart_application!(options, false)
474
+
475
+ when 'upgrade'
476
+ upgrade_application!(options)
477
+
478
+ else
479
+ show_help("ERROR: Invalid command: #{command}")
480
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unicorn-ctl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Oleksiy Kovyrin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
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: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
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
+ description: A script to control unicorn instances.
47
+ email:
48
+ - alexey@kovyrin.net
49
+ executables:
50
+ - unicornctl
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - bin/unicornctl
55
+ - LICENSE.txt
56
+ - CHANGELOG.md
57
+ - README.md
58
+ homepage: https://github.com/swiftype/unicorn-ctl
59
+ licenses:
60
+ - MIT
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.23
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Start/stop/restart/upgrade unicorn instances reliably.
83
+ test_files: []