flapjack 0.7.27 → 0.7.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +9 -0
  3. data/bin/flapjack +22 -28
  4. data/bin/flapjack-nagios-receiver +5 -27
  5. data/bin/flapjack-populator +2 -2
  6. data/bin/flapper +13 -14
  7. data/bin/receive-events +3 -20
  8. data/bin/simulate-failed-check +3 -20
  9. data/etc/flapjack_config.yaml.example +119 -86
  10. data/features/cli.feature +69 -0
  11. data/features/events.feature +15 -0
  12. data/features/packaging-lintian.feature +4 -6
  13. data/features/rollup.feature +198 -0
  14. data/features/steps/cli_steps.rb +81 -0
  15. data/features/steps/events_steps.rb +26 -16
  16. data/features/steps/notifications_steps.rb +2 -2
  17. data/features/steps/packaging-lintian_steps.rb +2 -2
  18. data/features/support/daemons.rb +113 -0
  19. data/features/support/env.rb +26 -4
  20. data/lib/flapjack/configuration.rb +2 -0
  21. data/lib/flapjack/data/contact.rb +76 -5
  22. data/lib/flapjack/data/entity_check.rb +16 -0
  23. data/lib/flapjack/data/message.rb +11 -8
  24. data/lib/flapjack/data/notification.rb +31 -3
  25. data/lib/flapjack/data/notification_rule.rb +1 -1
  26. data/lib/flapjack/filters/delays.rb +1 -5
  27. data/lib/flapjack/gateways/api/contact_methods.rb +12 -6
  28. data/lib/flapjack/gateways/email.rb +35 -26
  29. data/lib/flapjack/gateways/email/alert.html.erb +4 -4
  30. data/lib/flapjack/gateways/email/alert.text.erb +2 -2
  31. data/lib/flapjack/gateways/email/alert_subject.text.erb +14 -0
  32. data/lib/flapjack/gateways/email/rollup.html.erb +48 -0
  33. data/lib/flapjack/gateways/email/rollup.text.erb +20 -0
  34. data/lib/flapjack/gateways/email/rollup_subject.text.erb +19 -0
  35. data/lib/flapjack/gateways/jabber.rb +97 -47
  36. data/lib/flapjack/gateways/sms_messagenet.rb +26 -24
  37. data/lib/flapjack/gateways/sms_messagenet/alert.text.erb +15 -0
  38. data/lib/flapjack/gateways/sms_messagenet/rollup.text.erb +34 -0
  39. data/lib/flapjack/gateways/web/views/contact.html.erb +16 -8
  40. data/lib/flapjack/notifier.rb +17 -4
  41. data/lib/flapjack/processor.rb +1 -1
  42. data/lib/flapjack/version.rb +1 -1
  43. data/spec/lib/flapjack/coordinator_spec.rb +19 -19
  44. data/spec/lib/flapjack/data/contact_spec.rb +100 -25
  45. data/spec/lib/flapjack/data/event_spec.rb +1 -1
  46. data/spec/lib/flapjack/data/message_spec.rb +1 -1
  47. data/spec/lib/flapjack/data/notification_spec.rb +11 -3
  48. data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +36 -17
  49. data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +1 -1
  50. data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +38 -38
  51. data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +15 -15
  52. data/spec/lib/flapjack/gateways/email_spec.rb +4 -4
  53. data/spec/lib/flapjack/gateways/jabber_spec.rb +13 -14
  54. data/spec/lib/flapjack/gateways/oobetet_spec.rb +2 -2
  55. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +5 -5
  56. data/spec/lib/flapjack/gateways/sms_messagenet.spec.rb +1 -1
  57. data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +2 -2
  58. data/spec/lib/flapjack/gateways/web_spec.rb +4 -4
  59. data/spec/lib/flapjack/logger_spec.rb +3 -3
  60. data/spec/lib/flapjack/pikelet_spec.rb +10 -10
  61. data/spec/lib/flapjack/processor_spec.rb +4 -4
  62. data/spec/lib/flapjack/redis_pool_spec.rb +1 -1
  63. metadata +70 -5
  64. checksums.yaml +0 -15
@@ -94,7 +94,11 @@ module Flapjack
94
94
  end
95
95
 
96
96
  def ok?
