flapjack-diner 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +21 -0
  4. data/.rubocop_todo.yml +135 -0
  5. data/Gemfile +7 -0
  6. data/README.md +86 -20
  7. data/flapjack-diner.gemspec +11 -15
  8. data/lib/flapjack-diner.rb +23 -548
  9. data/lib/flapjack-diner/argument_validator.rb +31 -22
  10. data/lib/flapjack-diner/resources/checks.rb +58 -0
  11. data/lib/flapjack-diner/resources/contacts.rb +70 -0
  12. data/lib/flapjack-diner/resources/entities.rb +68 -0
  13. data/lib/flapjack-diner/resources/maintenance_periods.rb +82 -0
  14. data/lib/flapjack-diner/resources/media.rb +61 -0
  15. data/lib/flapjack-diner/resources/notification_rules.rb +66 -0
  16. data/lib/flapjack-diner/resources/notifications.rb +27 -0
  17. data/lib/flapjack-diner/resources/pagerduty_credentials.rb +60 -0
  18. data/lib/flapjack-diner/resources/reports.rb +33 -0
  19. data/lib/flapjack-diner/tools.rb +277 -0
  20. data/lib/flapjack-diner/version.rb +1 -1
  21. data/spec/argument_validator_spec.rb +15 -15
  22. data/spec/flapjack-diner_spec.rb +58 -1275
  23. data/spec/pacts/flapjack-diner-flapjack.json +4522 -0
  24. data/spec/resources/checks_spec.rb +171 -0
  25. data/spec/resources/contacts_spec.rb +297 -0
  26. data/spec/resources/entities_spec.rb +181 -0
  27. data/spec/resources/maintenance_periods_spec.rb +603 -0
  28. data/spec/resources/media_spec.rb +277 -0
  29. data/spec/resources/notification_rules_spec.rb +341 -0
  30. data/spec/resources/notifications_spec.rb +210 -0
  31. data/spec/resources/pagerduty_credentials_spec.rb +243 -0
  32. data/spec/resources/reports_spec.rb +255 -0
  33. data/spec/spec_helper.rb +14 -2
  34. metadata +35 -72
