flapjack 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +29 -0
- data/README.md +1 -4
- data/bin/flapjack +24 -2
- data/bin/flapjack-nagios-receiver +4 -2
- data/bin/receive-events +191 -0
- data/bin/simulate-failed-check +144 -0
- data/features/notification_rules.feature +63 -2
- data/features/steps/events_steps.rb +15 -3
- data/flapjack.gemspec +1 -1
- data/lib/flapjack/data/contact.rb +15 -9
- data/lib/flapjack/data/entity.rb +19 -1
- data/lib/flapjack/data/entity_check.rb +12 -0
- data/lib/flapjack/data/event.rb +10 -2
- data/lib/flapjack/data/notification.rb +12 -8
- data/lib/flapjack/data/notification_rule.rb +3 -1
- data/lib/flapjack/executive.rb +71 -17
- data/lib/flapjack/gateways/api.rb +5 -2
- data/lib/flapjack/gateways/jabber.rb +26 -17
- data/lib/flapjack/gateways/web.rb +54 -9
- data/lib/flapjack/gateways/web/public/css/bootstrap-responsive.min.css +9 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap.min.css +9 -0
- data/lib/flapjack/gateways/web/public/css/flapjack.css +51 -0
- data/lib/flapjack/gateways/web/public/img/flapjack_white_bg_400_353.jpeg +0 -0
- data/lib/flapjack/gateways/web/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/flapjack/gateways/web/public/img/glyphicons-halflings.png +0 -0
- data/lib/flapjack/gateways/web/public/js/bootstrap.min.js +6 -0
- data/lib/flapjack/gateways/web/views/_foot.haml +8 -0
- data/lib/flapjack/gateways/web/views/_head.haml +10 -0
- data/lib/flapjack/gateways/web/views/_nav.haml +9 -3
- data/lib/flapjack/gateways/web/views/check.haml +140 -138
- data/lib/flapjack/gateways/web/views/checks.haml +49 -0
- data/lib/flapjack/gateways/web/views/contact.haml +78 -37
- data/lib/flapjack/gateways/web/views/contacts.haml +23 -17
- data/lib/flapjack/gateways/web/views/entities.haml +28 -0
- data/lib/flapjack/gateways/web/views/entity.haml +44 -0
- data/lib/flapjack/gateways/web/views/index.haml +27 -44
- data/lib/flapjack/gateways/web/views/self_stats.haml +65 -22
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/executive_spec.rb +6 -2
- data/spec/lib/flapjack/gateways/api_spec.rb +15 -0
- data/spec/lib/flapjack/gateways/web/views/contact.haml_spec.rb +2 -1
- data/spec/lib/flapjack/gateways/web/views/index.haml_spec.rb +3 -2
- data/spec/lib/flapjack/gateways/web_spec.rb +23 -9
- data/tmp/create_events_failure.rb +6 -4
- metadata +23 -12
data/CHANGELOG.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
## Flapjack Changelog
|
2
|
+
|
3
|
+
# 0.7.2 - 2013-05-06
|
4
|
+
- Feature: executive instance keys now expire after 7 days, touched every event gh-111 (@jessereynolds)
|
5
|
+
- Feature: slightly less sucky looking web UI, also now includes entity listing screens (@jessereynolds)
|
6
|
+
- Feature: expose notification rules and intervals via the Web UI gh-150, gh-151 (@jessereynolds)
|
7
|
+
- Feature: command line - support "--version", "help" etc gh-134 (@jessereynolds)
|
8
|
+
- Feature: replay events from another flapjack gh-138 (@jessereynolds)
|
9
|
+
- Bug: recovery is not resetting notification intervals gh-136 (@jessereynolds)
|
10
|
+
- Bug: recoveries are blocked for users with notification rules gh-148 (@jessereynolds)
|
11
|
+
- Bug: jabber gateway now uses configured alias for commands gh-138 (@jessereynolds)
|
12
|
+
- Bug: jabber gateway was crashing on querying entities with invalid regex gh-147 (@jessereynolds)
|
13
|
+
- Bug: handle media addresses correctly when adding contacts and generating messages gh-145 (@jessereynolds)
|
14
|
+
|
15
|
+
# 0.7.1 - 2013-04-24
|
16
|
+
- Feature: archive incoming events in a sliding window gh-127 (@jessereynolds)
|
17
|
+
- Bug: Unable to retrieve status of a check containing non word characters via the API gh-117 (@ali-graham)
|
18
|
+
- Bug: Disable Thin's loading of Daemons gh-133 (@jessereynolds, thanks @johnf)
|
19
|
+
|
20
|
+
# 0.7.0 - 2013-04-18
|
21
|
+
- Feature: Introduce Notification Rules gh-55 (@jessereynolds)
|
22
|
+
- Feature: Tagging on contacts and entities, expose via API gh-125 (@ali-graham)
|
23
|
+
- Feature: API improvements (notification rules, contact's timezone and notification intervals per media, tags) (@ali-graham, @jessereynolds)
|
24
|
+
- Feature: Contact mass update (rather than drop all then import) gh-124 (@ali-graham)
|
25
|
+
- Feature: Improve error handling (log file paths, permissions), expose internal stats as json gh-122 (@auxesis)
|
26
|
+
- Incompatable Change: POST /contacts in the API now includes intervals per media and is incompatible with previous versions
|
27
|
+
|
28
|
+
# 0.6.61 - 2013-01-11
|
29
|
+
- todo (and previous versions)
|
data/README.md
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# Flapjack
|
2
2
|
|
3
|
-
[![
|
4
|
-
|
5
|
-
[id_travis_link]: https://secure.travis-ci.org/#!/flpjck/flapjack
|
6
|
-
[id_travis_img]: https://secure.travis-ci.org/flpjck/flapjack.png
|
3
|
+
[![Build Status](https://travis-ci.org/flpjck/flapjack.png)](https://travis-ci.org/flpjck/flapjack)
|
7
4
|
|
8
5
|
[flapjack-project.com](http://flapjack-project.com/)
|
9
6
|
|
data/bin/flapjack
CHANGED
@@ -10,6 +10,7 @@ require 'optparse'
|
|
10
10
|
require 'ostruct'
|
11
11
|
|
12
12
|
require 'flapjack/configuration'
|
13
|
+
require 'flapjack/version'
|
13
14
|
|
14
15
|
options = OpenStruct.new
|
15
16
|
options.config = File.join('etc', 'flapjack_config.yaml')
|
@@ -17,7 +18,7 @@ options.daemonize = nil
|
|
17
18
|
|
18
19
|
@exe = File.basename(__FILE__)
|
19
20
|
|
20
|
-
OptionParser.new do |opts|
|
21
|
+
optparse = OptionParser.new do |opts|
|
21
22
|
opts.banner = "Usage: flapjack COMMAND [OPTIONS]"
|
22
23
|
|
23
24
|
opts.separator ""
|
@@ -27,6 +28,8 @@ OptionParser.new do |opts|
|
|
27
28
|
opts.separator " restart #{" " * 23} (re)start flapjack"
|
28
29
|
opts.separator " reload #{" " * 24} reload flapjack configuration"
|
29
30
|
opts.separator " status #{" " * 24} see if flapjack is running"
|
31
|
+
opts.separator " version #{" " * 23} display flapjack version and exit"
|
32
|
+
opts.separator " help #{" " * 26} display this usage info"
|
30
33
|
opts.separator ""
|
31
34
|
opts.separator "Options"
|
32
35
|
|
@@ -46,7 +49,13 @@ OptionParser.new do |opts|
|
|
46
49
|
options.log_path = l
|
47
50
|
end
|
48
51
|
|
49
|
-
|
52
|
+
opts.on("-v", "--version", "display flapjack version") do |v|
|
53
|
+
options.version = v
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
optparse.parse!(ARGV)
|
50
59
|
|
51
60
|
FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
|
52
61
|
|
@@ -71,6 +80,10 @@ daemonize = options.daemonize.nil? ?
|
|
71
80
|
!!config_env['daemonize'] :
|
72
81
|
options.daemonize
|
73
82
|
|
83
|
+
if options.version
|
84
|
+
puts Flapjack::VERSION
|
85
|
+
exit
|
86
|
+
end
|
74
87
|
|
75
88
|
flapjack_coord = Proc.new {
|
76
89
|
require 'flapjack/coordinator'
|
@@ -177,12 +190,21 @@ when "status"
|
|
177
190
|
exit 3
|
178
191
|
end
|
179
192
|
|
193
|
+
when "help"
|
194
|
+
puts optparse
|
195
|
+
exit
|
196
|
+
|
197
|
+
when "version"
|
198
|
+
puts Flapjack::VERSION
|
199
|
+
exit
|
200
|
+
|
180
201
|
else
|
181
202
|
if ARGV.nil? || ARGV.empty?
|
182
203
|
puts "No command provided"
|
183
204
|
else
|
184
205
|
puts "Unknown command provided: '#{ARGV[0]}'"
|
185
206
|
end
|
207
|
+
puts "\n#{optparse}"
|
186
208
|
exit 1
|
187
209
|
|
188
210
|
end
|
@@ -74,7 +74,7 @@ options.daemonize = nil
|
|
74
74
|
|
75
75
|
exe = File.basename(__FILE__)
|
76
76
|
|
77
|
-
OptionParser.new do |opts|
|
77
|
+
optparse = OptionParser.new do |opts|
|
78
78
|
opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
|
79
79
|
|
80
80
|
opts.separator ""
|
@@ -106,7 +106,8 @@ OptionParser.new do |opts|
|
|
106
106
|
options.log_path = l
|
107
107
|
end
|
108
108
|
|
109
|
-
end
|
109
|
+
end
|
110
|
+
optparse.parse!(ARGV)
|
110
111
|
|
111
112
|
FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
|
112
113
|
|
@@ -219,6 +220,7 @@ else
|
|
219
220
|
else
|
220
221
|
puts "Unknown command provided: '#{ARGV[0]}'"
|
221
222
|
end
|
223
|
+
puts "\n#{optparse}"
|
222
224
|
exit 1
|
223
225
|
|
224
226
|
end
|
data/bin/receive-events
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
unless $:.include?(File.dirname(__FILE__) + '/../lib/')
|
4
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
require 'ostruct'
|
9
|
+
require 'redis'
|
10
|
+
require 'yajl/json_gem'
|
11
|
+
|
12
|
+
require 'flapjack/configuration'
|
13
|
+
|
14
|
+
def pike(message)
|
15
|
+
puts "piking out: #{message}"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_event(event, opts)
|
20
|
+
redis = opts[:redis]
|
21
|
+
redis.lpush 'events', event
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive(opts)
|
25
|
+
redis = Redis.new(opts[:redis_options])
|
26
|
+
source_redis = Redis.new(:url => opts[:source])
|
27
|
+
|
28
|
+
archives = get_archive_keys_stats(source_redis)
|
29
|
+
raise "found no archives!" unless archives && archives.length > 0
|
30
|
+
|
31
|
+
puts "found archives: #{archives.inspect}"
|
32
|
+
|
33
|
+
# each archive bucket is a redis list that is written
|
34
|
+
# with brpoplpush, that is newest items are added to the left (head)
|
35
|
+
# of the list, so oldest events are to be found at the tail of the list.
|
36
|
+
#
|
37
|
+
# the index of these archives, in the 'archives' array, also stores the
|
38
|
+
# redis key names for each bucket in oldest to newest
|
39
|
+
events_sent = 0
|
40
|
+
case
|
41
|
+
when opts[:all]
|
42
|
+
archive_key = archives[0][:name]
|
43
|
+
cursor = -1
|
44
|
+
when opts[:last], opts[:time]
|
45
|
+
raise "Sorry, unimplemented"
|
46
|
+
else
|
47
|
+
# wait for the next event to be archived, so point the cursor at a non-existant
|
48
|
+
# slot in the list, the one before the 0'th
|
49
|
+
archive_key = archives[-1][:name]
|
50
|
+
cursor = -1 - archives[-1][:size]
|
51
|
+
end
|
52
|
+
|
53
|
+
puts archive_key
|
54
|
+
|
55
|
+
loop do
|
56
|
+
new_archive_key = false
|
57
|
+
# something to read at cursor?
|
58
|
+
event = source_redis.lindex(archive_key, cursor)
|
59
|
+
if event
|
60
|
+
send_event(event, :redis => redis)
|
61
|
+
events_sent += 1
|
62
|
+
print "#{events_sent} " if events_sent % 1000 == 0
|
63
|
+
cursor -= 1
|
64
|
+
else
|
65
|
+
puts "\narchive key: #{archive_key}, cursor: #{cursor}"
|
66
|
+
# do we need to look at the next archive bucket?
|
67
|
+
archives = get_archive_keys_stats(source_redis)
|
68
|
+
i = archives.index {|a| a[:name] == archive_key }
|
69
|
+
if archives[i][:size] = (cursor.abs + 1)
|
70
|
+
if archives[i + 1]
|
71
|
+
archive_key = archives[i + 1][:name]
|
72
|
+
puts archive_key
|
73
|
+
cursor = -1
|
74
|
+
new_archive_key = true
|
75
|
+
else
|
76
|
+
return unless opts[:follow]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
sleep 1 unless new_archive_key
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_archive_keys_stats(source_redis)
|
85
|
+
source_redis.keys("events_archive:*").sort.map {|a|
|
86
|
+
{ :name => a,
|
87
|
+
:size => source_redis.llen(a) }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
options = OpenStruct.new
|
92
|
+
options.config = nil
|
93
|
+
options.daemonize = nil
|
94
|
+
|
95
|
+
exe = File.basename(__FILE__)
|
96
|
+
|
97
|
+
optparse = OptionParser.new do |opts|
|
98
|
+
opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
|
99
|
+
|
100
|
+
opts.separator ""
|
101
|
+
opts.separator "Commands"
|
102
|
+
opts.separator " help"
|
103
|
+
opts.separator ""
|
104
|
+
opts.separator "Options"
|
105
|
+
|
106
|
+
opts.on("-c", "--config [PATH]", String, "PATH to the config file to use") do |c|
|
107
|
+
options.config = c
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.on("-s", "--source URL", String, "URL of source redis database, eg redis://localhost:6379/0") do |s|
|
111
|
+
options.source = s
|
112
|
+
end
|
113
|
+
|
114
|
+
opts.on("-f", "--follow", String, "keep reading events as they are archived on the source") do |f|
|
115
|
+
options.follow = true
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on("-a", "--all", String, "replay all archived events from the source") do |a|
|
119
|
+
options.all = true
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.on("-l", "--last COUNT", String, "replay the last COUNT events from the source") do |l|
|
123
|
+
options.count = l
|
124
|
+
end
|
125
|
+
|
126
|
+
opts.on("-t", "--time TIME", String, "replay all events archived on the source since TIME") do |t|
|
127
|
+
options.since = t
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
optparse.parse!(ARGV)
|
132
|
+
|
133
|
+
FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
|
134
|
+
|
135
|
+
if options.config
|
136
|
+
config_file = options.config
|
137
|
+
pike "specified config file cannot be read" unless File.readable?(config_file)
|
138
|
+
else
|
139
|
+
[ 'etc/flapjack_config.yaml',
|
140
|
+
File.dirname(__FILE__) + 'etc/flapjack_config.yaml',
|
141
|
+
'/etc/flapjack/flapjack_config.yaml' ].each do |candidate|
|
142
|
+
if File.readable?(candidate)
|
143
|
+
config_file = candidate
|
144
|
+
break
|
145
|
+
else
|
146
|
+
puts "not found and/or not readable: #{candidate}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
pike "no config file specified and none found in default locations" unless config_file
|
150
|
+
end
|
151
|
+
|
152
|
+
config = Flapjack::Configuration.new
|
153
|
+
config.load(config_file)
|
154
|
+
config_env = config.all
|
155
|
+
redis_options = config.for_redis
|
156
|
+
|
157
|
+
if config_env.nil? || config_env.empty?
|
158
|
+
puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{options.config}'"
|
159
|
+
exit(false)
|
160
|
+
end
|
161
|
+
|
162
|
+
unless options.source
|
163
|
+
puts "--source URL is required"
|
164
|
+
exit 1
|
165
|
+
end
|
166
|
+
|
167
|
+
unless options.follow || options.all
|
168
|
+
puts "one or both of --follow or --all is required"
|
169
|
+
exit 1
|
170
|
+
end
|
171
|
+
|
172
|
+
case ARGV[0]
|
173
|
+
when "help"
|
174
|
+
puts optparse
|
175
|
+
exit
|
176
|
+
else
|
177
|
+
unless ARGV.nil? || ARGV.empty?
|
178
|
+
puts "Unknown command provided: '#{ARGV[0]}'"
|
179
|
+
puts "\n#{optparse}"
|
180
|
+
exit 1
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
receive(:follow => options.follow,
|
185
|
+
:all => options.all,
|
186
|
+
:source => options.source,
|
187
|
+
:last => options.last,
|
188
|
+
:time => options.time,
|
189
|
+
:redis_options => redis_options)
|
190
|
+
|
191
|
+
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
unless $:.include?(File.dirname(__FILE__) + '/../lib/')
|
4
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
require 'ostruct'
|
9
|
+
require 'redis'
|
10
|
+
require 'yajl/json_gem'
|
11
|
+
require 'eventmachine'
|
12
|
+
|
13
|
+
require 'flapjack/configuration'
|
14
|
+
|
15
|
+
def pike(message)
|
16
|
+
puts "piking out: #{message}"
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def send_event(event, opts)
|
21
|
+
redis = opts[:redis]
|
22
|
+
redis.lpush 'events', event.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
def fail(opts)
|
26
|
+
redis = Redis.new(opts[:redis_options])
|
27
|
+
stop_after = opts[:minutes].to_i * 60
|
28
|
+
recover = opts[:recover]
|
29
|
+
event = {
|
30
|
+
'entity' => opts[:entity] || 'foo-app-01',
|
31
|
+
'check' => opts[:check] || 'HTTP',
|
32
|
+
'type' => 'service',
|
33
|
+
'timestamp' => Time.now.to_i
|
34
|
+
}
|
35
|
+
failure = event.merge('state' => 'critical', 'summary' => 'Connection refused')
|
36
|
+
recovery = event.merge('state' => 'ok', 'summary' => '200 OK, 37 ms')
|
37
|
+
key = "#{event['entity']}:#{event['check']}"
|
38
|
+
|
39
|
+
puts "#{Time.now}: sending failure event for #{key}"
|
40
|
+
send_event(failure, :redis => redis)
|
41
|
+
|
42
|
+
EM.run {
|
43
|
+
|
44
|
+
EM.add_timer(stop_after) do
|
45
|
+
puts "#{Time.now}: stopping"
|
46
|
+
if recover
|
47
|
+
puts "#{Time.now}: sending recovery event for #{key}"
|
48
|
+
send_event(recovery, :redis => redis)
|
49
|
+
end
|
50
|
+
EM.stop
|
51
|
+
end
|
52
|
+
|
53
|
+
EM.add_periodic_timer(10) do
|
54
|
+
puts "#{Time.now}: sending failure event for #{key}"
|
55
|
+
send_event(failure, :redis => redis)
|
56
|
+
end
|
57
|
+
|
58
|
+
}
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
options = OpenStruct.new
|
64
|
+
options.config = nil
|
65
|
+
options.daemonize = nil
|
66
|
+
|
67
|
+
exe = File.basename(__FILE__)
|
68
|
+
|
69
|
+
optparse = OptionParser.new do |opts|
|
70
|
+
opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
|
71
|
+
|
72
|
+
opts.separator ""
|
73
|
+
opts.separator "Commands"
|
74
|
+
opts.separator " fail-and-recover"
|
75
|
+
opts.separator " fail"
|
76
|
+
opts.separator ""
|
77
|
+
opts.separator "Options"
|
78
|
+
|
79
|
+
opts.on("-c", "--config [PATH]", String, "PATH to the config file to use") do |c|
|
80
|
+
options.config = c
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on("-t", "--time MINUTES", String, "MINUTES to generate failure events for") do |t|
|
84
|
+
options.minutes = t
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on("-e", "--entity ENTITY", String, "ENTITY to generate failure events for ('foo-app-01')") do |e|
|
88
|
+
options.entity = e
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("-k", "--check CHECK", String, "CHECK to generate failure events for ('HTTP')") do |k|
|
92
|
+
options.check = k
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
optparse.parse!(ARGV)
|
97
|
+
|
98
|
+
FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
|
99
|
+
|
100
|
+
if options.config
|
101
|
+
config_file = options.config
|
102
|
+
pike "specified config file cannot be read" unless File.readable?(config_file)
|
103
|
+
else
|
104
|
+
[ 'etc/flapjack_config.yaml',
|
105
|
+
File.dirname(__FILE__) + 'etc/flapjack_config.yaml',
|
106
|
+
'/etc/flapjack/flapjack_config.yaml' ].each do |candidate|
|
107
|
+
if File.readable?(candidate)
|
108
|
+
config_file = candidate
|
109
|
+
break
|
110
|
+
else
|
111
|
+
puts "not found and/or not readable: #{candidate}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
pike "no config file specified and none found in default locations" unless config_file
|
115
|
+
end
|
116
|
+
|
117
|
+
config = Flapjack::Configuration.new
|
118
|
+
config.load(config_file)
|
119
|
+
config_env = config.all
|
120
|
+
redis_options = config.for_redis
|
121
|
+
|
122
|
+
if config_env.nil? || config_env.empty?
|
123
|
+
puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{options.config}'"
|
124
|
+
exit(false)
|
125
|
+
end
|
126
|
+
|
127
|
+
options = {:redis_options => redis_options, :minutes => options.minutes,
|
128
|
+
:entity => options.entity, :check => options.check}
|
129
|
+
case ARGV[0]
|
130
|
+
when "fail-and-recover"
|
131
|
+
fail(options.merge(:recover => true))
|
132
|
+
puts " done."
|
133
|
+
when "fail"
|
134
|
+
fail(options.merge(:recover => false))
|
135
|
+
puts " done."
|
136
|
+
else
|
137
|
+
if ARGV.nil? || ARGV.empty?
|
138
|
+
puts "No command provided."
|
139
|
+
else
|
140
|
+
puts "Unknown command provided: '#{ARGV[0]}'"
|
141
|
+
end
|
142
|
+
puts "\n#{optparse}"
|
143
|
+
exit 1
|
144
|
+
end
|