97
- ['ok', 'up'].include?(@state)
97
+ @state && ['ok', 'up'].include?(@state.downcase)
98
+ end
99
+
100
+ def acknowledgement?
101
+ @state && ['acknowledgement'].include?(@state.downcase)
98
102
  end
99
103
 
100
104
  def contents
@@ -190,9 +194,33 @@ module Flapjack
190
194
 
191
195
  logger.debug "media_to_use: #{media_to_use}"
192
196
 
193
- media_to_use.each_pair.inject([]) { |ret, (k, v)|
197
+ # here begins rollup madness
198
+ media_to_use.each_pair.inject([]) { |ret, (media, address)|
199
+ rollup_type = nil
200
+
201
+ contact.add_alerting_check_for_media(media, @event_id) unless ok? || acknowledgement?
202
+
203
+ # expunge checks in (un)scheduled maintenance from the alerting set
204
+ cleaned = contact.clean_alerting_checks_for_media(media)
205
+ logger.debug("cleaned alerting checks for #{media}: #{cleaned}")
206
+
207
+ alerting_checks = contact.count_alerting_checks_for_media(media)
208
+ rollup_threshold = contact.rollup_threshold_for_media(media)
209
+ case
210
+ when rollup_threshold.nil?
211
+ # back away slowly
212
+ when alerting_checks >= rollup_threshold
213
+ next ret if contact.drop_rollup_notifications_for_media?(media)
214
+ contact.update_sent_rollup_alert_keys_for_media(media, :delete => ok?)
215
+ rollup_type = 'problem'
216
+ when (alerting_checks + cleaned >= rollup_threshold)
217
+ # alerting checks was just cleaned such that it is now below the rollup threshold
218
+ rollup_type = 'recovery'
219
+ end
220
+ logger.debug "rollup decisions: #{@event_id} #{@state} #{media} #{address} rollup_type: #{rollup_type}"
221
+
194
222
  m = Flapjack::Data::Message.for_contact(contact,
195
- :medium => k, :address => v)
223
+ :medium => media, :address => address, :rollup => rollup_type)
196
224
  ret << m
197
225
  ret
198
226
  }
@@ -145,7 +145,7 @@ module Flapjack
145
145
  :contact_id => rule_data[:contact_id].to_s,
146
146
  :entities => Oj.dump(rule_data[:entities]),
147
147
  :tags => Oj.dump(tag_data),
148
- :time_restrictions => Oj.dump(rule_data[:time_restrictions]),
148
+ :time_restrictions => Oj.dump(rule_data[:time_restrictions], :mode => :compat),
149
149
  :unknown_media => Oj.dump(rule_data[:unknown_media]),
150
150
  :warning_media => Oj.dump(rule_data[:warning_media]),
151
151
  :critical_media => Oj.dump(rule_data[:critical_media]),
@@ -36,12 +36,8 @@ module Flapjack
36
36
  end
37
37
 
38
38
  last_problem_alert = entity_check.last_notification_for_state(:problem)[:timestamp]
39
- last_warning_alert = entity_check.last_notification_for_state(:warning)[:timestamp]
40
- last_critical_alert = entity_check.last_notification_for_state(:critical)[:timestamp]
41
39
  last_change = entity_check.last_change
42
- last_notification = entity_check.last_notification
43
- last_alert_state = last_notification[:type]
44
- last_alert_timestamp = last_notification[:timestamp]
40
+ last_alert_state = entity_check.last_notification[:type]
45
41
 
46
42
  current_time = Time.now.to_i
47
43
  current_state_duration = current_time - last_change
@@ -197,9 +197,11 @@ module Flapjack
197
197
 
198
198
  media = contact.media
199
199
  media_intervals = contact.media_intervals
200
+ media_rollup_thresholds = contact.media_rollup_thresholds
200
201
  media_addr_int = hashify(*media.keys) {|k|
201
- [k, {'address' => media[k],
202
- 'interval' => media_intervals[k] }]
202
+ [k, {'address' => media[k],
203
+ 'interval' => media_intervals[k],
204
+ 'rollup_threshold' => media_rollup_thresholds[k] }]
203
205
  }
204
206
  media_addr_int.to_json
205
207
  end
@@ -218,8 +220,10 @@ module Flapjack
218
220
  if interval.nil?