@@ -0,0 +1,66 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ require 'flapjack-diner/version'
6
+ require 'flapjack-diner/argument_validator'
7
+
8
+ module Flapjack
9
+ module Diner
10
+ module Resources
11
+ module NotificationRules
12
+ def create_contact_notification_rules(*args)
13
+ ids, data = unwrap_ids(*args), unwrap_create_data(*args)
14
+ raise "'create_contact_notification_rules' requires at least one " \
15
+ 'contact id parameter' if ids.nil? || ids.empty?
16
+ validate_params(data) do
17
+ validate :query => STRING_PARAMS, :as => :array_of_strings
18
+ validate :query => BOOLEAN_PARAMS, :as => :boolean
19
+ end
20
+ perform_post("/contacts/#{escaped_ids(ids)}/notification_rules",
21
+ nil, :notification_rules => data)
22
+ end
23
+
24
+ def notification_rules(*ids)
25
+ perform_get('notification_rules', '/notification_rules', ids)
26
+ end
27
+
28
+ def update_notification_rules(*args)
29
+ ids, params = unwrap_ids(*args), unwrap_params(*args)
30
+ raise "'update_notification_rules' requires at least one " \
31
+ 'notification rule id parameter' if ids.nil? || ids.empty?
32
+ validate_params(params) do
33
+ validate :query => STRING_PARAMS, :as => :array_of_strings
34
+ validate :query => BOOLEAN_PARAMS, :as => :boolean
35
+ end
36
+ perform_patch("/notification_rules/#{escaped_ids(ids)}", nil,
37
+ update_notification_rules_ops(params))
38
+ end
39
+
40
+ def delete_notification_rules(*ids)
41
+ raise "'delete_notification_rules' requires at least one " \
42
+ 'notification rule id parameter' if ids.nil? || ids.empty?
43
+ perform_delete('/notification_rules', ids)
44
+ end
45
+
46
+ private
47
+
48
+ STRING_PARAMS = [:entities, :regex_entities, :tags, :regex_tags,
49
+ :unknown_media, :warning_media, :critical_media]
50
+ BOOLEAN_PARAMS = [:unknown_blackhole, :warning_blackhole,
51
+ :critical_blackhole]
52
+ OTHER_PARAMS = [:time_restrictions]
53
+
54
+ def update_notification_rules_ops(params)
55
+ ops = params.each_with_object([]) do |(k, v), memo|
56
+ next unless (STRING_PARAMS + BOOLEAN_PARAMS + OTHER_PARAMS).include?(k)
57
+ memo << patch_replace('notification_rules', k, v)
58
+ end
59
+ raise "'update_notification_rules' did not find any valid update " \
60
+ 'fields' if ops.empty?
61
+ ops
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ require 'flapjack-diner/version'
6
+ require 'flapjack-diner/argument_validator'
7
+
8
+ module Flapjack
9
+ module Diner
10
+ module Resources
11
+ module Notifications
12
+ %w(entities checks).each do |data_type|
13
+ define_method("create_test_notifications_#{data_type}") do |*args|
14
+ ids, data = unwrap_ids(*args), unwrap_create_data(*args)
15
+ raise "'create_test_notifications_#{data_type}' requires at " \
16
+ "least one #{data_type} id parameter" if ids.nil? || ids.empty?
17
+ validate_params(data) do
18
+ validate :query => :summary, :as => :string
19
+ end
20
+ perform_post("/test_notifications/#{data_type}", ids,
21
+ :test_notifications => data)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ require 'flapjack-diner/version'
6
+ require 'flapjack-diner/argument_validator'
7
+
8
+ module Flapjack
9
+ module Diner
10
+ module Resources
11
+ module PagerdutyCredentials
12
+ def create_contact_pagerduty_credentials(*args)
13
+ ids, data = unwrap_ids(*args), unwrap_create_data(*args)
14
+ raise "'create_contact_pagerduty_credentials' requires at least " \
15
+ 'one contact id parameter' if ids.nil? || ids.empty?
16
+ validate_params(data) do
17
+ validate :query => [:service_key], :as => [:required, :string]
18
+ end
19
+ perform_post("/contacts/#{escaped_ids(ids)}/pagerduty_credentials",
20
+ nil, :pagerduty_credentials => data)
21
+ end
22
+
23
+ def pagerduty_credentials(*ids)
24
+ perform_get('pagerduty_credentials', '/pagerduty_credentials', ids)
25
+ end
26
+
27
+ def update_pagerduty_credentials(*args)
28
+ ids, params = unwrap_ids(*args), unwrap_params(*args)
29
+ raise "'update_pagerduty_credentials' requires at least one " \
30
+ ' pagerduty_credentials id parameter' if ids.nil? || ids.empty?
31
+ validate_params(params) do
32
+ validate :query => [:service_key, :subdomain,
33
+ :username, :password], :as => :string
34
+ end
35
+ perform_patch("/pagerduty_credentials/#{escaped_ids(ids)}",
36
+ nil, update_pagerduty_credentials_ops(params))
37
+ end
38
+
39
+ def delete_pagerduty_credentials(*ids)
40
+ raise "'delete_pagerduty_credentials' requires at least one " \
41
+ 'pagerduty_credentials id parameter' if ids.nil? || ids.empty?
42
+ perform_delete('/pagerduty_credentials', ids)
43
+ end
44
+
45
+ private
46
+
47
+ def update_pagerduty_credentials_ops(params)
48
+ ops = params.each_with_object([]) do |(k, v), memo|
49
+ next unless [:service_key, :subdomain,
50
+ :username, :password].include?(k)
51
+ memo << patch_replace('pagerduty_credentials', k, v)
52
+ end
53
+ raise "'update_pagerduty_credentials' did not find any valid " \
54
+ 'update fields' if ops.empty?
55
+ ops
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ require 'flapjack-diner/version'
6
+ require 'flapjack-diner/argument_validator'
7
+
8
+ module Flapjack
9
+ module Diner
10
+ module Resources
11
+ module Reports
12
+ %w(entities checks).each do |data_type|
13
+ define_method("status_report_#{data_type}") do |*ids|
14
+ perform_get('status_reports', "/status_report/#{data_type}", ids)
15
+ end
16
+
17
+ %w(scheduled_maintenance unscheduled_maintenance
18
+ downtime outage).each do |report_type|
19
+ define_method("#{report_type}_report_#{data_type}") do |*args|
20
+ ids, params = unwrap_ids(*args), unwrap_params(*args)
21
+ validate_params(params) do
22
+ validate :query => [:start_time, :end_time], :as => :time
23
+ end
24
+ perform_get("#{report_type}_reports",
25
+ "/#{report_type}_report/#{data_type}",
26
+ ids, params)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,277 @@
1
+ require 'uri'
2
+
3
+ module Flapjack
4
+ module Diner
5
+ module Tools
6
+ SUCCESS_STATUS_CODES = [200, 201, 204]
7
+
8
+ attr_accessor :last_error
9
+
10
+ private
11
+
12
+ def log_request(method_type, req_uri, data = nil)
13
+ return if logger.nil? || req_uri.nil?
14
+ log_msg = "#{method_type} #{req_uri}"
15
+ unless %w(GET DELETE).include?(method_type) || data.nil?
16
+ log_msg << "\n Body: #{data.inspect}"
17
+ end
18
+ logger.info log_msg
19
+ end
20
+
21
+ def perform_get(name, path, ids = [], data = nil)
22
+ @last_error = nil
23
+ req_uri = build_uri(path, ids, data)
24
+ log_request('GET', req_uri, data)
25
+ handled = handle_response(get(req_uri.request_uri))
26
+
27
+ result = (!handled.nil? && handled.is_a?(Hash)) ? handled[name] :
28
+ handled
29
+
30
+ return_keys_as_strings.is_a?(TrueClass) ? result : symbolize(result)
31
+ end
32
+
33
+ def perform_post(path, ids = [], data = nil)
34
+ @last_error = nil
35
+ req_uri = build_uri(path, ids)
36
+ log_request('POST', req_uri, data)
37
+ opts = if data.nil?
38
+ {}
39
+ else
40
+ {:body => prepare_nested_query(data).to_json,
41
+ :headers => {'Content-Type' => 'application/vnd.api+json'}}
42
+ end
43
+ handle_response(post(req_uri.request_uri, opts))
44
+ end
45
+
46
+ def perform_patch(path, ids = [], data = nil)
47
+ @last_error = nil
48
+ req_uri = build_uri(path, ids)
49
+ log_request('PATCH', req_uri, data)
50
+ opts = if data.nil?
51
+ {}
52
+ else
53
+ {:body => prepare_nested_query(data).to_json,
54
+ :headers => {'Content-Type' => 'application/json-patch+json'}}
55
+ end
56
+ handle_response(patch(req_uri.request_uri, opts))
57
+ end
58
+
59
+ def perform_delete(path, ids = [], data = nil)
60
+ @last_error = nil
61
+ req_uri = build_uri(path, ids, data)
62
+ log_request('DELETE', req_uri, data)
63
+ handle_response(delete(req_uri.request_uri))
64
+ end
65
+
66
+ def log_response(response)
67
+ return if logger.nil? || !response.respond_to?(:code)
68
+ response_message = " Response Code: #{response.code}"
69
+ unless response.message.nil? || (response.message.eql?(''))
70
+ response_message << " #{response.message}"
71
+ end
72
+ logger.info response_message
73
+ return if response.body.nil?
74
+ logger.info " Response Body: #{response.body[0..300]}"
75
+ end
76
+
77
+ def handle_response(response)
78
+ log_response(response)
79
+ return true if 204.eql?(response.code)
80
+ parsed = if response.respond_to?(:parsed_response)
81
+ response.parsed_response
82
+ else
83
+ nil
84
+ end
85
+ return parsed if [200, 201].include?(response.code)
86
+ @last_error = handle_error(response.code, parsed)
87
+ nil
88
+ end
89
+
90
+ def handle_error(code, parsed)
91
+ case parsed
92
+ when Hash
93
+ err = {'status_code' => code}.merge(parsed)
94
+ return_keys_as_strings.is_a?(TrueClass) ? err : symbolize(err)
95
+ else
96
+ parsed
97
+ end
98
+ end
99
+
100
+ def validate_params(query = {}, &validation)
101
+ return unless block_given?
102
+ case query
103
+ when Array
104
+ query.each do |q|
105
+ ArgumentValidator.new(q).instance_eval(&validation)
106
+ end
107
+ else
108
+ ArgumentValidator.new(query).instance_eval(&validation)
109
+ end
110
+ end
111
+
112
+ # copied from Rack::Utils -- builds the query string for GETs
113
+ def build_nested_query(value, prefix = nil)
114
+ case value
115
+ when Array
116
+ build_array_query(value, prefix)
117
+ when Hash
118
+ build_hash_query(value, prefix)
119
+ else
120
+ build_data_query(value, prefix)
121
+ end
122
+ end
123
+
124
+ def build_array_query(value, prefix)
125
+ value.map {|v| build_nested_query(v, "#{prefix}[]") }.join('&')
126
+ end
127
+
128
+ def build_hash_query(value, prefix)
129
+ value.map do |k, v|
130
+ data = prefix ? "#{prefix}[#{escape(k)}]" : escape(k)
131
+ build_nested_query(v, data)
132
+ end.join('&')
133
+ end
134
+
135
+ def build_data_query(value, prefix)
136
+ if value.respond_to?(:iso8601)
137
+ raise(ArgumentError, 'Value must be a Hash') if prefix.nil?
138
+ "#{prefix}=#{escape(value.iso8601)}"
139
+ elsif value.is_a?(String) || value.is_a?(Integer)
140
+ raise(ArgumentError, 'Value must be a Hash') if prefix.nil?
141
+ "#{prefix}=#{escape(value.to_s)}"
142
+ else
143
+ prefix
144
+ end
145
+ end
146
+
147
+ def escaped_ids(ids = [])
148
+ ids.map {|id| URI.escape(id.to_s) }.join(',')
149
+ end
150
+
151
+ def escape(s)
152
+ URI.encode_www_form_component(s)
153
+ end
154
+
155
+ def unwrap_ids(*args)
156
+ args.select {|a| a.is_a?(String) || a.is_a?(Integer) }
157
+ end
158
+
159
+ def unwrap_params(*args)
160
+ args.each_with_object({}) do |e, a|
161
+ a.update(symbolize(e)) if e.is_a?(Hash)
162
+ end
163
+ end
164
+
165
+ def unwrap_data(*args)
166
+ args.select {|a| a.is_a?(Array) && a.all? {|av| av.is_a?(Hash) } }
167
+ .reduce([], &:'+')
168
+ end
169
+
170
+ def unwrap_create_data(*args)
171
+ data_h = args.select {|a| a.is_a?(Hash) }
172
+ data_a = unwrap_data(*args)
173
+
174
+ raise 'Create data may be passed as a Hash or an Array of Hashes, ' \
175
+ 'not both' unless data_h.empty? || data_a.empty?
176
+
177
+ data = data_h
178
+ data = data_a if data.empty?
179
+ data = nil if data.empty?
180
+ data
181
+ end
182
+
183
+ def patch_replace(type, k, v)
184
+ {:op => 'replace',
185
+ :path => "/#{type}/0/#{k}",
186
+ :value => v}
187
+ end
188
+
189
+ def patch_add(type, linked, v)
190
+ {:op => 'add',
191
+ :path => "/#{type}/0/links/#{linked}/-",
192
+ :value => v}
193
+ end
194
+
195
+ def patch_remove(type, linked, v)
196
+ {:op => 'remove',
197
+ :path => "/#{type}/0/links/#{linked}/#{v}"}
198
+ end
199
+
200
+ # used for the JSON data hashes in POST, PUT, DELETE
201
+ def prepare_nested_query(value)
202
+ case value
203
+ when Array
204
+ prepare_array_query(value)
205
+ when Hash
206
+ prepare_hash_query(value)
207
+ else
208
+ prepare_data_query(value)
209
+ end
210
+ end
211
+
212
+ def prepare_array_query(value)
213
+ value.map {|v| prepare_nested_query(v) }
214
+ end
215
+
216
+ def prepare_hash_query(value)
217
+ value.each_with_object({}) do |(k, v), a|
218
+ a[k] = prepare_nested_query(v)
219
+ end
220
+ end
221
+
222
+ def prepare_data_query(value)
223
+ if value.respond_to?(:iso8601)
224
+ value.iso8601
225
+ else
226
+ case value
227
+ when Integer, TrueClass, FalseClass, NilClass
228
+ value
229
+ else
230
+ value.to_s
231
+ end
232
+ end
233
+ end
234
+
235
+ def normalise_port(port_str, protocol)
236
+ if port_str.nil? || port_str.to_i < 1 || port_str.to_i > 65_535
237
+ 'https'.eql?(protocol) ? 443 : 80
238
+ else
239
+ port_str.to_i
240
+ end
241
+ end
242
+
243
+ def protocol_host_port
244
+ %r{^(?:(?<protocol>https?)://)
245
+ (?<host>[a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])
246
+ (?::(?<port>\d+))?
247
+ }ix =~ base_uri
248
+
249
+ protocol = protocol.nil? ? 'http' : protocol.downcase
250
+ [protocol, host, normalise_port(port, protocol)]
251
+ end
252
+
253
+ def build_uri(path, ids = [], params = [])
254
+ pr, ho, po = protocol_host_port
255
+ path += '/' + escaped_ids(ids) unless ids.nil? || ids.empty?
256
+ query = if params.nil? || params.empty?
257
+ nil
258
+ else
259
+ build_nested_query(params)
260
+ end
261
+ URI::HTTP.build(:protocol => pr, :host => ho, :port => po,
262
+ :path => path, :query => query)
263
+ end
264
+
265
+ def symbolize(obj)
266
+ case obj
267
+ when Hash
268
+ obj.each_with_object({}) {|(k, v), a| a[k.to_sym] = symbolize(v) }
269
+ when Array
270
+ obj.each_with_object([]) {|e, a| a << symbolize(e) }
271
+ else
272
+ obj
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end