flapjack-diner 0.12

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/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/flapjack-diner/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ali Graham"]
6
+ gem.email = ["ali.graham@bulletproof.net"]
7
+ gem.summary = %q{Access the API of a Flapjack system monitoring server}
8
+ gem.description = %q{Wraps raw API calls to a Flapjack server API with friendlier ruby methods.}
9
+ gem.homepage = 'https://github.com/flpjck/flapjack-diner'
10
+
11
+ gem.files = `git ls-files`.split($\) - ['Gemfile.lock']
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "flapjack-diner"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Flapjack::Diner::VERSION
17
+
18
+ gem.add_dependency('httparty', '>= 0.10')
19
+ gem.add_dependency('json', '>= 1.7.7')
20
+
21
+ gem.add_development_dependency('bundler')
22
+ gem.add_development_dependency('rake')
23
+ gem.add_development_dependency('rspec', '>= 2.0.0')
24
+ gem.add_development_dependency('simplecov')
25
+ gem.add_development_dependency('webmock')
26
+ end
@@ -0,0 +1,455 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'uri'
4
+ require 'cgi'
5
+
6
+ require "flapjack-diner/version"
7
+ require "flapjack-diner/argument_validator"
8
+
9
+ module Flapjack
10
+ module Diner
11
+ SUCCESS_STATUS_CODES = [200, 204]
12
+
13
+ include HTTParty
14
+
15
+ format :json
16
+
17
+ class << self
18
+
19
+ attr_accessor :logger
20
+
21
+ # NB: clients will need to handle any exceptions caused by,
22
+ # e.g., network failures or non-parseable JSON data.
23
+
24
+ def entities
25
+ perform_get('/entities')
26
+ end
27
+
28
+ def checks(entity)
29
+ perform_get("/checks/#{escape(entity)}")
30
+ end
31
+
32
+ def status(entity, options = {})
33
+ check = options.delete(:check)
34
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
35
+ validate_bulk_params(args)
36
+ perform_get('/status', args)
37
+ end
38
+
39
+ def bulk_status(options = {})
40
+ validate_bulk_params(options)
41
+ perform_get('/status', options)
42
+ end
43
+
44
+ # maybe rename 'create_acknowledgement!' ?
45
+ def acknowledge!(entity, check, options = {})
46
+ args = options.merge(:check => {entity => check})
47
+ validate_bulk_params(args)
48
+ perform_post('/acknowledgements', args)
49
+ end
50
+
51
+ def bulk_acknowledge!(options = {})
52
+ validate_bulk_params(options)
53
+ perform_post('/acknowledgements', options)
54
+ end
55
+
56
+ # maybe rename 'create_test_notifications!' ?
57
+ def test_notifications!(entity, check, options = {})
58
+ args = options.merge(:check => {entity => check})
59
+ validate_bulk_params(args)
60
+ perform_post('/test_notifications', args)
61
+ end
62
+
63
+ def bulk_test_notifications!(options = {})
64
+ validate_bulk_params(options)
65
+ perform_post('/test_notifications', options)
66
+ end
67
+
68
+ def create_scheduled_maintenance!(entity, check, options = {})
69
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
70
+
71
+ validate_bulk_params(args) do
72
+ validate :query => :start_time, :as => [:required, :time]
73
+ validate :query => :duration, :as => [:required, :integer]
74
+ end
75
+
76
+ perform_post('/scheduled_maintenances', args)
77
+ end
78
+
79
+ def bulk_create_scheduled_maintenance!(options = {})
80
+ validate_bulk_params(options) do
81
+ validate :query => :start_time, :as => [:required, :time]
82
+ validate :query => :duration, :as => [:required, :integer]
83
+ end
84
+
85
+ perform_post('/scheduled_maintenances', options)
86
+ end
87
+
88
+ def delete_scheduled_maintenance!(entity, check, options = {})
89
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
90
+
91
+ validate_bulk_params(args) do
92
+ validate :query => :start_time, :as => :required
93
+ end
94
+
95
+ perform_delete('/scheduled_maintenances', args)
96
+ end
97
+
98
+ def bulk_delete_scheduled_maintenance!(options = {})
99
+ validate_bulk_params(options) do
100
+ validate :query => :start_time, :as => :required
101
+ end
102
+
103
+ perform_delete('/scheduled_maintenances', options)
104
+ end
105
+
106
+ def delete_unscheduled_maintenance!(entity, check, options = {})
107
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
108
+ validate_bulk_params(args) do
109
+ validate :query => :end_time, :as => :time
110
+ end
111
+ perform_delete('/unscheduled_maintenances', args)
112
+ end
113
+
114
+ def bulk_delete_unscheduled_maintenance!(options)
115
+ validate_bulk_params(options) do
116
+ validate :query => :end_time, :as => :time
117
+ end
118
+ perform_delete('/unscheduled_maintenances', options)
119
+ end
120
+
121
+ def scheduled_maintenances(entity, options = {})
122
+ check = options.delete(:check)
123
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
124
+
125
+ validate_bulk_params(args) do
126
+ validate :query => [:start_time, :end_time], :as => :time
127
+ end
128
+
129
+ perform_get('/scheduled_maintenances', args)
130
+ end
131
+
132
+ def bulk_scheduled_maintenances(options = {})
133
+ validate_bulk_params(options) do
134
+ validate :query => [:start_time, :end_time], :as => :time
135
+ end
136
+
137
+ perform_get('/scheduled_maintenances', options)
138
+ end
139
+
140
+ def unscheduled_maintenances(entity, options = {})
141
+ check = options.delete(:check)
142
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
143
+
144
+ validate_bulk_params(args) do
145
+ validate :query => [:start_time, :end_time], :as => :time
146
+ end
147
+
148
+ perform_get('/unscheduled_maintenances', args)
149
+ end
150
+
151
+ def bulk_unscheduled_maintenances(options = {})
152
+ validate_bulk_params(options) do
153
+ validate :query => [:start_time, :end_time], :as => :time
154
+ end
155
+
156
+ perform_get('/unscheduled_maintenances', options)
157
+ end
158
+
159
+ def outages(entity, options = {})
160
+ check = options.delete(:check)
161
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
162
+
163
+ validate_bulk_params(args) do
164
+ validate :query => [:start_time, :end_time], :as => :time
165
+ end
166
+
167
+ perform_get('/outages', args)
168
+ end
169
+
170
+ def bulk_outages(options = {})
171
+ validate_bulk_params(options) do
172
+ validate :query => [:start_time, :end_time], :as => :time
173
+ end
174
+
175
+ perform_get('/outages', options)
176
+ end
177
+
178
+ def downtime(entity, options = {})
179
+ check = options.delete(:check)
180
+ args = options.merge( check ? {:check => {entity => check}} : {:entity => entity} )
181
+
182
+ validate_bulk_params(args) do
183
+ validate :query => [:start_time, :end_time], :as => :time
184
+ end
185
+
186
+ perform_get('/downtime', args)
187
+ end
188
+
189
+ def bulk_downtime(options = {})
190
+ validate_bulk_params(options) do
191
+ validate :query => [:start_time, :end_time], :as => :time
192
+ end
193
+
194
+ perform_get('/downtime', options)
195
+ end
196
+
197
+ def entity_tags(entity)
198
+ perform_get("/entities/#{escape(entity)}/tags")
199
+ end
200
+
201
+ def add_entity_tags!(entity, *tags)
202
+ perform_post("/entities/#{escape(entity)}/tags", :tag => tags)
203
+ end
204
+
205
+ def delete_entity_tags!(entity, *tags)
206
+ perform_delete("/entities/#{escape(entity)}/tags", :tag => tags)
207
+ end
208
+
209
+ def contacts
210
+ perform_get('/contacts')
211
+ end
212
+
213
+ def contact(contact_id)
214
+ perform_get("/contacts/#{escape(contact_id)}")
215
+ end
216
+
217
+ def contact_tags(contact_id)
218
+ perform_get("/contacts/#{escape(contact_id)}/tags")
219
+ end
220
+
221
+ def contact_entitytags(contact_id)
222
+ perform_get("/contacts/#{escape(contact_id)}/entity_tags")
223
+ end
224
+
225
+ def add_contact_tags!(contact_id, *tags)
226
+ perform_post("/contacts/#{escape(contact_id)}/tags", :tag => tags)
227
+ end
228
+
229
+ # TODO better checking of provided data
230
+ def add_contact_entitytags!(contact_id, entity_tags = {})
231
+ perform_post("/contacts/#{escape(contact_id)}/entity_tags", :entity => entity_tags)
232
+ end
233
+
234
+ def delete_contact_tags!(contact_id, *tags)
235
+ perform_delete("/contacts/#{escape(contact_id)}/tags", :tag => tags)
236
+ end
237
+
238
+ # TODO better checking of provided data
239
+ def delete_contact_entitytags!(contact_id, entity_tags = {})
240
+ perform_delete("/contacts/#{escape(contact_id)}/entity_tags", :entity => entity_tags)
241
+ end
242
+
243
+ def notification_rules(contact_id)
244
+ perform_get("/contacts/#{escape(contact_id)}/notification_rules")
245
+ end
246
+
247
+ def notification_rule(rule_id)
248
+ perform_get("/notification_rules/#{escape(rule_id)}")
249
+ end
250
+
251
+ def create_notification_rule!(rule)
252
+ perform_post('/notification_rules', rule)
253
+ end
254
+
255
+ def update_notification_rule!(rule_id, rule)
256
+ perform_put("/notification_rules/#{escape(rule_id)}", rule)
257
+ end
258
+
259
+ def delete_notification_rule!(rule_id)
260
+ perform_delete("/notification_rules/#{escape(rule_id)}")
261
+ end
262
+
263
+ def contact_media(contact_id)
264
+ perform_get("/contacts/#{escape(contact_id)}/media")
265
+ end
266
+
267
+ def contact_medium(contact_id, media_type)
268
+ perform_get("/contacts/#{escape(contact_id)}/media/#{escape(media_type)}")
269
+ end
270
+
271
+ def update_contact_medium!(contact_id, media_type, media)
272
+ perform_put("/contacts/#{escape(contact_id)}/media/#{escape(media_type)}", media)
273
+ end
274
+
275
+ def delete_contact_medium!(contact_id, media_type)
276
+ perform_delete("/contacts/#{escape(contact_id)}/media/#{escape(media_type)}")
277
+ end
278
+
279
+ def contact_timezone(contact_id)
280
+ perform_get("/contacts/#{escape(contact_id)}/timezone")
281
+ end
282
+
283
+ def update_contact_timezone!(contact_id, timezone)
284
+ perform_put("/contacts/#{escape(contact_id)}/timezone", :timezone => timezone)
285
+ end
286
+
287
+ def delete_contact_timezone!(contact_id)
288
+ perform_delete("/contacts/#{escape(contact_id)}/timezone")
289
+ end
290
+
291
+ def last_error
292
+ @last_error
293
+ end
294
+
295
+ private
296
+
297
+ def perform_get(path, params = nil)
298
+ req_uri = build_uri(path, params)
299
+ logger.info "GET #{req_uri}" if logger
300
+ response = get(req_uri.request_uri)
301
+ handle_response(response)
302
+ end
303
+
304
+ def perform_post(path, body = {})
305
+ req_uri = build_uri(path)
306
+ if logger
307
+ log_post = "POST #{req_uri}"
308
+ log_post << "\n Params: #{body.inspect}" if body
309
+ logger.info log_post
310
+ end
311
+ opts = body ? {:body => prepare_nested_query(body).to_json, :headers => {'Content-Type' => 'application/json'}} : {}
312
+ response = post(req_uri.request_uri, opts)
313
+ handle_response(response)
314
+ end
315
+
316
+ def perform_put(path, body = {})
317
+ req_uri = build_uri(path)
318
+ if logger
319
+ log_put = "PUT #{req_uri}"
320
+ log_put << "\n Params: #{body.inspect}" if body
321
+ logger.info log_put
322
+ end
323
+ opts = body ? {:body => prepare_nested_query(body).to_json, :headers => {'Content-Type' => 'application/json'}} : {}
324
+ response = put(req_uri.request_uri, opts)
325
+ handle_response(response)
326
+ end
327
+
328
+ def perform_delete(path, body = nil)
329
+ req_uri = build_uri(path)
330
+ if logger
331
+ log_delete = "DELETE #{req_uri}"
332
+ log_delete << "\n Params: #{body.inspect}" if body
333
+ logger.info log_delete
334
+ end
335
+ opts = body ? {:body => prepare_nested_query(body).to_json, :headers => {'Content-Type' => 'application/json'}} : {}
336
+ response = delete(req_uri.request_uri, opts)
337
+ handle_response(response)
338
+ end
339
+
340
+ def handle_response(response)
341
+ response_body = response.body
342
+ response_start = response_body ? response_body[0..300] : nil
343
+ if logger
344
+ logger.info " Response Code: #{response.code}#{response.message ? response.message : ''}"
345
+ logger.info " Response Body: #{response_start}" if response_start
346
+ end
347
+ parsed_response = response.respond_to?(:parsed_response) ? response.parsed_response : nil
348
+ unless SUCCESS_STATUS_CODES.include?(response.code)
349
+ self.last_error = {'status_code' => response.code}.merge(parsed_response)
350
+ return nil
351
+ end
352
+ return true unless (response.code == 200) && parsed_response
353
+ parsed_response
354
+ end
355
+
356
+ def validate_bulk_params(query = {}, &validation)
357
+ errors = []
358
+
359
+ entities = query[:entity]
360
+ checks = query[:check]
361
+
362
+ if entities && !entities.is_a?(String) &&
363
+ (!entities.is_a?(Array) || !entities.all? {|e| e.is_a?(String)})
364
+ raise ArgumentError.new("Entity argument must be a String, or an Array of Strings")
365
+ end
366
+
367
+ if checks && (!checks.is_a?(Hash) || !checks.all? {|k, v|
368
+ k.is_a?(String) && (v.is_a?(String) || (v.is_a?(Array) && v.all?{|vv| vv.is_a?(String)}))
369
+ })
370
+ raise ArgumentError.new("Check argument must be a Hash with keys String, values either String or Array of Strings")
371
+ end
372
+
373
+ if entities.nil? && checks.nil?
374
+ raise ArgumentError.new("Entity and/or check arguments must be provided")
375
+ end
376
+
377
+ ArgumentValidator.new(query).instance_eval(&validation) if block_given?
378
+ end
379
+
380
+ # copied from Rack::Utils -- builds the query string for GETs
381
+ def build_nested_query(value, prefix = nil)
382
+ if value.respond_to?(:iso8601)
383
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
384
+ "#{prefix}=#{escape(value.iso8601)}"
385
+ else
386
+ case value
387
+ when Array
388
+ value.map { |v|
389
+ build_nested_query(v, "#{prefix}[]")
390
+ }.join("&")
391
+ when Hash
392
+ value.map { |k, v|
393
+ build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
394
+ }.join("&")
395
+ when String, Integer
396
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
397
+ "#{prefix}=#{escape(value.to_s)}"
398
+ else
399
+ prefix
400
+ end
401
+ end
402
+ end
403
+
404
+ def escape(s)
405
+ URI.encode_www_form_component(s)
406
+ end
407
+
408
+ # used for the JSON data hashes in POST, PUT, DELETE
409
+ def prepare_nested_query(value)
410
+ if value.respond_to?(:iso8601)
411
+ value.iso8601
412
+ else
413
+ case value
414
+ when Array
415
+ value.map { |v| prepare_nested_query(v) }
416
+ when Hash
417
+ value.inject({}) do |memo, (k, v)|
418
+ memo[k] = prepare_nested_query(v)
419
+ memo
420
+ end
421
+ when Integer, TrueClass, FalseClass, NilClass
422
+ value
423
+ else
424
+ value.to_s
425
+ end
426
+ end
427
+ end
428
+
429
+ def protocol_host_port
430
+ self.base_uri =~ /^(?:(https?):\/\/)?([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?::(\d+))?/i
431
+ protocol = ($1 || 'http').downcase
432
+ host = $2
433
+ port = $3
434
+
435
+ if port.nil? || port.to_i < 1 || port.to_i > 65535
436
+ port = 'https'.eql?(protocol) ? 443 : 80
437
+ else
438
+ port = port.to_i
439
+ end
440
+
441
+ [protocol, host, port]
442
+ end
443
+
444
+ def build_uri(path, params = nil)
445
+ pr, ho, po = protocol_host_port
446
+ URI::HTTP.build(:protocol => pr, :host => ho, :port => po,
447
+ :path => path, :query => (params.nil? || params.empty? ? nil : build_nested_query(params)))
448
+ end
449
+
450
+ def last_error=(error)
451
+ @last_error = error
452
+ end
453
+ end
454
+ end
455
+ end