flapjack 0.7.16 → 0.7.17
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +9 -0
- data/lib/flapjack/data/entity_check.rb +13 -8
- data/lib/flapjack/data/notification_rule.rb +11 -7
- data/lib/flapjack/gateways/api.rb +28 -655
- data/lib/flapjack/gateways/api/contact_methods.rb +342 -0
- data/lib/flapjack/gateways/api/entity_methods.rb +364 -0
- data/lib/flapjack/gateways/api/entity_presenter.rb +6 -6
- data/lib/flapjack/gateways/api/rack/json_params_parser.rb +26 -0
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/data/entity_check_spec.rb +21 -0
- data/spec/lib/flapjack/data/notification_rule_spec.rb +11 -6
- data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +709 -0
- data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +1 -3
- data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +868 -0
- data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +13 -12
- data/spec/lib/flapjack/gateways/api_spec.rb +1 -1520
- data/tmp/redis_find_spurious_unknown_states.rb +52 -0
- metadata +12 -6
- data/.rbenv-version +0 -1
- data/tmp/redis_delete_all_keys.rb +0 -11
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## Flapjack Changelog
|
2
2
|
|
3
|
+
# 0.7.17 - 2013-07-04
|
4
|
+
- Feature: split API methods into two separate files, also specs gh-215 (@ali-graham)
|
5
|
+
- Feature: unlock ruby version, build with Ruby 2 in travis gh-237 (@ali-graham)
|
6
|
+
- Feature: include notification rule validation error details in api add, update functions gh-184 (@ali-graham)
|
7
|
+
- Feature: API: delete scheduled maintenance should return an error if no start_time parameter is passed gh-240 (@ali-graham)
|
8
|
+
- Bug: entity name in bulk status api response is incorrect gh-233 (@jessereynolds)
|
9
|
+
- Bug: non-changing checks creating state-change records gh-235 (@ali-graham)
|
10
|
+
- Bug: posting scheduled maintenance in new api format throwing 500 gh-239 (@jessereynolds)
|
11
|
+
|
3
12
|
# 0.7.16 - 2013-06-27
|
4
13
|
- Bug: errors accessing API gh-231 (@ali-graham)
|
5
14
|
|
@@ -221,16 +221,21 @@ module Flapjack
|
|
221
221
|
# FIXME: Iterate through a list of tags associated with an entity:check pair, and update counters
|
222
222
|
@redis.zrem("failed_checks:client:#{client}", @key) if client
|
223
223
|
end
|
224
|
-
end
|
225
224
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
225
|
+
# Retain event data for entity:check pair
|
226
|
+
@redis.rpush("#{@key}:states", timestamp)
|
227
|
+
@redis.set("#{@key}:#{timestamp}:state", new_state)
|
228
|
+
@redis.set("#{@key}:#{timestamp}:summary", summary) if summary
|
229
|
+
@redis.set("#{@key}:#{timestamp}:details", details) if details
|
230
|
+
@redis.set("#{@key}:#{timestamp}:count", count) if count
|
231
|
+
|
232
|
+
@redis.zadd("#{@key}:sorted_state_timestamps", timestamp, timestamp)
|
233
|
+
end
|
232
234
|
|
233
|
-
|
235
|
+
# Even if this isn't a state change, we need to update the current state
|
236
|
+
# hash summary and details (as they may have changed)
|
237
|
+
@redis.hset("check:#{@key}", 'summary', (summary || ''))
|
238
|
+
@redis.hset("check:#{@key}", 'details', (details || ''))
|
234
239
|
end
|
235
240
|
|
236
241
|
def last_update
|
@@ -37,15 +37,17 @@ module Flapjack
|
|
37
37
|
raise "Redis connection not set" unless redis = options[:redis]
|
38
38
|
|
39
39
|
rule_id = SecureRandom.uuid
|
40
|
-
self.add_or_update(rule_data.merge(:id => rule_id), options)
|
40
|
+
errors = self.add_or_update(rule_data.merge(:id => rule_id), options)
|
41
|
+
return errors unless errors.nil? || errors.empty?
|
41
42
|
self.find_by_id(rule_id, :redis => redis)
|
42
43
|
end
|
43
44
|
|
44
45
|
def update(rule_data, opts = {})
|
45
|
-
|
46
|
+
errors = self.class.add_or_update({:contact_id => @contact_id}.merge(rule_data.merge(:id => @id)),
|
46
47
|
:redis => @redis, :logger => opts[:logger])
|
48
|
+
return errors unless errors.nil? || errors.empty?
|
47
49
|
refresh
|
48
|
-
|
50
|
+
nil
|
49
51
|
end
|
50
52
|
|
51
53
|
# NB: ice_cube doesn't have much rule data validation, and has
|
@@ -119,7 +121,9 @@ module Flapjack
|
|
119
121
|
rule_data[:warning_blackhole] = rule_data[:warning_blackhole] || false
|
120
122
|
rule_data[:critical_blackhole] = rule_data[:critical_blackhole] || false
|
121
123
|
|
122
|
-
|
124
|
+
errors = self.validate_data(rule_data, options)
|
125
|
+
|
126
|
+
return errors unless errors.nil? || errors.empty?
|
123
127
|
|
124
128
|
# whitelisting fields, rather than passing through submitted data directly
|
125
129
|
json_rule_data = {
|
@@ -139,7 +143,7 @@ module Flapjack
|
|
139
143
|
json_rule_data[:id])
|
140
144
|
redis.hmset("notification_rule:#{json_rule_data[:id]}",
|
141
145
|
*json_rule_data.flatten)
|
142
|
-
|
146
|
+
nil
|
143
147
|
end
|
144
148
|
|
145
149
|
def self.prepare_time_restriction(time_restriction, timezone = nil)
|
@@ -266,14 +270,14 @@ module Flapjack
|
|
266
270
|
ret
|
267
271
|
}
|
268
272
|
|
269
|
-
return
|
273
|
+
return if errors.empty?
|
270
274
|
|
271
275
|
if logger = options[:logger]
|
272
276
|
error_str = errors.join(", ")
|
273
277
|
logger.info "validation error: #{error_str}"
|
274
278
|
logger.debug "rule failing validations: #{d.inspect}"
|
275
279
|
end
|
276
|
-
|
280
|
+
errors
|
277
281
|
end
|
278
282
|
|
279
283
|
def refresh
|
@@ -11,39 +11,13 @@ require 'time'
|
|
11
11
|
require 'rack/fiber_pool'
|
12
12
|
require 'sinatra/base'
|
13
13
|
|
14
|
-
require 'flapjack/data/contact'
|
15
|
-
require 'flapjack/data/entity'
|
16
|
-
require 'flapjack/data/entity_check'
|
17
|
-
|
18
|
-
require 'flapjack/gateways/api/entity_presenter'
|
19
|
-
require 'flapjack/gateways/api/entity_check_presenter'
|
20
14
|
require 'flapjack/rack_logger'
|
21
15
|
require 'flapjack/redis_pool'
|
22
16
|
|
23
|
-
|
24
|
-
# TODO move to its own file
|
25
|
-
module Rack
|
26
|
-
class JsonParamsParser < Struct.new(:app)
|
27
|
-
def call(env)
|
28
|
-
if env['rack.input'] and not input_parsed?(env) and type_match?(env)
|
29
|
-
env['rack.request.form_input'] = env['rack.input']
|
30
|
-
data = env['rack.input'].read
|
31
|
-
env['rack.input'].rewind
|
32
|
-
env['rack.request.form_hash'] = data.empty? ? {} : JSON.parse(data)
|
33
|
-
end
|
34
|
-
app.call(env)
|
35
|
-
end
|
17
|
+
require 'flapjack/gateways/api/rack/json_params_parser'
|
36
18
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def type_match? env
|
42
|
-
type = env['CONTENT_TYPE'] and
|
43
|
-
type.split(/\s*[;,]\s*/, 2).first.downcase == 'application/json'
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
19
|
+
require 'flapjack/gateways/api/contact_methods'
|
20
|
+
require 'flapjack/gateways/api/entity_methods'
|
47
21
|
|
48
22
|
module Flapjack
|
49
23
|
|
@@ -51,48 +25,20 @@ module Flapjack
|
|
51
25
|
|
52
26
|
class API < Sinatra::Base
|
53
27
|
|
54
|
-
# used for backwards-compatible route matching below
|
55
|
-
ENTITY_CHECK_FRAGMENT = '(?:/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(.+))?)?'
|
56
|
-
|
57
|
-
class EntityCheckNotFound < RuntimeError
|
58
|
-
attr_reader :entity, :check
|
59
|
-
def initialize(entity, check)
|
60
|
-
@entity = entity
|
61
|
-
@check = check
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class EntityNotFound < RuntimeError
|
66
|
-
attr_reader :entity
|
67
|
-
def initialize(entity)
|
68
|
-
@entity = entity
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
class ContactNotFound < RuntimeError
|
73
|
-
attr_reader :contact_id
|
74
|
-
def initialize(contact_id)
|
75
|
-
@contact_id = contact_id
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class NotificationRuleNotFound < RuntimeError
|
80
|
-
attr_reader :rule_id
|
81
|
-
def initialize(rule_id)
|
82
|
-
@rule_id = rule_id
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
28
|
include Flapjack::Utility
|
87
29
|
|
88
30
|
set :show_exceptions, false
|
89
31
|
|
90
|
-
rescue_exception = Proc.new { |env, exception|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
}
|
95
|
-
use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
|
32
|
+
#rescue_exception = Proc.new { |env, exception|
|
33
|
+
# @logger.error exception.message
|
34
|
+
# @logger.error exception.backtrace.join("\n")
|
35
|
+
# [503, {}, {:errors => [exception.message]}.to_json]
|
36
|
+
#}
|
37
|
+
#use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
|
38
|
+
#
|
39
|
+
# FIXME: not sure why the above isn't working, had to add a general
|
40
|
+
# error handler later in this file
|
41
|
+
use Rack::FiberPool, :size => 25
|
96
42
|
|
97
43
|
use Rack::MethodOverride
|
98
44
|
use Rack::JsonParamsParser
|
@@ -118,496 +64,21 @@ module Flapjack
|
|
118
64
|
self.class.instance_variable_get('@logger')
|
119
65
|
end
|
120
66
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
ret.to_json
|
128
|
-
end
|
129
|
-
|
130
|
-
get '/checks/:entity' do
|
131
|
-
content_type :json
|
132
|
-
entity = find_entity(params[:entity])
|
133
|
-
entity.check_list.to_json
|
134
|
-
end
|
135
|
-
|
136
|
-
get %r{/status#{ENTITY_CHECK_FRAGMENT}} do
|
137
|
-
content_type :json
|
138
|
-
|
139
|
-
captures = params[:captures] || []
|
140
|
-
entity_name = captures[0]
|
141
|
-
check = captures[1]
|
142
|
-
|
143
|
-
entities, checks = entities_and_checks(entity_name, check)
|
144
|
-
|
145
|
-
results = present_api_results(entities, checks, 'status') {|presenter|
|
146
|
-
presenter.status
|
147
|
-
}
|
148
|
-
|
149
|
-
if entity_name
|
150
|
-
# compatible with previous data format
|
151
|
-
results = results.collect {|status_h| status_h[:status]}
|
152
|
-
(check ? results.first : results).to_json
|
153
|
-
else
|
154
|
-
# new and improved data format which reflects the request param structure
|
155
|
-
results.to_json
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
get %r{/((?:outages|(?:un)?scheduled_maintenances|downtime))#{ENTITY_CHECK_FRAGMENT}} do
|
160
|
-
action = params[:captures][0].to_sym
|
161
|
-
entity_name = params[:captures][1]
|
162
|
-
check = params[:captures][2]
|
163
|
-
|
164
|
-
entities, checks = entities_and_checks(entity_name, check)
|
165
|
-
|
166
|
-
start_time = validate_and_parsetime(params[:start_time])
|
167
|
-
end_time = validate_and_parsetime(params[:end_time])
|
168
|
-
|
169
|
-
results = present_api_results(entities, checks, action) {|presenter|
|
170
|
-
presenter.send(action, start_time, end_time)
|
171
|
-
}
|
172
|
-
|
173
|
-
if check
|
174
|
-
# compatible with previous data format
|
175
|
-
results.first[action].to_json
|
176
|
-
elsif entity_name
|
177
|
-
# compatible with previous data format
|
178
|
-
rename = {:unscheduled_maintenances => :unscheduled_maintenance,
|
179
|
-
:scheduled_maintenances => :scheduled_maintenance}
|
180
|
-
drop = [:entity]
|
181
|
-
results.collect{|r|
|
182
|
-
r.inject({}) {|memo, (k, v)|
|
183
|
-
if new_k = rename[k]
|
184
|
-
memo[new_k] = v
|
185
|
-
elsif !drop.include?(k)
|
186
|
-
memo[k] = v
|
187
|
-
end
|
188
|
-
memo
|
189
|
-
}
|
190
|
-
}.to_json
|
191
|
-
else
|
192
|
-
# new and improved data format which reflects the request param structure
|
193
|
-
results.to_json
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
# create a scheduled maintenance period for a check on an entity
|
198
|
-
post %r{/scheduled_maintenances#{ENTITY_CHECK_FRAGMENT}} do
|
199
|
-
entity_name = params[:captures][0]
|
200
|
-
check = params[:captures][1]
|
201
|
-
|
202
|
-
entities, checks = entities_and_checks(entity_name, check)
|
203
|
-
|
204
|
-
start_time = validate_and_parsetime(params[:start_time])
|
205
|
-
|
206
|
-
act_proc = proc {|entity_check|
|
207
|
-
entity_check.create_scheduled_maintenance(:start_time => start_time,
|
208
|
-
:duration => params[:duration].to_i, :summary => params[:summary])
|
209
|
-
}
|
210
|
-
|
211
|
-
bulk_api_check_action(entities, checks, act_proc)
|
212
|
-
status 204
|
213
|
-
end
|
214
|
-
|
215
|
-
# create an acknowledgement for a service on an entity
|
216
|
-
# NB currently, this does not acknowledge a specific failure event, just
|
217
|
-
# the entity-check as a whole
|
218
|
-
post %r{/acknowledgements#{ENTITY_CHECK_FRAGMENT}} do
|
219
|
-
captures = params[:captures] || []
|
220
|
-
entity_name = captures[0]
|
221
|
-
check = captures[1]
|
222
|
-
|
223
|
-
entities, checks = entities_and_checks(entity_name, check)
|
224
|
-
|
225
|
-
dur = params[:duration] ? params[:duration].to_i : nil
|
226
|
-
duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
|
227
|
-
summary = params[:summary]
|
228
|
-
|
229
|
-
opts = {'duration' => duration}
|
230
|
-
opts['summary'] = summary if summary
|
231
|
-
|
232
|
-
act_proc = proc {|entity_check|
|
233
|
-
Flapjack::Data::Event.create_acknowledgement(
|
234
|
-
entity_check.entity_name, entity_check.check,
|
235
|
-
:summary => params[:summary],
|
236
|
-
:duration => duration,
|
237
|
-
:redis => redis)
|
238
|
-
}
|
239
|
-
|
240
|
-
bulk_api_check_action(entities, checks, act_proc)
|
241
|
-
status 204
|
242
|
-
end
|
243
|
-
|
244
|
-
delete %r{/((?:un)?scheduled_maintenances)} do
|
245
|
-
action = params[:captures][0]
|
246
|
-
|
247
|
-
# no backwards-compatible mode here, it's a new method
|
248
|
-
entities, checks = entities_and_checks(nil, nil)
|
249
|
-
|
250
|
-
act_proc = case action
|
251
|
-
when 'scheduled_maintenances'
|
252
|
-
start_time = validate_and_parsetime(params[:start_time])
|
253
|
-
opts = {}
|
254
|
-
opts[:start_time] = start_time.to_i if start_time
|
255
|
-
proc {|entity_check| entity_check.delete_scheduled_maintenance(opts) }
|
256
|
-
when 'unscheduled_maintenances'
|
257
|
-
end_time = validate_and_parsetime(params[:end_time])
|
258
|
-
opts = {}
|
259
|
-
opts[:end_time] = end_time.to_i if end_time
|
260
|
-
proc {|entity_check| entity_check.end_unscheduled_maintenance(opts) }
|
261
|
-
end
|
262
|
-
|
263
|
-
bulk_api_check_action(entities, checks, act_proc)
|
264
|
-
status 204
|
265
|
-
end
|
266
|
-
|
267
|
-
post %r{/test_notifications#{ENTITY_CHECK_FRAGMENT}} do
|
268
|
-
captures = params[:captures] || []
|
269
|
-
entity_name = captures[0]
|
270
|
-
check = captures[1]
|
271
|
-
|
272
|
-
entities, checks = entities_and_checks(entity_name, check)
|
273
|
-
|
274
|
-
act_proc = proc {|entity_check|
|
275
|
-
summary = params[:summary] ||
|
276
|
-
"Testing notifications to all contacts interested in entity #{entity_check.entity.name}"
|
277
|
-
Flapjack::Data::Event.test_notifications(
|
278
|
-
entity_check.entity_name, entity_check.check,
|
279
|
-
:summary => summary,
|
280
|
-
:redis => redis)
|
281
|
-
}
|
282
|
-
|
283
|
-
bulk_api_check_action(entities, checks, act_proc)
|
284
|
-
status 204
|
285
|
-
end
|
286
|
-
|
287
|
-
post '/entities' do
|
288
|
-
pass unless 'application/json'.eql?(request.content_type)
|
289
|
-
|
290
|
-
errors = []
|
291
|
-
ret = nil
|
292
|
-
|
293
|
-
# FIXME should scan for invalid records before making any changes, fail early
|
294
|
-
|
295
|
-
entities = params[:entities]
|
296
|
-
unless entities
|
297
|
-
logger.debug("no entities object found in the following supplied JSON:")
|
298
|
-
logger.debug(request.body)
|
299
|
-
return err(403, "No entities object received")
|
300
|
-
end
|
301
|
-
return err(403, "The received entities object is not an Enumerable") unless entities.is_a?(Enumerable)
|
302
|
-
return err(403, "Entity with a nil id detected") unless entities.any? {|e| !e['id'].nil?}
|
303
|
-
|
304
|
-
entities.each do |entity|
|
305
|
-
unless entity['id']
|
306
|
-
errors << "Entity not imported as it has no id: #{entity.inspect}"
|
307
|
-
next
|
308
|
-
end
|
309
|
-
Flapjack::Data::Entity.add(entity, :redis => redis)
|
310
|
-
end
|
311
|
-
errors.empty? ? 204 : err(403, *errors)
|
312
|
-
end
|
313
|
-
|
314
|
-
post '/contacts' do
|
315
|
-
pass unless 'application/json'.eql?(request.content_type)
|
316
|
-
content_type :json
|
317
|
-
|
318
|
-
errors = []
|
319
|
-
|
320
|
-
contacts_data = params[:contacts]
|
321
|
-
if contacts_data.nil? || !contacts_data.is_a?(Enumerable)
|
322
|
-
errors << "No valid contacts were submitted"
|
323
|
-
else
|
324
|
-
# stringifying as integer string params are automatically integered,
|
325
|
-
# but our redis ids are strings
|
326
|
-
contacts_data_ids = contacts_data.reject {|c| c['id'].nil? }.
|
327
|
-
map {|co| co['id'].to_s }
|
328
|
-
|
329
|
-
if contacts_data_ids.empty?
|
330
|
-
errors << "No contacts with IDs were submitted"
|
331
|
-
else
|
332
|
-
contacts = Flapjack::Data::Contact.all(:redis => redis)
|
333
|
-
contacts_h = hashify(*contacts) {|c| [c.id, c] }
|
334
|
-
contacts_ids = contacts_h.keys
|
335
|
-
|
336
|
-
# delete contacts not found in the bulk list
|
337
|
-
(contacts_ids - contacts_data_ids).each do |contact_to_delete_id|
|
338
|
-
contact_to_delete = contacts.detect {|c| c.id == contact_to_delete_id }
|
339
|
-
contact_to_delete.delete!
|
340
|
-
end
|
341
|
-
|
342
|
-
# add or update contacts found in the bulk list
|
343
|
-
contacts_data.reject {|cd| cd['id'].nil? }.each do |contact_data|
|
344
|
-
if contacts_ids.include?(contact_data['id'].to_s)
|
345
|
-
contacts_h[contact_data['id'].to_s].update(contact_data)
|
346
|
-
else
|
347
|
-
Flapjack::Data::Contact.add(contact_data, :redis => redis)
|
348
|
-
end
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
errors.empty? ? 204 : err(403, *errors)
|
353
|
-
end
|
354
|
-
|
355
|
-
# Returns all the contacts
|
356
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts
|
357
|
-
get '/contacts' do
|
358
|
-
content_type :json
|
359
|
-
|
360
|
-
Flapjack::Data::Contact.all(:redis => redis).to_json
|
361
|
-
end
|
362
|
-
|
363
|
-
# Returns the core information about the specified contact
|
364
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id
|
365
|
-
get '/contacts/:contact_id' do
|
366
|
-
content_type :json
|
367
|
-
|
368
|
-
contact = find_contact(params[:contact_id])
|
369
|
-
contact.to_json
|
370
|
-
end
|
371
|
-
|
372
|
-
# Lists this contact's notification rules
|
373
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_notification_rules
|
374
|
-
get '/contacts/:contact_id/notification_rules' do
|
375
|
-
content_type :json
|
376
|
-
|
377
|
-
contact = find_contact(params[:contact_id])
|
378
|
-
contact.notification_rules.to_json
|
379
|
-
end
|
380
|
-
|
381
|
-
# Get the specified notification rule for this user
|
382
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_notification_rules_id
|
383
|
-
get '/notification_rules/:id' do
|
384
|
-
content_type :json
|
385
|
-
|
386
|
-
rule = find_rule(params[:id])
|
387
|
-
rule.to_json
|
388
|
-
end
|
389
|
-
|
390
|
-
# Creates a notification rule for a contact
|
391
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-post_contacts_id_notification_rules
|
392
|
-
post '/notification_rules' do
|
393
|
-
content_type :json
|
394
|
-
|
395
|
-
if params[:id]
|
396
|
-
halt err(403, "post cannot be used for update, do a put instead")
|
397
|
-
end
|
398
|
-
|
399
|
-
logger.debug("post /notification_rules data: ")
|
400
|
-
logger.debug(params.inspect)
|
401
|
-
|
402
|
-
contact = find_contact(params[:contact_id])
|
403
|
-
|
404
|
-
rule_data = hashify(:entities, :entity_tags,
|
405
|
-
:warning_media, :critical_media, :time_restrictions,
|
406
|
-
:warning_blackhole, :critical_blackhole) {|k| [k, params[k]]}
|
407
|
-
|
408
|
-
unless rule = contact.add_notification_rule(rule_data, :logger => logger)
|
409
|
-
halt err(403, "invalid notification rule data")
|
410
|
-
end
|
411
|
-
rule.to_json
|
412
|
-
end
|
413
|
-
|
414
|
-
# Updates a notification rule
|
415
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
416
|
-
put('/notification_rules/:id') do
|
417
|
-
content_type :json
|
418
|
-
|
419
|
-
logger.debug("put /notification_rules/#{params[:id]} data: ")
|
420
|
-
logger.debug(params.inspect)
|
421
|
-
|
422
|
-
rule = find_rule(params[:id])
|
423
|
-
contact = find_contact(rule.contact_id)
|
424
|
-
|
425
|
-
rule_data = hashify(:entities, :entity_tags,
|
426
|
-
:warning_media, :critical_media, :time_restrictions,
|
427
|
-
:warning_blackhole, :critical_blackhole) {|k| [k, params[k]]}
|
428
|
-
|
429
|
-
unless rule.update(rule_data, :logger => logger)
|
430
|
-
halt err(403, "invalid notification rule data")
|
431
|
-
end
|
432
|
-
rule.to_json
|
433
|
-
end
|
434
|
-
|
435
|
-
# Deletes a notification rule
|
436
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
437
|
-
delete('/notification_rules/:id') do
|
438
|
-
logger.debug("delete /notification_rules/#{params[:id]}")
|
439
|
-
rule = find_rule(params[:id])
|
440
|
-
logger.debug("rule to delete: #{rule.inspect}, contact_id: #{rule.contact_id}")
|
441
|
-
contact = find_contact(rule.contact_id)
|
442
|
-
contact.delete_notification_rule(rule)
|
443
|
-
status 204
|
444
|
-
end
|
445
|
-
|
446
|
-
# Returns the media of a contact
|
447
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media
|
448
|
-
get '/contacts/:contact_id/media' do
|
449
|
-
content_type :json
|
450
|
-
|
451
|
-
contact = find_contact(params[:contact_id])
|
452
|
-
|
453
|
-
media = contact.media
|
454
|
-
media_intervals = contact.media_intervals
|
455
|
-
media_addr_int = hashify(*media.keys) {|k|
|
456
|
-
[k, {'address' => media[k],
|
457
|
-
'interval' => media_intervals[k] }]
|
458
|
-
}
|
459
|
-
media_addr_int.to_json
|
460
|
-
end
|
461
|
-
|
462
|
-
# Returns the specified media of a contact
|
463
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media_media
|
464
|
-
get('/contacts/:contact_id/media/:id') do
|
465
|
-
content_type :json
|
466
|
-
|
467
|
-
contact = find_contact(params[:contact_id])
|
468
|
-
media = contact.media[params[:id]]
|
469
|
-
if media.nil?
|
470
|
-
halt err(403, "no #{params[:id]} for contact '#{params[:contact_id]}'")
|
471
|
-
end
|
472
|
-
interval = contact.media_intervals[params[:id]]
|
473
|
-
if interval.nil?
|
474
|
-
halt err(403, "no #{params[:id]} interval for contact '#{params[:contact_id]}'")
|
475
|
-
end
|
476
|
-
{'address' => media,
|
477
|
-
'interval' => interval}.to_json
|
478
|
-
end
|
479
|
-
|
480
|
-
# Creates or updates a media of a contact
|
481
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_media_media
|
482
|
-
put('/contacts/:contact_id/media/:id') do
|
483
|
-
content_type :json
|
484
|
-
|
485
|
-
contact = find_contact(params[:contact_id])
|
486
|
-
errors = []
|
487
|
-
if params[:address].nil?
|
488
|
-
errors << "no address for '#{params[:id]}' media"
|
489
|
-
end
|
490
|
-
|
491
|
-
halt err(403, *errors) unless errors.empty?
|
492
|
-
|
493
|
-
contact.set_address_for_media(params[:id], params[:address])
|
494
|
-
contact.set_interval_for_media(params[:id], params[:interval])
|
495
|
-
|
496
|
-
{'address' => contact.media[params[:id]],
|
497
|
-
'interval' => contact.media_intervals[params[:id]]}.to_json
|
498
|
-
end
|
499
|
-
|
500
|
-
# delete a media of a contact
|
501
|
-
delete('/contacts/:contact_id/media/:id') do
|
502
|
-
contact = find_contact(params[:contact_id])
|
503
|
-
contact.remove_media(params[:id])
|
504
|
-
status 204
|
505
|
-
end
|
506
|
-
|
507
|
-
# Returns the timezone of a contact
|
508
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_timezone
|
509
|
-
get('/contacts/:contact_id/timezone') do
|
510
|
-
content_type :json
|
511
|
-
|
512
|
-
contact = find_contact(params[:contact_id])
|
513
|
-
contact.timezone.name.to_json
|
514
|
-
end
|
515
|
-
|
516
|
-
# Sets the timezone of a contact
|
517
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
518
|
-
put('/contacts/:contact_id/timezone') do
|
519
|
-
content_type :json
|
520
|
-
|
521
|
-
contact = find_contact(params[:contact_id])
|
522
|
-
contact.timezone = params[:timezone]
|
523
|
-
contact.timezone.name.to_json
|
524
|
-
end
|
525
|
-
|
526
|
-
# Removes the timezone of a contact
|
527
|
-
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
528
|
-
delete('/contacts/:contact_id/timezone') do
|
529
|
-
contact = find_contact(params[:contact_id])
|
530
|
-
contact.timezone = nil
|
531
|
-
status 204
|
532
|
-
end
|
533
|
-
|
534
|
-
post '/contacts/:contact_id/tags' do
|
535
|
-
content_type :json
|
536
|
-
|
537
|
-
tags = find_tags(params[:tag])
|
538
|
-
contact = find_contact(params[:contact_id])
|
539
|
-
contact.add_tags(*tags)
|
540
|
-
contact.tags.to_json
|
541
|
-
end
|
542
|
-
|
543
|
-
post '/contacts/:contact_id/entity_tags' do
|
544
|
-
content_type :json
|
545
|
-
contact = find_contact(params[:contact_id])
|
546
|
-
contact.entities.map {|e| e[:entity]}.each do |entity|
|
547
|
-
next unless tags = params[:entity][entity.name]
|
548
|
-
entity.add_tags(*tags)
|
549
|
-
end
|
550
|
-
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
551
|
-
[et[:entity].name, et[:tags]]
|
552
|
-
}
|
553
|
-
contact_ent_tag.to_json
|
554
|
-
end
|
555
|
-
|
556
|
-
delete '/contacts/:contact_id/tags' do
|
557
|
-
tags = find_tags(params[:tag])
|
558
|
-
contact = find_contact(params[:contact_id])
|
559
|
-
contact.delete_tags(*tags)
|
560
|
-
status 204
|
561
|
-
end
|
562
|
-
|
563
|
-
delete '/contacts/:contact_id/entity_tags' do
|
564
|
-
contact = find_contact(params[:contact_id])
|
565
|
-
contact.entities.map {|e| e[:entity]}.each do |entity|
|
566
|
-
next unless tags = params[:entity][entity.name]
|
567
|
-
entity.delete_tags(*tags)
|
568
|
-
end
|
569
|
-
status 204
|
570
|
-
end
|
571
|
-
|
572
|
-
get '/contacts/:contact_id/tags' do
|
573
|
-
content_type :json
|
574
|
-
|
575
|
-
contact = find_contact(params[:contact_id])
|
576
|
-
contact.tags.to_json
|
577
|
-
end
|
578
|
-
|
579
|
-
get '/contacts/:contact_id/entity_tags' do
|
580
|
-
content_type :json
|
581
|
-
|
582
|
-
contact = find_contact(params[:contact_id])
|
583
|
-
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
584
|
-
[et[:entity].name, et[:tags]]
|
585
|
-
}
|
586
|
-
contact_ent_tag.to_json
|
587
|
-
end
|
588
|
-
|
589
|
-
post '/entities/:entity/tags' do
|
590
|
-
content_type :json
|
591
|
-
|
592
|
-
tags = find_tags(params[:tag])
|
593
|
-
entity = find_entity(params[:entity])
|
594
|
-
entity.add_tags(*tags)
|
595
|
-
entity.tags.to_json
|
67
|
+
before do
|
68
|
+
input = env['rack.input'].read
|
69
|
+
input_short = input.gsub(/\n/, '').gsub(/\s+/, ' ')
|
70
|
+
logger.info("#{request.request_method} #{request.path_info}#{request.query_string} #{input_short[0..80]}")
|
71
|
+
logger.debug("#{request.request_method} #{request.path_info}#{request.query_string} #{input}")
|
72
|
+
env['rack.input'].rewind
|
596
73
|
end
|
597
74
|
|
598
|
-
|
599
|
-
|
600
|
-
entity = find_entity(params[:entity])
|
601
|
-
entity.delete_tags(*tags)
|
602
|
-
status 204
|
75
|
+
after do
|
76
|
+
logger.debug("Returning #{response.status} for #{request.request_method} #{request.path_info}#{request.query_string}")
|
603
77
|
end
|
604
78
|
|
605
|
-
|
606
|
-
content_type :json
|
79
|
+
register Flapjack::Gateways::API::EntityMethods
|
607
80
|
|
608
|
-
|
609
|
-
entity.tags.to_json
|
610
|
-
end
|
81
|
+
register Flapjack::Gateways::API::ContactMethods
|
611
82
|
|
612
83
|
not_found do
|
613
84
|
logger.debug("in not_found :-(")
|
@@ -634,6 +105,11 @@ module Flapjack
|
|
634
105
|
err(403, "could not find entity check '#{e.check}'")
|
635
106
|
end
|
636
107
|
|
108
|
+
error do
|
109
|
+
e = env['sinatra.error']
|
110
|
+
err(response.status, "#{e.class} - #{e.message}")
|
111
|
+
end
|
112
|
+
|
637
113
|
private
|
638
114
|
|
639
115
|
def err(status, *msg)
|
@@ -642,109 +118,6 @@ module Flapjack
|
|
642
118
|
[status, {}, {:errors => msg}.to_json]
|
643
119
|
end
|
644
120
|
|
645
|
-
def find_contact(contact_id)
|
646
|
-
contact = Flapjack::Data::Contact.find_by_id(contact_id, :logger => logger, :redis => redis)
|
647
|
-
raise Flapjack::Gateways::API::ContactNotFound.new(contact_id) if contact.nil?
|
648
|
-
contact
|
649
|
-
end
|
650
|
-
|
651
|
-
def find_rule(rule_id)
|
652
|
-
rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :logger => logger, :redis => redis)
|
653
|
-
raise Flapjack::Gateways::API::NotificationRuleNotFound.new(rule_id) if rule.nil?
|
654
|
-
rule
|
655
|
-
end
|
656
|
-
|
657
|
-
def find_entity(entity_name)
|
658
|
-
entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
|
659
|
-
raise Flapjack::Gateways::API::EntityNotFound.new(entity_name) if entity.nil?
|
660
|
-
entity
|
661
|
-
end
|
662
|
-
|
663
|
-
def find_entity_check(entity, check)
|
664
|
-
entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
|
665
|
-
check, :redis => redis)
|
666
|
-
raise Flapjack::Gateways::API::EntityCheckNotFound.new(entity, check) if entity_check.nil?
|
667
|
-
entity_check
|
668
|
-
end
|
669
|
-
|
670
|
-
def entities_and_checks(entity_name, check)
|
671
|
-
if entity_name
|
672
|
-
# backwards-compatible, single entity or entity&check from route
|
673
|
-
entities = check ? nil : [entity_name]
|
674
|
-
checks = check ? {entity_name => check} : nil
|
675
|
-
else
|
676
|
-
# new and improved bulk API queries
|
677
|
-
entities = params[:entity]
|
678
|
-
checks = params[:check]
|
679
|
-
entities = [entities] unless entities.nil? || entities.is_a?(Array)
|
680
|
-
# TODO err if checks isn't a Hash (similar rules as in flapjack-diner)
|
681
|
-
end
|
682
|
-
[entities, checks]
|
683
|
-
end
|
684
|
-
|
685
|
-
def bulk_api_check_action(entities, entity_checks, action, params = {})
|
686
|
-
unless entities.nil? || entities.empty?
|
687
|
-
entities.each do |entity_name|
|
688
|
-
entity = find_entity(entity_name)
|
689
|
-
checks = entity.check_list.sort
|
690
|
-
checks.each do |check|
|
691
|
-
action.call( find_entity_check(entity, check) )
|
692
|
-
end
|
693
|
-
end
|
694
|
-
end
|
695
|
-
|
696
|
-
unless entity_checks.nil? || entity_checks.empty?
|
697
|
-
entity_checks.each_pair do |entity_name, checks|
|
698
|
-
entity = find_entity(entity_name)
|
699
|
-
checks = [checks] unless checks.is_a?(Array)
|
700
|
-
checks.each do |check|
|
701
|
-
action.call( find_entity_check(entity, check) )
|
702
|
-
end
|
703
|
-
end
|
704
|
-
end
|
705
|
-
end
|
706
|
-
|
707
|
-
def present_api_results(entities, entity_checks, result_type, &block)
|
708
|
-
result = []
|
709
|
-
|
710
|
-
unless entities.nil? || entities.empty?
|
711
|
-
result += entities.collect {|entity_name|
|
712
|
-
entity = find_entity(entity_name)
|
713
|
-
yield(Flapjack::Gateways::API::EntityPresenter.new(entity, :redis => redis))
|
714
|
-
}.flatten(1)
|
715
|
-
end
|
716
|
-
|
717
|
-
unless entity_checks.nil? || entity_checks.empty?
|
718
|
-
result += entity_checks.inject([]) {|memo, (entity_name, checks)|
|
719
|
-
checks = [checks] unless checks.is_a?(Array)
|
720
|
-
entity = find_entity(entity_name)
|
721
|
-
memo += checks.collect {|check|
|
722
|
-
entity_check = find_entity_check(entity, check)
|
723
|
-
{:entity => entity_name,
|
724
|
-
:check => check,
|
725
|
-
result_type.to_sym => yield(Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check))
|
726
|
-
}
|
727
|
-
}
|
728
|
-
}.flatten(1)
|
729
|
-
end
|
730
|
-
|
731
|
-
result
|
732
|
-
end
|
733
|
-
|
734
|
-
def find_tags(tags)
|
735
|
-
halt err(403, "no tags") if tags.nil? || tags.empty?
|
736
|
-
tags
|
737
|
-
end
|
738
|
-
|
739
|
-
# NB: casts to UTC before converting to a timestamp
|
740
|
-
def validate_and_parsetime(value)
|
741
|
-
return unless value
|
742
|
-
Time.iso8601(value).getutc.to_i
|
743
|
-
rescue ArgumentError => e
|
744
|
-
logger.error "Couldn't parse time from '#{value}'"
|
745
|
-
nil
|
746
|
-
end
|
747
|
-
|
748
121
|
end
|
749
122
|
|
750
123
|
end
|