flapjack 0.7.16 → 0.7.17
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.
- 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
|