219
221
  halt err(403, "no #{params[:id]} interval for contact '#{params[:contact_id]}'")
220
222
  end
221
- {'address' => media,
222
- 'interval' => interval}.to_json
223
+ rollup_threshold = contact.media_rollup_thresholds[params[:id]]
224
+ {'address' => media,
225
+ 'interval' => interval,
226
+ 'rollup_threshold' => rollup_threshold }.to_json
223
227
  end
224
228
 
225
229
  # Creates or updates a media of a contact
@@ -237,9 +241,11 @@ module Flapjack
237
241
 
238
242
  contact.set_address_for_media(params[:id], params[:address])
239
243
  contact.set_interval_for_media(params[:id], params[:interval])
244
+ contact.set_rollup_threshold_for_media(params[:id], params[:rollup_threshold])
240
245
 
241
- {'address' => contact.media[params[:id]],
242
- 'interval' => contact.media_intervals[params[:id]]}.to_json
246
+ {'address' => contact.media[params[:id]],
247
+ 'interval' => contact.media_intervals[params[:id]],
248
+ 'rollup_threshold' => contact.media_rollup_thresholds[params[:id]]}.to_json
243
249
  end
244
250
 
245
251
  # delete a media of a contact
@@ -4,6 +4,7 @@ require 'mail'
4
4
  require 'erb'
5
5
  require 'socket'
6
6
  require 'chronic_duration'
7
+ require 'active_support/inflector'
7
8
 
8
9
  require 'em-synchrony'
9
10
  require 'em/protocols/smtpclient'
@@ -26,6 +27,7 @@ module Flapjack
26
27
  @logger.debug("new email gateway pikelet with the following options: #{@config.inspect}")
27
28
  @smtp_config = @config.delete('smtp_config')
28
29
  @sent = 0
30
+ @fqdn = `/bin/hostname -f`.chomp
29
31
  end
30
32
 
31
33
  # TODO refactor to remove complexity
@@ -34,12 +36,17 @@ module Flapjack
34
36
  deliver( notification )
35
37
  end
36
38
 
39
+ # sets a bunch of class instance variables for each email
37
40
  def prepare(notification)
38
41
  @logger.debug "Woo, got a notification to send out: #{notification.inspect}"
39
42
 
40
43
  # The instance variables are referenced by the templates, which
41
44
  # share the current binding context
42
45
  @notification_type = notification['notification_type']
46
+ @notification_id = notification['id'] || SecureRandom.uuid
47
+ @rollup = notification['rollup']
48
+ @rollup_alerts = notification['rollup_alerts']
49
+ @rollup_threshold = notification['rollup_threshold']
43
50
  @contact_first_name = notification['contact_first_name']
44
51
  @contact_last_name = notification['contact_last_name']
45
52
  @state = notification['state']
@@ -57,17 +64,6 @@ module Flapjack
57
64
  @in_unscheduled_maintenance = entity_check.in_scheduled_maintenance?
58
65
  @in_scheduled_maintenance = entity_check.in_unscheduled_maintenance?
59
66
 
60
- headline_map = {'problem' => 'Problem: ',
61
- 'recovery' => 'Recovery: ',
62
- 'acknowledgement' => 'Acknowledgement: ',
63
- 'test' => 'Test Notification: ',
64
- 'unknown' => ''
65
- }
66
-
67
- headline = headline_map[@notification_type] || ''
68
-
69
- @subject = "#{headline}'#{@check}' on #{@entity_name}"
70
- @subject += " is #{@state.upcase}" unless ['acknowledgement', 'test'].include?(@notification_type)
71
67
  rescue => e
72
68
  @logger.error "Error preparing email to #{m_to}: #{e.class}: #{e.message}"
73
69
  @logger.error e.backtrace.join("\n")
@@ -87,23 +83,19 @@ module Flapjack
87
83
  end
88
84
  end
89
85
 
90
- fqdn = `/bin/hostname -f`.chomp
91
- m_from = "flapjack@#{fqdn}"
86
+ m_from = "flapjack@#{@fqdn}"
92
87
  @logger.debug("flapjack_mailer: set from to #{m_from}")
93
88
  m_reply_to = m_from
94
89
  m_to = notification['address']
95
90
 
96
- @logger.debug("sending Flapjack::Notification::Email " +
97
- "#{notification['id']} to: #{m_to} subject: #{@subject}")
98
91
 
