mongrel2 0.53.0 → 0.54.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +56 -2
- data/History.rdoc +7 -0
- data/Manifest.txt +17 -0
- data/Rakefile +21 -18
- data/bin/m2sh.rb +3 -738
- data/gem.deps.rb +2 -0
- data/lib/mongrel2.rb +1 -1
- data/lib/mongrel2/cli.rb +496 -0
- data/lib/mongrel2/cli/access.rb +29 -0
- data/lib/mongrel2/cli/bootstrap.rb +39 -0
- data/lib/mongrel2/cli/commit.rb +34 -0
- data/lib/mongrel2/cli/hosts.rb +51 -0
- data/lib/mongrel2/cli/init.rb +29 -0
- data/lib/mongrel2/cli/load.rb +41 -0
- data/lib/mongrel2/cli/log.rb +27 -0
- data/lib/mongrel2/cli/quickstart.rb +52 -0
- data/lib/mongrel2/cli/reload.rb +33 -0
- data/lib/mongrel2/cli/routes.rb +51 -0
- data/lib/mongrel2/cli/running.rb +44 -0
- data/lib/mongrel2/cli/servers.rb +34 -0
- data/lib/mongrel2/cli/settings.rb +26 -0
- data/lib/mongrel2/cli/start.rb +72 -0
- data/lib/mongrel2/cli/stop.rb +33 -0
- data/lib/mongrel2/config.rb +1 -0
- data/lib/mongrel2/config/directory.rb +11 -0
- data/lib/mongrel2/config/dsl.rb +13 -20
- data/lib/mongrel2/config/handler.rb +12 -0
- data/lib/mongrel2/config/host.rb +1 -1
- data/lib/mongrel2/config/proxy.rb +6 -0
- data/lib/mongrel2/config/server.rb +3 -2
- data/spec/mongrel2/config/dsl_spec.rb +6 -0
- metadata +73 -14
- metadata.gz.sig +0 -0
data/gem.deps.rb
ADDED
data/lib/mongrel2.rb
CHANGED
data/lib/mongrel2/cli.rb
ADDED
@@ -0,0 +1,496 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'gli'
|
5
|
+
require 'loggability'
|
6
|
+
require 'pastel'
|
7
|
+
require 'pathname'
|
8
|
+
require 'tty/prompt'
|
9
|
+
require 'tty/table'
|
10
|
+
|
11
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
12
|
+
require 'mongrel2/config'
|
13
|
+
|
14
|
+
|
15
|
+
# A tool for interacting with a Mongrel2 config database and server. This isn't
|
16
|
+
# quite a replacement for the real m2sh yet; here's what I have working so far:
|
17
|
+
#
|
18
|
+
# [√] load Load a config.
|
19
|
+
# [√] config Alias for load.
|
20
|
+
# [-] shell Starts an interactive shell.
|
21
|
+
# [√] access Prints the access log.
|
22
|
+
# [√] servers Lists the servers in a config database.
|
23
|
+
# [√] hosts Lists the hosts in a server.
|
24
|
+
# [√] routes Lists the routes in a host.
|
25
|
+
# [√] commit Adds a message to the log.
|
26
|
+
# [√] log Prints the commit log.
|
27
|
+
# [√] start Starts a server.
|
28
|
+
# [√] stop Stops a server.
|
29
|
+
# [√] reload Reloads a server.
|
30
|
+
# [√] running Tells you what's running.
|
31
|
+
# [-] control Connects to the control port.
|
32
|
+
# [√] version Prints the Mongrel2 and m2sh version.
|
33
|
+
# [√] help Get help, lists commands.
|
34
|
+
# [-] uuid Prints out a randomly generated UUID.
|
35
|
+
#
|
36
|
+
# I just use 'uuidgen' to generate uuids (which is all m2sh does, as
|
37
|
+
# well), so I don't plan to implement that. The 'control' command is more-easily
|
38
|
+
# accessed via pry+Mongrel2::Control, so I'm not going to implement that, either.
|
39
|
+
# Everything else should be analagous to (or better than) the m2sh that comes with
|
40
|
+
# mongrel2. I implemented the 'shell' mode, but I found I never used it, and it
|
41
|
+
# introduced a dependency on the Termios library, so I removed it.
|
42
|
+
#
|
43
|
+
module Mongrel2::CLI
|
44
|
+
extend Loggability,
|
45
|
+
GLI::App
|
46
|
+
|
47
|
+
|
48
|
+
# Write logs to Mongrel2's logger
|
49
|
+
log_to :mongrel2
|
50
|
+
|
51
|
+
|
52
|
+
#
|
53
|
+
# GLI
|
54
|
+
#
|
55
|
+
|
56
|
+
# Set up global[:description] and options
|
57
|
+
program_desc 'Mongrel2 Configurator'
|
58
|
+
|
59
|
+
# The command version
|
60
|
+
version Mongrel2::VERSION
|
61
|
+
|
62
|
+
# Use an OpenStruct for options instead of a Hash
|
63
|
+
use_openstruct( true )
|
64
|
+
|
65
|
+
# Subcommand options are independent of global[:ones]
|
66
|
+
subcommand_option_handling :normal
|
67
|
+
|
68
|
+
# Strict argument validation
|
69
|
+
arguments :strict
|
70
|
+
|
71
|
+
|
72
|
+
# Custom parameter types
|
73
|
+
accept Array do |value|
|
74
|
+
value.strip.split(/\s*,\s*/)
|
75
|
+
end
|
76
|
+
accept Pathname do |value|
|
77
|
+
Pathname( value.strip )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Global options
|
82
|
+
desc 'Enable debugging output'
|
83
|
+
switch [:d, :debug]
|
84
|
+
|
85
|
+
desc 'Enable verbose output'
|
86
|
+
switch [:v, :verbose]
|
87
|
+
|
88
|
+
desc 'Set log level to LEVEL (one of %s)' % [Loggability::LOG_LEVELS.keys.join(', ')]
|
89
|
+
arg_name :LEVEL
|
90
|
+
flag [:l, :loglevel], must_match: Loggability::LOG_LEVELS.keys
|
91
|
+
|
92
|
+
desc "Don't actually do anything, just show what would happen."
|
93
|
+
switch [:n, 'dry-run']
|
94
|
+
|
95
|
+
desc "Additional Ruby libs to require before doing anything."
|
96
|
+
flag [:r, 'requires'], type: Array
|
97
|
+
|
98
|
+
desc "Specify the PATH of the config database to use."
|
99
|
+
arg_name :PATH
|
100
|
+
flag [:config, :C], default: Mongrel2::DEFAULT_CONFIG_URI
|
101
|
+
|
102
|
+
desc "Specify the REASON for an action for the event log."
|
103
|
+
arg_name :REASON
|
104
|
+
flag [:why], type: String
|
105
|
+
|
106
|
+
|
107
|
+
#
|
108
|
+
# GLI Event callbacks
|
109
|
+
#
|
110
|
+
|
111
|
+
# Set up global options
|
112
|
+
pre do |global, command, options, args|
|
113
|
+
self.set_logging_level( global[:l] )
|
114
|
+
Loggability.format_with( :color ) if $stdout.tty?
|
115
|
+
|
116
|
+
# Include a 'lib' directory if there is one
|
117
|
+
$LOAD_PATH.unshift( 'lib' ) if File.directory?( 'lib' )
|
118
|
+
|
119
|
+
self.require_additional_libs( global[:r] ) if global[:r]
|
120
|
+
|
121
|
+
self.setup_pastel_aliases
|
122
|
+
self.setup_output( global )
|
123
|
+
|
124
|
+
Mongrel2::Config.configure( configdb: global.config ) if global.config
|
125
|
+
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
# Write the error to the log on exceptions.
|
131
|
+
on_error do |exception|
|
132
|
+
|
133
|
+
case exception
|
134
|
+
when OptionParser::ParseError, GLI::CustomExit
|
135
|
+
msg = exception.full_message(highlight: false, order: :bottom)
|
136
|
+
self.log.debug( msg )
|
137
|
+
else
|
138
|
+
msg = exception.full_message(highlight: true, order: :bottom)
|
139
|
+
self.log.error( msg )
|
140
|
+
end
|
141
|
+
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
##
|
149
|
+
# Registered subcommand modules
|
150
|
+
singleton_class.attr_accessor :subcommand_modules
|
151
|
+
|
152
|
+
|
153
|
+
### Overridden -- Add registered subcommands immediately before running.
|
154
|
+
def self::run( * )
|
155
|
+
self.add_registered_subcommands
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Add the specified +mod+ule containing subcommands to the 'mongrel2' command.
|
161
|
+
def self::register_subcommands( mod )
|
162
|
+
self.subcommand_modules ||= []
|
163
|
+
self.subcommand_modules.push( mod )
|
164
|
+
mod.extend( GLI::DSL, GLI::AppSupport, Loggability, CommandUtilities )
|
165
|
+
mod.log_to( :mongrel2 )
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Add the commands from the registered subcommand modules.
|
170
|
+
def self::add_registered_subcommands
|
171
|
+
self.subcommand_modules ||= []
|
172
|
+
self.subcommand_modules.each do |mod|
|
173
|
+
merged_commands = mod.commands.merge( self.commands )
|
174
|
+
self.commands.update( merged_commands )
|
175
|
+
command_objs = self.commands_declaration_order | self.commands.values
|
176
|
+
self.commands_declaration_order.replace( command_objs )
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
### Return the Pastel colorizer.
|
182
|
+
###
|
183
|
+
def self::pastel
|
184
|
+
@pastel ||= Pastel.new( enabled: $stdout.tty? )
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
### Return the TTY prompt used by the command to communicate with the
|
189
|
+
### user.
|
190
|
+
def self::prompt
|
191
|
+
@prompt ||= TTY::Prompt.new( output: $stderr )
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
### Discard the existing HighLine prompt object if one existed. Mostly useful for
|
196
|
+
### testing.
|
197
|
+
def self::reset_prompt
|
198
|
+
@prompt = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
### Set the global logging +level+ if it's defined.
|
203
|
+
def self::set_logging_level( level=nil )
|
204
|
+
if level
|
205
|
+
Loggability.level = level.to_sym
|
206
|
+
else
|
207
|
+
Loggability.level = :fatal
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
### Load any additional Ruby libraries given with the -r global option.
|
213
|
+
def self::require_additional_libs( requires)
|
214
|
+
requires.each do |path|
|
215
|
+
path = "mongrel2/#{path}" unless path.start_with?( 'mongrel2/' )
|
216
|
+
require( path )
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
### Setup pastel color aliases
|
222
|
+
###
|
223
|
+
def self::setup_pastel_aliases
|
224
|
+
self.pastel.alias_color( :headline, :bold, :white, :on_black )
|
225
|
+
self.pastel.alias_color( :header, :bold, :white )
|
226
|
+
self.pastel.alias_color( :success, :bold, :green )
|
227
|
+
self.pastel.alias_color( :error, :bold, :red )
|
228
|
+
self.pastel.alias_color( :key, :green )
|
229
|
+
self.pastel.alias_color( :even_row, :bold )
|
230
|
+
self.pastel.alias_color( :odd_row, :reset )
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
### Set up the output levels and globals based on the associated +global+ options.
|
235
|
+
def self::setup_output( global )
|
236
|
+
|
237
|
+
# Turn on Ruby debugging and/or verbosity if specified
|
238
|
+
if global[:n]
|
239
|
+
$DRYRUN = true
|
240
|
+
Loggability.level = :warn
|
241
|
+
else
|
242
|
+
$DRYRUN = false
|
243
|
+
end
|
244
|
+
|
245
|
+
if global[:verbose]
|
246
|
+
$VERBOSE = true
|
247
|
+
Loggability.level = :info
|
248
|
+
end
|
249
|
+
|
250
|
+
if global[:debug]
|
251
|
+
$DEBUG = true
|
252
|
+
Loggability.level = :debug
|
253
|
+
end
|
254
|
+
|
255
|
+
if global[:loglevel]
|
256
|
+
Loggability.level = global[:loglevel]
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
#
|
263
|
+
# GLI subcommands
|
264
|
+
#
|
265
|
+
|
266
|
+
|
267
|
+
# Convenience module for subcommand registration syntax sugar.
|
268
|
+
module Subcommand
|
269
|
+
|
270
|
+
### Extension callback -- register the extending object as a subcommand.
|
271
|
+
def self::extended( mod )
|
272
|
+
Mongrel2::CLI.log.debug "Registering subcommands from %p" % [ mod ]
|
273
|
+
Mongrel2::CLI.register_subcommands( mod )
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
###############
|
278
|
+
module_function
|
279
|
+
###############
|
280
|
+
|
281
|
+
### Exit with the specified +exit_code+ after printing the given +message+.
|
282
|
+
def exit_now!( message, exit_code=1 )
|
283
|
+
raise GLI::CustomExit.new( message, exit_code )
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
### Exit with a helpful +message+ and display the usage.
|
288
|
+
def help_now!( message=nil )
|
289
|
+
exception = OptionParser::ParseError.new( message )
|
290
|
+
def exception.exit_code; 64; end
|
291
|
+
|
292
|
+
raise exception
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
### Get the prompt (a TTY::Prompt object)
|
297
|
+
def prompt
|
298
|
+
return Mongrel2::CLI.prompt
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
### Return the global Pastel object for convenient formatting, color, etc.
|
303
|
+
def hl
|
304
|
+
return Mongrel2::CLI.pastel
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
### Return the specified +string+ in the 'headline' ANSI color.
|
309
|
+
def headline_string( string )
|
310
|
+
return hl.headline( string )
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
### Return the specified +string+ in the 'highlight' ANSI color.
|
315
|
+
def highlight_string( string )
|
316
|
+
return hl.highlight( string )
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
### Return the specified +string+ in the 'success' ANSI color.
|
321
|
+
def success_string( string )
|
322
|
+
return hl.success( string )
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
### Return the specified +string+ in the 'error' ANSI color.
|
327
|
+
def error_string( string )
|
328
|
+
return hl.error( string )
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
### Output a table with the given +header+ (an array) and +rows+
|
333
|
+
### (an array of arrays).
|
334
|
+
def display_table( header, rows )
|
335
|
+
table = TTY::Table.new( header, rows )
|
336
|
+
renderer = nil
|
337
|
+
|
338
|
+
if hl.enabled?
|
339
|
+
renderer = TTY::Table::Renderer::Unicode.new(
|
340
|
+
table,
|
341
|
+
multiline: true,
|
342
|
+
padding: [0,1,0,1]
|
343
|
+
)
|
344
|
+
renderer.border.style = :dim
|
345
|
+
|
346
|
+
else
|
347
|
+
renderer = TTY::Table::Renderer::ASCII.new(
|
348
|
+
table,
|
349
|
+
multiline: true,
|
350
|
+
padding: [0,1,0,1]
|
351
|
+
)
|
352
|
+
end
|
353
|
+
|
354
|
+
puts renderer.render
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
### Return the count of visible (i.e., non-control) characters in the given +string+.
|
359
|
+
def visible_chars( string )
|
360
|
+
return string.to_s.gsub(/\e\[.*?m/, '').scan( /\P{Cntrl}/ ).size
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
### In dry-run mode, output the description instead of running the provided block and
|
365
|
+
### return the +return_value+.
|
366
|
+
### If dry-run mode is not enabled, yield to the block.
|
367
|
+
def unless_dryrun( description, return_value=true )
|
368
|
+
if $DRYRUN
|
369
|
+
self.log.warn( "DRYRUN> #{description}" )
|
370
|
+
return return_value
|
371
|
+
else
|
372
|
+
return yield
|
373
|
+
end
|
374
|
+
end
|
375
|
+
alias_method :unless_dry_run, :unless_dryrun
|
376
|
+
|
377
|
+
end # module Subcommand
|
378
|
+
|
379
|
+
|
380
|
+
# Functions for common command tasks
|
381
|
+
module CommandUtilities
|
382
|
+
|
383
|
+
### Search the current mongrel2 config for a server matching +serverspec+ and
|
384
|
+
### return it as a Mongrel2::Config::Server object.
|
385
|
+
def find_server( serverspec=nil )
|
386
|
+
server = nil
|
387
|
+
servers = Mongrel2::Config.servers
|
388
|
+
|
389
|
+
raise "No servers are configured." if servers.empty?
|
390
|
+
|
391
|
+
# If there's only one configured server, just make sure if a serverspec was given
|
392
|
+
# that it would have matched.
|
393
|
+
if servers.length == 1
|
394
|
+
server = servers.first if !serverspec ||
|
395
|
+
servers.first.values.values_at( :uuid, :default_host, :name ).include?( serverspec )
|
396
|
+
|
397
|
+
# Otherwise, require an argument and search for the desired server if there is one
|
398
|
+
else
|
399
|
+
raise "You must specify a server uuid/hostname/name when more " +
|
400
|
+
"than one server is configured." if servers.length > 1 && !serverspec
|
401
|
+
|
402
|
+
server = servers.find {|s| s.uuid == serverspec } ||
|
403
|
+
servers.find {|s| s.default_host == serverspec } ||
|
404
|
+
servers.find {|s| s.name == serverspec }
|
405
|
+
end
|
406
|
+
|
407
|
+
raise "No servers match '#{serverspec}'" unless server
|
408
|
+
|
409
|
+
return server
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
### Read command line history from HISTORY_FILE
|
414
|
+
def read_history
|
415
|
+
histfile = HISTORY_FILE.expand_path
|
416
|
+
|
417
|
+
if histfile.exist?
|
418
|
+
lines = histfile.readlines.collect {|line| line.chomp }
|
419
|
+
self.log.debug "Read %d saved history commands from %s." % [ lines.length, histfile ]
|
420
|
+
Readline::HISTORY.push( *lines )
|
421
|
+
else
|
422
|
+
self.log.debug "History file '%s' was empty or non-existant." % [ histfile ]
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
|
427
|
+
### Save command line history to HISTORY_FILE
|
428
|
+
def save_history
|
429
|
+
histfile = HISTORY_FILE.expand_path
|
430
|
+
|
431
|
+
lines = Readline::HISTORY.to_a.reverse.uniq.reverse
|
432
|
+
lines = lines[ -DEFAULT_HISTORY_SIZE, DEFAULT_HISTORY_SIZE ] if
|
433
|
+
lines.length > DEFAULT_HISTORY_SIZE
|
434
|
+
|
435
|
+
self.log.debug "Saving %d history lines to %s." % [ lines.length, histfile ]
|
436
|
+
|
437
|
+
histfile.open( File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
|
438
|
+
ofh.puts( *lines )
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
### Invoke the user's editor on the given +filename+ and return the exit code
|
444
|
+
### from doing so.
|
445
|
+
def edit( filename )
|
446
|
+
editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
|
447
|
+
system editor, filename.to_s
|
448
|
+
unless $?.success? || editor =~ /vim/i
|
449
|
+
raise "Editor exited with an error status (%d)" % [ $?.exitstatus ]
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
|
454
|
+
### Search the PATH for a mongrel2 binary, returning the absolute Pathname to it if found, and
|
455
|
+
### outputting a warning and describing how to set ENV['MONGREL2'] if not.
|
456
|
+
def find_mongrel2
|
457
|
+
if ENV['MONGREL2']
|
458
|
+
m2 = Pathname( ENV['MONGREL2'] )
|
459
|
+
error = nil
|
460
|
+
if !m2.file?
|
461
|
+
error = "but it isn't a plain file."
|
462
|
+
elsif !m2.executable?
|
463
|
+
error = "but it isn't executable."
|
464
|
+
end
|
465
|
+
|
466
|
+
raise "MONGREL2 was set to #{m2}, #{error}" if error
|
467
|
+
|
468
|
+
return m2
|
469
|
+
else
|
470
|
+
m2 = ENV['PATH'].split( File::PATH_SEPARATOR ).
|
471
|
+
map {|dir| Pathname(dir) + 'mongrel2' }.
|
472
|
+
find {|path| path.executable? }
|
473
|
+
|
474
|
+
return m2 if m2
|
475
|
+
|
476
|
+
raise "The 'mongrel2' binary doesn't seem to be in your PATH. Either " +
|
477
|
+
"add the appropriate directory to your PATH or set the MONGREL2 " +
|
478
|
+
"environment variable to the full path."
|
479
|
+
end
|
480
|
+
|
481
|
+
end
|
482
|
+
|
483
|
+
end # module CommandUtilities
|
484
|
+
|
485
|
+
### Load commands from any files in the specified directory relative to LOAD_PATHs
|
486
|
+
def self::commands_from( subdir )
|
487
|
+
Gem.find_latest_files( File.join(subdir, '*.rb') ).each do |rbfile|
|
488
|
+
self.log.debug " loading %s..." % [ rbfile ]
|
489
|
+
require( rbfile )
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
|
494
|
+
commands_from 'mongrel2/cli'
|
495
|
+
|
496
|
+
end # class Mongrel2::CLI
|