flapjack 0.7.27 → 0.7.28

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 (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