99
- mail = prepare_email(:subject => @subject,
100
- :from => m_from,
101
- :to => m_to)
92
+ mail = prepare_email(:from => m_from,
93
+ :to => m_to)
102
94
 
103
95
  smtp_args = {:from => m_from,
104
96
  :to => m_to,
105
97
  :content => "#{mail.to_s}\r\n.\r\n",
106
- :domain => fqdn,
98
+ :domain => @fqdn,
107
99
  :host => host || 'localhost',
108
100
  :port => port || 25,
109
101
  :starttls => starttls}
@@ -132,20 +124,37 @@ module Flapjack
132
124
  private
133
125
 
134
126
  def prepare_email(opts = {})
127
+ from = opts[:from]
128
+ to = opts[:to]
129
+ message_id = "<#{@notification_id}@#{@fqdn}>"
130
+
131
+ message_type = case
132
+ when @rollup
133
+ 'rollup'
134
+ else
135
+ 'alert'
136
+ end
137
+
138
+ subject_template = ERB.new(File.read(File.dirname(__FILE__) +
139
+ "/email/#{message_type}_subject.text.erb"), nil, '-')
135
140
 
136
141
  text_template = ERB.new(File.read(File.dirname(__FILE__) +
137
- '/email/alert.text.erb'), nil, '-')
142
+ "/email/#{message_type}.text.erb"), nil, '-')
138
143
 
139
144
  html_template = ERB.new(File.read(File.dirname(__FILE__) +
140
- '/email/alert.html.erb'), nil, '-')
145
+ "/email/#{message_type}.html.erb"), nil, '-')
146
+
147
+ bnd = binding
148
+ subject = subject_template.result(bnd).chomp
141
149
 
142
- bnd = binding
150
+ @logger.debug("preparing email to: #{to}, subject: #{subject}, message-id: #{message_id}")
143
151
 
144
152
  mail = Mail.new do
145
- from opts[:from]
146
- to opts[:to]
147
- subject opts[:subject]
148
- reply_to opts[:from]
153
+ from from
154
+ to to
155
+ subject subject
156
+ reply_to from
157
+ message_id message_id
149
158
 
150
159
  text_part do
151
160
  body text_template.result(bnd)
@@ -29,7 +29,7 @@
29
29
 
30
30
  <tr>
31
31
  <td><strong>State</strong></td>
32
- <td><%= @state.upcase %></td>
32
+ <td><%= ['ok'].include?(@state) ? @state.upcase : @state.titleize %></td>
33
33
  </tr>
34
34
 
35
35
  <tr>
@@ -61,7 +61,7 @@
61
61
  <% if @last_state %>
62
62
  <tr>
63
63
  <td><strong>Previous State</strong></td>
64
- <td><%= @last_state.upcase %></td>
64
+ <td><%= ['ok'].include?(@last_state) ? @last_state.upcase : @last_state.titleize %></td>
65
65
  </tr>
66
66
  <% end %>
67
67
 
@@ -75,5 +75,5 @@
75
75
  </tbody>
76
76
  </table>
77
77
 
78
- <p>Cheers,</p>
79
- <p>Flapjack</p>
78
+ <p>Cheers,<br/>
79
+ Flapjack</p>
@@ -4,7 +4,7 @@ Monitoring has detected the following:
4
4
 
5
5
  Entity: <%= @entity_name %>
6
6
  Check: <%= @check %>
7
- State: <%= @state.upcase %>
7
+ State: <%= ['ok'].include?(@state) ? @state.upcase : @state.titleize %>
8
8
  Summary: <%= @summary %>
9
9
  <% if @details -%>
10
10
  Details: <%= @details %>
@@ -16,7 +16,7 @@ Time: <%= Time.at(@time.to_i).to_s %>
16
16
  Duration: <%= ChronicDuration.output(@duration) %>
17
17
  <% end -%>
18
18
  <% if @last_state -%>
19
- Previous State: <%= @last_state.upcase %>
19
+ Previous State: <%= ['ok'].include?(@last_state) ? @last_state.upcase : @last_state.titleize %>
20
20
  <% end -%>
21
21
  <% if @last_summary -%>
22
22
  Previous Summary: <%= @last_summary %>
@@ -0,0 +1,14 @@
1
+ <% case @notification_type -%>
2
+ <% when "problem" -%>
3
+ <%= "Problem: " -%>
4
+ <% when "recovery" -%>
5
+ <%= "Recovery: " -%>
6
+ <% when "acknowledgement" -%>
7
+ <%= "Acknowledgement: " -%>
8
+ <% when "test" -%>
9
+ <%= "Test notification: " -%>
10
+ <% end -%>
11
+ '<%= @check %>' on <%= @entity_name -%>
12
+ <% if ! ['acknowledgement', 'test'].include?(@notification_type) -%>
13
+ is <%= ['ok'].include?(@state) ? @state.upcase : @state.titleize -%>
14
+ <% end -%>
@@ -0,0 +1,48 @@
1
+ <style type="text/css" media="screen">
2
+ #container {
3
+ text-transform: uppercase;
4
+ }
5
+ table {
6
+ border-collapse: collapse;
7
+ }
8
+ table, th, td {
9
+ border: 1px solid #666;
10
+ padding: 4px;
11
+ }
12
+ </style>
13
+
14
+ <p>Hi <%= @contact_first_name %></p>
15
+
16
+ <p>You have <%= @rollup_alerts.length %> alerting check<%= @rollup_alerts.length == 1 ? '' : 's' %> as follows:</p>
17
+
18
+ <table>
19
+ <tbody>
20
+ <tr>
21
+ <th>Check</th>
22
+ <th>Entity</th>
23
+ <th>State</th>
24
+ <th>Duration</th>
25
+ </tr>
26
+ <% @rollup_alerts.sort_by {|entity_check, details| details['duration'] }.each do |rollup_alert| -%>
27
+ <% r_entity, r_check = rollup_alert[0].split(':', 2) -%>
28
+ <% state = rollup_alert[1]['state'] -%>
29
+ <% duration = ChronicDuration.output(rollup_alert[1]['duration']) -%>
30
+ <tr>
31
+ <td><%= r_check %></td>
32
+ <td><%= r_entity %></td>
33
+ <td><%= ['ok'].include?(state) ? state.upcase : state.titleize %></td>
34
+ <td><%= duration %></td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <% if @rollup.downcase == 'recovery' %>
41
+ <p>As your email summary threshold is <%= @rollup_threshold %>, we're taking your email alerts out of summary mode now. You'll now be emailed individually for each alerting check.</p>
42
+ <% else %>
43
+ <p>Your email alerts are being summarised as your email summary threshold is set to <%= @rollup_threshold %>. You'll receive summary emails like this one until your number of alerting checks falls below <%= @rollup_threshold %>.</p>
44
+ <% end %>
45
+
46
+ <p>Cheers,<br/>
47
+ Flapjack</p>
48
+
@@ -0,0 +1,20 @@
1
+ Hi <%= @contact_first_name %>
2
+
3
+ You have <%= @rollup_alerts.length %> alerting check<%= @rollup_alerts.length == 1 ? '' : 's' %> as follows:
4
+
5
+ <% @rollup_alerts.sort_by {|entity_check, details| details['duration'] }.each do |rollup_alert| -%>
6
+ <% r_entity, r_check = rollup_alert[0].split(':', 2) -%>
7
+ <% state = rollup_alert[1]['state'] -%>
8
+ <% duration = ChronicDuration.output(rollup_alert[1]['duration']) -%>
9
+ * <%= r_check %> on <%= r_entity %> is <%= ['ok'].include?(state) ? state.upcase : state.titleize %> (<%= duration %>)
10
+ <% end -%>
11
+
12
+ <% if @rollup.downcase == 'recovery' -%>
13
+ As your email summary threshold is <%= @rollup_threshold %>, we're taking your email alerts out of summary mode now. You'll now be emailed individually for each alerting check.
14
+ <% else -%>
15
+ Your email alerts are being summarised as your email summary threshold is set to <%= @rollup_threshold %>. You'll receive summary emails like this one until your number of alerting checks falls below <%= @rollup_threshold %>.
16
+ <% end -%>
17
+
18
+ Cheers,
19
+ Flapjack
20
+
@@ -0,0 +1,19 @@
1
+ <%
2
+ state_counts = @rollup_alerts.inject({}) do |memo, alert|
3
+ puts "alert: #{alert.inspect}"
4
+ memo[alert[1]['state']] = (memo[alert[1]['state']] || 0) + 1
5
+ memo
6
+ end
7
+ states_summary = ['critical', 'warning', 'unknown'].inject([]) do |memo, state|
8
+ next memo unless state_counts[state]
9
+ memo << "#{state.titleize}: #{state_counts[state]}"
10
+ memo
11
+ end.join(', ')
12
+ -%>
13
+ <% case @rollup -%>
14
+ <% when "problem" -%>
15
+ <%= "Problem summary: " -%>
16
+ <% when "recovery" -%>
17
+ <%= "Problem summaries finishing: " -%>
18
+ <% end -%>
19
+ <%= states_summary -%>
@@ -127,6 +127,38 @@ module Flapjack
127
127
  end
128
128
  end
129
129
 
130
+ def get_check_details(entity_check)
131
+ sched = entity_check.current_maintenance(:scheduled => true)
132
+ unsched = entity_check.current_maintenance(:unscheduled => true)
133
+ out = ''
134
+
135
+ if sched.nil? && unsched.nil?
136
+ out += "Not in scheduled or unscheduled maintenance.\n"
137
+ else
138
+ if sched.nil?
139
+ out += "Not in scheduled maintenance.\n"
140
+ else
141
+ start = Time.at(sched[:start_time])
142
+ finish = Time.at(sched[:start_time] + sched[:duration])
143
+ remain = time_period_in_words( (finish - current_time).ceil )
144
+ # TODO a simpler time format?
145
+ out += "In scheduled maintenance: #{start} -> #{finish} (#{remain} remaining)\n"
146
+ end
147
+
148
+ if unsched.nil?
149
+ out += "Not in unscheduled maintenance.\n"
150
+ else
151
+ start = Time.at(unsched[:start_time])
152
+ finish = Time.at(unsched[:start_time] + unsched[:duration])
153
+ remain = time_period_in_words( (finish - current_time).ceil )
154
+ # TODO a simpler time format?
155
+ out += "In unscheduled maintenance: #{start} -> #{finish} (#{remain} remaining)\n"
156
+ end
157
+ end
158
+
159
+ out
160
+ end
161
+
130
162
  def interpreter(command_raw)
131
163
  msg = nil
132
164
  action = nil
@@ -190,12 +222,13 @@ module Flapjack
190
222
 
191
223
  when /^help$/i
192
224
  msg = "commands: \n" +
193
- " ACKID <id> <comment> [duration: <time spec>] \n" +
194
- " find entities matching /pattern/ \n" +
195
- " test notifications for <entity>[:<check>] \n" +
196
- " tell me about <entity>[:<check>] \n" +
197
- " identify \n" +
198
- " help \n"
225
+ " ACKID <id> <comment> [duration: <time spec>]\n" +
226
+ " find entities matching /pattern/\n" +
227
+ " find checks[ matching /pattern/] on (<entity>|entities matching /pattern/)\n" +
228
+ " test notifications for <entity>[:<check>]\n" +
229
+ " tell me about <entity>[:<check>]\n" +
230
+ " identify\n" +
231
+ " help\n"
199
232
 
200
233
  when /^identify$/i
201
234
  t = Process.times
@@ -230,43 +263,6 @@ module Flapjack
230
263
 
231
264
  current_time = Time.now
232
265
 
233
- get_details = proc {|entity_check|
234
- sched = entity_check.current_maintenance(:scheduled => true)
235
- unsched = entity_check.current_maintenance(:unscheduled => true)
236
- out = ''
237
-
238
- if check_name.nil?
239
- check = entity_check.check
240
- out += "---\n#{entity_name}:#{check}\n"
241
- end
242
-
243
- if sched.nil? && unsched.nil?
244
- out += "Not in scheduled or unscheduled maintenance.\n"
245
- else
246
- if sched.nil?
247
- out += "Not in scheduled maintenance.\n"
248
- else
249
- start = Time.at(sched[:start_time])
250
- finish = Time.at(sched[:start_time] + sched[:duration])
251
- remain = time_period_in_words( (finish - current_time).ceil )
252
- # TODO a simpler time format?
253
- out += "In scheduled maintenance: #{start} -> #{finish} (#{remain} remaining)\n"
254
- end
255
-
256
- if unsched.nil?
257
- out += "Not in unscheduled maintenance.\n"
258
- else
259
- start = Time.at(unsched[:start_time])
260
- finish = Time.at(unsched[:start_time] + unsched[:duration])
261
- remain = time_period_in_words( (finish - current_time).ceil )
262
- # TODO a simpler time format?
263
- out += "In unscheduled maintenance: #{start} -> #{finish} (#{remain} remaining)\n"
264
- end
265
- end
266
-
267
- out
268
- }
269
-
270
266
  check_names = check_name.nil? ? entity.check_list.sort : [check_name]
271
267
 
272
268
  if check_names.empty?
@@ -275,15 +271,69 @@ module Flapjack
275
271
  check_names.each do |check|
276
272
  entity_check = Flapjack::Data::EntityCheck.for_entity(entity, check, :redis => @redis)
277
273
  next if entity_check.nil?
278
- msg += get_details.call(entity_check)
274
+ msg += "---\n#{entity_name}:#{check}\n" if check_name.nil?
275
+ msg += get_check_details(entity_check)
279
276
  end
280
277
  end
281
278
  else
282
279
  msg = "hmmm, I can't see #{entity_name} in my systems"
283
280
  end
284
281
 
285
- when /^(find )?entities matching\s+\/(.*)\/.*$/i
286
- pattern = $2.chomp.strip
282
+ when /^(?:find )?checks(?:\s+matching\s+\/(.+)\/)?\s+on\s+(?:entities matching\s+\/(.+)\/|([a-z0-9\-\.]+))/i
283
+ check_pattern = $1 ? $1.chomp.strip : nil
284
+ entity_pattern = $2 ? $2.chomp.strip : nil
285
+ entity_name = $3
286
+
287
+ entity_names = if entity_name
288
+ [entity_name]
289
+ elsif entity_pattern
290
+ Flapjack::Data::Entity.find_all_name_matching(entity_pattern, :redis => @redis)
291
+ else
292
+ []
293
+ end
294
+
295
+ msg = ""
296
+
297
+ # hash with entity => check_list, filtered by pattern if required
298
+ entities = entity_names.map {|name|
299
+ Flapjack::Data::Entity.find_by_name(name, :redis => @redis)
300
+ }.compact.inject({}) {|memo, entity|
301
+ memo[entity] = entity.check_list.select {|check_name|
302
+ !check_pattern || (check_name =~ /#{check_pattern}/i)
303
+ }
304
+ memo
305
+ }
306
+
307
+ report_entities = proc {|ents|
308
+ ents.inject('') do |memo, (entity, check_list)|
309
+ if check_list.empty?
310
+ memo += "Entity: #{entity.name} has no checks\n"
311
+ else
312
+ memo += "Entity: #{entity.name}\nChecks: #{check_list.join(', ')}\n"
313
+ end
314
+ memo += "----\n"
315
+ memo
316
+ end
317
+ }
318
+
319
+ case
320
+ when entity_pattern
321
+ if entities.empty?
322
+ msg = "found no entities matching /#{entity_pattern}/"
323
+ else
324
+ msg = "found #{entities.size} entities matching /#{entity_pattern}/ ... \n" +
325
+ report_entities.call(entities)
326
+ end
327
+ when entity_name
328
+ if entities.empty?
329
+ msg = "found no entity for '#{entity_name}'"
330
+ else
331
+ msg = report_entities.call(entities)
332
+ end
333
+ end
334
+
335
+ when /^(?:find )?entities matching\s+\/(.+)\//i
336
+ pattern = $1.chomp.strip
287
337
  entity_list = Flapjack::Data::Entity.find_all_name_matching(pattern, :redis => @redis)
288
338
 
289
339
  if entity_list
@@ -295,7 +345,7 @@ module Flapjack
295
345
  when number_found == 0
296
346
  msg = "found no entities matching /#{pattern}/"
297
347
  when number_found == 1
298
- msg = "found #{number_found} entity matching /#{pattern}/ ... \n"
348
+ msg = "found 1 entity matching /#{pattern}/ ... \n"
299
349
  when number_found > max_showable
300
350
  msg = "showing first #{max_showable} of #{number_found} entities found matching /#{pattern}/\n"
301
351
  else