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 CHANGED
@@ -23,4 +23,7 @@ tmp/pids/*
23
23
  tmp/spec*
24
24
  tmp/profiles
25
25
  flapjack-*.gem
26
+ .rbenv-version
27
+ .ruby-version
28
+ .rvmrc
26
29
 
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - "1.9.3"
4
+ - "2.0.0"
4
5
  gemfile:
5
6
  - Gemfile
6
7
  services:
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
- # Retain event data for entity:check pair
227
- @redis.rpush("#{@key}:states", timestamp)
228
- @redis.set("#{@key}:#{timestamp}:state", new_state)
229
- @redis.set("#{@key}:#{timestamp}:summary", summary) if summary
230
- @redis.set("#{@key}:#{timestamp}:details", details) if details
231
- @redis.set("#{@key}:#{timestamp}:count", count) if count
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
- @redis.zadd("#{@key}:sorted_state_timestamps", timestamp, timestamp)
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
- return false unless self.class.add_or_update({:contact_id => @contact_id}.merge(rule_data.merge(:id => @id)),
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
- true
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
- return unless self.validate_data(rule_data, options)
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
- true
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 true if errors.empty?
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
- false
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
- # from https://github.com/sinatra/sinatra/issues/501
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
- def input_parsed? env
38
- env['rack.request.form_input'].eql? env['rack.input']
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
- @logger.error exception.message
92
- @logger.error exception.backtrace.join("\n")
93
- [503, {}, {:errors => [exception.message]}.to_json]
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
- get '/entities' do
122
- content_type :json
123
- ret = Flapjack::Data::Entity.all(:redis => redis).sort_by(&:name).collect {|e|
124
- presenter = Flapjack::Gateways::API::EntityPresenter.new(e, :redis => redis)
125
- {'id' => e.id, 'name' => e.name, 'checks' => presenter.status }
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
- delete '/entities/:entity/tags' do
599
- tags = find_tags(params[:tag])
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
- get '/entities/:entity/tags' do
606
- content_type :json
79
+ register Flapjack::Gateways::API::EntityMethods
607
80
 
608
- entity = find_entity(params[:entity])
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