unicorn-ctl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []