flapjack 1.6.0 → 2.0.0b1

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 (301) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -6
  3. data/.gitmodules +1 -1
  4. data/.rspec +1 -1
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +12 -13
  7. data/CHANGELOG.md +2 -9
  8. data/CONTRIBUTING.md +7 -2
  9. data/Gemfile +4 -13
  10. data/LICENCE +1 -0
  11. data/README.md +8 -2
  12. data/Rakefile +2 -2
  13. data/bin/flapjack +3 -12
  14. data/build.sh +4 -2
  15. data/etc/flapjack_config.toml.example +273 -0
  16. data/features/ack_after_sched_maint.feature +18 -21
  17. data/features/cli.feature +11 -71
  18. data/features/cli_flapjack-feed-events.feature +14 -15
  19. data/features/cli_flapjack-nagios-receiver.feature +12 -41
  20. data/features/cli_flapper.feature +12 -41
  21. data/features/cli_purge.feature +5 -6
  22. data/features/cli_receive-events.feature +6 -7
  23. data/features/cli_simulate-failed-check.feature +5 -6
  24. data/features/events.feature +206 -181
  25. data/features/events_check_names.feature +4 -7
  26. data/features/notification_rules.feature +144 -223
  27. data/features/notifications.feature +65 -57
  28. data/features/rollup.feature +45 -47
  29. data/features/steps/cli_steps.rb +4 -5
  30. data/features/steps/events_steps.rb +163 -373
  31. data/features/steps/notifications_steps.rb +408 -264
  32. data/features/steps/packaging-lintian_steps.rb +0 -4
  33. data/features/steps/time_travel_steps.rb +0 -26
  34. data/features/support/daemons.rb +6 -31
  35. data/features/support/env.rb +65 -74
  36. data/flapjack.gemspec +22 -24
  37. data/lib/flapjack.rb +14 -7
  38. data/lib/flapjack/cli/flapper.rb +74 -173
  39. data/lib/flapjack/cli/maintenance.rb +278 -109
  40. data/lib/flapjack/cli/migrate.rb +950 -0
  41. data/lib/flapjack/cli/purge.rb +19 -22
  42. data/lib/flapjack/cli/receiver.rb +150 -326
  43. data/lib/flapjack/cli/server.rb +8 -235
  44. data/lib/flapjack/cli/simulate.rb +42 -57
  45. data/lib/flapjack/configuration.rb +51 -37
  46. data/lib/flapjack/coordinator.rb +138 -129
  47. data/lib/flapjack/data/acknowledgement.rb +177 -0
  48. data/lib/flapjack/data/alert.rb +97 -158
  49. data/lib/flapjack/data/check.rb +611 -0
  50. data/lib/flapjack/data/condition.rb +70 -0
  51. data/lib/flapjack/data/contact.rb +226 -456
  52. data/lib/flapjack/data/event.rb +96 -184
  53. data/lib/flapjack/data/extensions/associations.rb +59 -0
  54. data/lib/flapjack/data/extensions/short_name.rb +25 -0
  55. data/lib/flapjack/data/medium.rb +428 -0
  56. data/lib/flapjack/data/metrics.rb +194 -0
  57. data/lib/flapjack/data/notification.rb +22 -281
  58. data/lib/flapjack/data/rule.rb +473 -0
  59. data/lib/flapjack/data/scheduled_maintenance.rb +244 -0
  60. data/lib/flapjack/data/state.rb +221 -0
  61. data/lib/flapjack/data/statistic.rb +112 -0
  62. data/lib/flapjack/data/tag.rb +277 -0
  63. data/lib/flapjack/data/test_notification.rb +182 -0
  64. data/lib/flapjack/data/unscheduled_maintenance.rb +159 -0
  65. data/lib/flapjack/data/validators/id_validator.rb +20 -0
  66. data/lib/flapjack/exceptions.rb +6 -0
  67. data/lib/flapjack/filters/acknowledgement.rb +23 -16
  68. data/lib/flapjack/filters/base.rb +0 -5
  69. data/lib/flapjack/filters/delays.rb +53 -43
  70. data/lib/flapjack/filters/ok.rb +23 -14
  71. data/lib/flapjack/filters/scheduled_maintenance.rb +3 -3
  72. data/lib/flapjack/filters/unscheduled_maintenance.rb +12 -3
  73. data/lib/flapjack/gateways/aws_sns.rb +65 -49
  74. data/lib/flapjack/gateways/aws_sns/alert.text.erb +2 -2
  75. data/lib/flapjack/gateways/aws_sns/alert_subject.text.erb +2 -2
  76. data/lib/flapjack/gateways/aws_sns/rollup_subject.text.erb +1 -1
  77. data/lib/flapjack/gateways/email.rb +107 -90
  78. data/lib/flapjack/gateways/email/alert.html.erb +19 -18
  79. data/lib/flapjack/gateways/email/alert.text.erb +20 -14
  80. data/lib/flapjack/gateways/email/alert_subject.text.erb +2 -1
  81. data/lib/flapjack/gateways/email/rollup.html.erb +14 -13
  82. data/lib/flapjack/gateways/email/rollup.text.erb +13 -10
  83. data/lib/flapjack/gateways/jabber.rb +679 -671
  84. data/lib/flapjack/gateways/jabber/alert.text.erb +9 -6
  85. data/lib/flapjack/gateways/jsonapi.rb +164 -350
  86. data/lib/flapjack/gateways/jsonapi/data/join_descriptor.rb +44 -0
  87. data/lib/flapjack/gateways/jsonapi/data/method_descriptor.rb +21 -0
  88. data/lib/flapjack/gateways/jsonapi/helpers/headers.rb +63 -0
  89. data/lib/flapjack/gateways/jsonapi/helpers/miscellaneous.rb +136 -0
  90. data/lib/flapjack/gateways/jsonapi/helpers/resources.rb +227 -0
  91. data/lib/flapjack/gateways/jsonapi/helpers/serialiser.rb +313 -0
  92. data/lib/flapjack/gateways/jsonapi/helpers/swagger_docs.rb +322 -0
  93. data/lib/flapjack/gateways/jsonapi/methods/association_delete.rb +115 -0
  94. data/lib/flapjack/gateways/jsonapi/methods/association_get.rb +288 -0
  95. data/lib/flapjack/gateways/jsonapi/methods/association_patch.rb +178 -0
  96. data/lib/flapjack/gateways/jsonapi/methods/association_post.rb +116 -0
  97. data/lib/flapjack/gateways/jsonapi/methods/metrics.rb +71 -0
  98. data/lib/flapjack/gateways/jsonapi/methods/resource_delete.rb +119 -0
  99. data/lib/flapjack/gateways/jsonapi/methods/resource_get.rb +186 -0
  100. data/lib/flapjack/gateways/jsonapi/methods/resource_patch.rb +239 -0
  101. data/lib/flapjack/gateways/jsonapi/methods/resource_post.rb +197 -0
  102. data/lib/flapjack/gateways/jsonapi/middleware/array_param_fixer.rb +27 -0
  103. data/lib/flapjack/gateways/jsonapi/{rack → middleware}/json_params_parser.rb +7 -6
  104. data/lib/flapjack/gateways/jsonapi/middleware/request_timestamp.rb +18 -0
  105. data/lib/flapjack/gateways/oobetet.rb +222 -170
  106. data/lib/flapjack/gateways/pager_duty.rb +388 -0
  107. data/lib/flapjack/gateways/pager_duty/alert.text.erb +13 -0
  108. data/lib/flapjack/gateways/slack.rb +56 -48
  109. data/lib/flapjack/gateways/slack/alert.text.erb +1 -1
  110. data/lib/flapjack/gateways/slack/rollup.text.erb +1 -1
  111. data/lib/flapjack/gateways/sms_aspsms.rb +155 -0
  112. data/lib/flapjack/gateways/sms_aspsms/alert.text.erb +7 -0
  113. data/lib/flapjack/gateways/sms_aspsms/rollup.text.erb +2 -0
  114. data/lib/flapjack/gateways/sms_messagenet.rb +77 -57
  115. data/lib/flapjack/gateways/sms_messagenet/alert.text.erb +3 -2
  116. data/lib/flapjack/gateways/sms_nexmo.rb +53 -51
  117. data/lib/flapjack/gateways/sms_nexmo/alert.text.erb +2 -2
  118. data/lib/flapjack/gateways/sms_nexmo/rollup.text.erb +1 -1
  119. data/lib/flapjack/gateways/sms_twilio.rb +79 -62
  120. data/lib/flapjack/gateways/sms_twilio/alert.text.erb +3 -2
  121. data/lib/flapjack/gateways/web.rb +437 -345
  122. data/lib/flapjack/gateways/web/middleware/request_timestamp.rb +18 -0
  123. data/lib/flapjack/gateways/web/public/css/bootstrap.css +3793 -4340
  124. data/lib/flapjack/gateways/web/public/css/bootstrap.css.map +1 -0
  125. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
  126. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +273 -214
  127. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  128. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
  129. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  130. data/lib/flapjack/gateways/web/public/js/bootstrap.js +1637 -1607
  131. data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -2
  132. data/lib/flapjack/gateways/web/views/_pagination.html.erb +19 -0
  133. data/lib/flapjack/gateways/web/views/check.html.erb +159 -121
  134. data/lib/flapjack/gateways/web/views/checks.html.erb +82 -41
  135. data/lib/flapjack/gateways/web/views/contact.html.erb +59 -71
  136. data/lib/flapjack/gateways/web/views/contacts.html.erb +32 -8
  137. data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
  138. data/lib/flapjack/gateways/web/views/{layout.erb → layout.html.erb} +7 -23
  139. data/lib/flapjack/gateways/web/views/self_stats.html.erb +32 -33
  140. data/lib/flapjack/gateways/web/views/tag.html.erb +32 -0
  141. data/lib/flapjack/gateways/web/views/tags.html.erb +51 -0
  142. data/lib/flapjack/logger.rb +34 -3
  143. data/lib/flapjack/notifier.rb +180 -112
  144. data/lib/flapjack/patches.rb +8 -63
  145. data/lib/flapjack/pikelet.rb +185 -143
  146. data/lib/flapjack/processor.rb +323 -191
  147. data/lib/flapjack/record_queue.rb +33 -0
  148. data/lib/flapjack/redis_proxy.rb +66 -0
  149. data/lib/flapjack/utility.rb +21 -15
  150. data/lib/flapjack/version.rb +2 -1
  151. data/libexec/httpbroker.go +218 -14
  152. data/libexec/oneoff.go +13 -10
  153. data/spec/lib/flapjack/configuration_spec.rb +286 -0
  154. data/spec/lib/flapjack/coordinator_spec.rb +103 -157
  155. data/spec/lib/flapjack/data/check_spec.rb +175 -0
  156. data/spec/lib/flapjack/data/contact_spec.rb +26 -349
  157. data/spec/lib/flapjack/data/event_spec.rb +76 -291
  158. data/spec/lib/flapjack/data/medium_spec.rb +19 -0
  159. data/spec/lib/flapjack/data/rule_spec.rb +43 -0
  160. data/spec/lib/flapjack/data/scheduled_maintenance_spec.rb +976 -0
  161. data/spec/lib/flapjack/data/unscheduled_maintenance_spec.rb +34 -0
  162. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +111 -60
  163. data/spec/lib/flapjack/gateways/email_spec.rb +194 -161
  164. data/spec/lib/flapjack/gateways/jabber_spec.rb +961 -162
  165. data/spec/lib/flapjack/gateways/jsonapi/methods/check_links_spec.rb +155 -0
  166. data/spec/lib/flapjack/gateways/jsonapi/methods/checks_spec.rb +426 -0
  167. data/spec/lib/flapjack/gateways/jsonapi/methods/contact_links_spec.rb +217 -0
  168. data/spec/lib/flapjack/gateways/jsonapi/methods/contacts_spec.rb +425 -0
  169. data/spec/lib/flapjack/gateways/jsonapi/methods/events_spec.rb +271 -0
  170. data/spec/lib/flapjack/gateways/jsonapi/methods/media_spec.rb +257 -0
  171. data/spec/lib/flapjack/gateways/jsonapi/methods/medium_links_spec.rb +163 -0
  172. data/spec/lib/flapjack/gateways/jsonapi/methods/metrics_spec.rb +8 -0
  173. data/spec/lib/flapjack/gateways/jsonapi/methods/rule_links_spec.rb +212 -0
  174. data/spec/lib/flapjack/gateways/jsonapi/methods/rules_spec.rb +289 -0
  175. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenance_links_spec.rb +49 -0
  176. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenances_spec.rb +242 -0
  177. data/spec/lib/flapjack/gateways/jsonapi/methods/tag_links_spec.rb +274 -0
  178. data/spec/lib/flapjack/gateways/jsonapi/methods/tags_spec.rb +302 -0
  179. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenance_links_spec.rb +49 -0
  180. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenances_spec.rb +339 -0
  181. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +1 -1
  182. data/spec/lib/flapjack/gateways/oobetet_spec.rb +151 -79
  183. data/spec/lib/flapjack/gateways/pager_duty_spec.rb +353 -0
  184. data/spec/lib/flapjack/gateways/slack_spec.rb +53 -53
  185. data/spec/lib/flapjack/gateways/sms_aspsms_spec.rb +106 -0
  186. data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +111 -54
  187. data/spec/lib/flapjack/gateways/sms_nexmo_spec.rb +50 -51
  188. data/spec/lib/flapjack/gateways/sms_twilio_spec.rb +108 -48
  189. data/spec/lib/flapjack/gateways/web_spec.rb +144 -216
  190. data/spec/lib/flapjack/notifier_spec.rb +132 -1
  191. data/spec/lib/flapjack/pikelet_spec.rb +111 -50
  192. data/spec/lib/flapjack/processor_spec.rb +210 -40
  193. data/spec/lib/flapjack/redis_proxy_spec.rb +45 -0
  194. data/spec/lib/flapjack/utility_spec.rb +11 -15
  195. data/spec/service_consumers/fixture_data.rb +547 -0
  196. data/spec/service_consumers/pact_helper.rb +21 -32
  197. data/spec/service_consumers/pacts/flapjack-diner_v2.0.json +4652 -0
  198. data/spec/service_consumers/provider_states_for_flapjack-diner.rb +279 -322
  199. data/spec/service_consumers/provider_support.rb +8 -0
  200. data/spec/spec_helper.rb +34 -44
  201. data/spec/support/erb_view_helper.rb +1 -1
  202. data/spec/support/factories.rb +58 -0
  203. data/spec/support/jsonapi_helper.rb +15 -26
  204. data/spec/support/mock_logger.rb +43 -0
  205. data/spec/support/xmpp_comparable.rb +24 -0
  206. data/src/flapjack/transport_test.go +30 -1
  207. data/tasks/dump_keys.rake +82 -0
  208. data/tasks/events.rake +7 -7
  209. data/tasks/support/flapjack_config_benchmark.toml +28 -0
  210. data/tasks/support/flapjack_config_benchmark.yaml +0 -2
  211. metadata +175 -222
  212. data/Guardfile +0 -14
  213. data/etc/flapjack_config.yaml.example +0 -477
  214. data/features/cli_flapjack-populator.feature +0 -90
  215. data/features/support/silent_system.rb +0 -4
  216. data/lib/flapjack/cli/import.rb +0 -108
  217. data/lib/flapjack/data/entity.rb +0 -652
  218. data/lib/flapjack/data/entity_check.rb +0 -1044
  219. data/lib/flapjack/data/message.rb +0 -56
  220. data/lib/flapjack/data/migration.rb +0 -234
  221. data/lib/flapjack/data/notification_rule.rb +0 -425
  222. data/lib/flapjack/data/semaphore.rb +0 -44
  223. data/lib/flapjack/data/tagged.rb +0 -48
  224. data/lib/flapjack/gateways/jsonapi/check_methods.rb +0 -206
  225. data/lib/flapjack/gateways/jsonapi/check_presenter.rb +0 -221
  226. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +0 -186
  227. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +0 -223
  228. data/lib/flapjack/gateways/jsonapi/medium_methods.rb +0 -185
  229. data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +0 -132
  230. data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +0 -141
  231. data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +0 -139
  232. data/lib/flapjack/gateways/jsonapi/report_methods.rb +0 -146
  233. data/lib/flapjack/gateways/pagerduty.rb +0 -318
  234. data/lib/flapjack/gateways/pagerduty/alert.text.erb +0 -10
  235. data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +0 -87
  236. data/lib/flapjack/gateways/web/public/css/select2.css +0 -615
  237. data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -67
  238. data/lib/flapjack/gateways/web/public/img/select2-spinner.gif +0 -0
  239. data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
  240. data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
  241. data/lib/flapjack/gateways/web/public/js/backbone.js +0 -1581
  242. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +0 -322
  243. data/lib/flapjack/gateways/web/public/js/flapjack.js +0 -82
  244. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.js +0 -1640
  245. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -1390
  246. data/lib/flapjack/gateways/web/public/js/modules/contact.js +0 -520
  247. data/lib/flapjack/gateways/web/public/js/modules/entity.js +0 -28
  248. data/lib/flapjack/gateways/web/public/js/modules/medium.js +0 -40
  249. data/lib/flapjack/gateways/web/public/js/select2.js +0 -3397
  250. data/lib/flapjack/gateways/web/public/js/tablesort.js +0 -44
  251. data/lib/flapjack/gateways/web/public/js/underscore.js +0 -1276
  252. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +0 -173
  253. data/lib/flapjack/gateways/web/views/entities.html.erb +0 -30
  254. data/lib/flapjack/gateways/web/views/entity.html.erb +0 -51
  255. data/lib/flapjack/rack_logger.rb +0 -47
  256. data/lib/flapjack/redis_pool.rb +0 -42
  257. data/spec/lib/flapjack/data/entity_check_spec.rb +0 -1418
  258. data/spec/lib/flapjack/data/entity_spec.rb +0 -872
  259. data/spec/lib/flapjack/data/message_spec.rb +0 -30
  260. data/spec/lib/flapjack/data/migration_spec.rb +0 -104
  261. data/spec/lib/flapjack/data/notification_rule_spec.rb +0 -232
  262. data/spec/lib/flapjack/data/notification_spec.rb +0 -53
  263. data/spec/lib/flapjack/data/semaphore_spec.rb +0 -24
  264. data/spec/lib/flapjack/filters/acknowledgement_spec.rb +0 -6
  265. data/spec/lib/flapjack/filters/delays_spec.rb +0 -6
  266. data/spec/lib/flapjack/filters/ok_spec.rb +0 -6
  267. data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +0 -6
  268. data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +0 -6
  269. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +0 -315
  270. data/spec/lib/flapjack/gateways/jsonapi/check_presenter_spec.rb +0 -223
  271. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +0 -131
  272. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +0 -389
  273. data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +0 -231
  274. data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +0 -169
  275. data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +0 -114
  276. data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +0 -590
  277. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +0 -249
  278. data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +0 -21
  279. data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +0 -24
  280. data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +0 -16
  281. data/spec/lib/flapjack/redis_pool_spec.rb +0 -29
  282. data/spec/service_consumers/pacts/flapjack-diner_v1.0.json +0 -4702
  283. data/tasks/entities.rake +0 -151
  284. data/tasks/profile.rake +0 -282
  285. data/tmp/acknowledge.rb +0 -13
  286. data/tmp/create_config_yaml.rb +0 -16
  287. data/tmp/create_event_ok.rb +0 -30
  288. data/tmp/create_event_unknown.rb +0 -30
  289. data/tmp/create_events_failure.rb +0 -34
  290. data/tmp/create_events_ok.rb +0 -32
  291. data/tmp/create_events_ok_fail_ack_ok.rb +0 -53
  292. data/tmp/create_events_ok_failure.rb +0 -41
  293. data/tmp/create_events_ok_failure_ack.rb +0 -53
  294. data/tmp/dummy_contacts.json +0 -43
  295. data/tmp/dummy_entities.json +0 -37
  296. data/tmp/generate_nagios_test_hosts.rb +0 -16
  297. data/tmp/notification_rules.rb +0 -73
  298. data/tmp/parse_config_yaml.rb +0 -7
  299. data/tmp/redis_find_spurious_unknown_states.rb +0 -52
  300. data/tmp/test_json_post.rb +0 -19
  301. data/tmp/test_notification_rules_api.rb +0 -171
@@ -1,12 +1,15 @@
1
+ <% check = @alert.check -%>
2
+ <% type = @alert.type -%>
1
3
  <%= @alert.type_sentence_case -%>
2
- : <% if @ack_str %><%= @ack_str %>, <% end -%>
3
- "<%= @alert.check %>" on <%= @alert.entity -%>
4
- <% unless ['acknowledgement', 'test'].include?(@alert.type) -%>
4
+ : <% unless @ack_str.nil? || @ack_str.empty? %><%= @ack_str %>, <% end -%>
5
+ <%= '"' + check.name + '"' -%>
6
+ <% unless ['acknowledgement', 'test'].include?(type) -%>
5
7
  is <%= @alert.state_title_case -%>
6
8
  <% end -%>
7
- <% if ['acknowledgement'].include?(@alert.type) -%>
9
+ <% if 'acknowledgement'.eql?(type) -%>
8
10
  has been acknowledged, unscheduled maintenance created for <%= time_period_in_words(@alert.acknowledgement_duration) -%>
9
11
  <% end -%>
10
- <% if @alert.summary && !@alert.summary.empty? -%>
11
- , <%= @alert.summary -%>
12
+ <% summary = @alert.summary -%>
13
+ <% unless summary.nil? || summary.empty? -%>
14
+ , <%= summary -%>
12
15
  <% end -%>
@@ -3,27 +3,40 @@
3
3
  # A HTTP-based API server, which provides queries to determine the status of
4
4
  # entities and the checks that are reported against them.
5
5
  #
6
- # There's a matching flapjack-diner gem at https://github.com/flapjack/flapjack-diner
6
+ # There's a matching flapjack-diner gem at https://github.com/flpjck/flapjack-diner
7
7
  # which consumes data from this API.
8
8
 
9
9
  require 'time'
10
10
 
11
- require 'rack/fiber_pool'
12
11
  require 'sinatra/base'
13
12
 
14
- require 'flapjack/rack_logger'
15
- require 'flapjack/redis_pool'
13
+ require 'active_support/inflector'
16
14
 
17
- require 'flapjack/gateways/jsonapi/rack/json_params_parser'
15
+ require 'swagger/blocks'
18
16
 
19
- require 'flapjack/gateways/jsonapi/check_methods'
20
- require 'flapjack/gateways/jsonapi/contact_methods'
21
- require 'flapjack/gateways/jsonapi/entity_methods'
22
- require 'flapjack/gateways/jsonapi/medium_methods'
23
- require 'flapjack/gateways/jsonapi/metrics_methods'
24
- require 'flapjack/gateways/jsonapi/notification_rule_methods'
25
- require 'flapjack/gateways/jsonapi/pagerduty_credential_methods'
26
- require 'flapjack/gateways/jsonapi/report_methods'
17
+ require 'flapjack'
18
+ require 'flapjack/utility'
19
+
20
+ require 'flapjack/data/acknowledgement'
21
+ require 'flapjack/data/check'
22
+ require 'flapjack/data/contact'
23
+ require 'flapjack/data/medium'
24
+ require 'flapjack/data/metrics'
25
+ require 'flapjack/data/rule'
26
+ require 'flapjack/data/scheduled_maintenance'
27
+ require 'flapjack/data/statistic'
28
+ require 'flapjack/data/state'
29
+ require 'flapjack/data/tag'
30
+ require 'flapjack/data/test_notification'
31
+ require 'flapjack/data/unscheduled_maintenance'
32
+
33
+ require 'flapjack/gateways/jsonapi/middleware/array_param_fixer'
34
+ require 'flapjack/gateways/jsonapi/middleware/json_params_parser'
35
+ require 'flapjack/gateways/jsonapi/middleware/request_timestamp'
36
+
37
+ %w[headers miscellaneous resources serialiser swagger_docs].each do |helper|
38
+ require "flapjack/gateways/jsonapi/helpers/#{helper}"
39
+ end
27
40
 
28
41
  module Flapjack
29
42
 
@@ -33,207 +46,104 @@ module Flapjack
33
46
 
34
47
  include Flapjack::Utility
35
48
 
36
- JSON_REQUEST_MIME_TYPES = ['application/vnd.api+json', 'application/json', 'application/json-patch+json']
37
- # http://www.iana.org/assignments/media-types/application/vnd.api+json
38
- JSONAPI_MEDIA_TYPE = 'application/vnd.api+json'
39
- # http://tools.ietf.org/html/rfc6902
40
- JSON_PATCH_MEDIA_TYPE = 'application/json-patch+json'
41
-
42
- class ContactNotFound < RuntimeError
43
- attr_reader :contact_id
44
- def initialize(contact_id)
45
- @contact_id = contact_id
46
- end
47
- end
48
-
49
- class ContactsNotFound < RuntimeError
50
- attr_reader :contact_ids
51
- def initialize(contact_ids)
52
- @contact_ids = contact_ids
53
- end
54
- end
55
-
56
- class NotificationRuleNotFound < RuntimeError
57
- attr_reader :notification_rule_id
58
- def initialize(notification_rule_id)
59
- @notification_rule_id = notification_rule_id
60
- end
61
- end
62
-
63
- class NotificationRulesNotFound < RuntimeError
64
- attr_reader :notification_rule_ids
65
- def initialize(notification_rule_ids)
66
- @notification_rule_ids = notification_rule_ids
67
- end
68
- end
69
-
70
- class EntityNotFound < RuntimeError
71
- attr_reader :entity
72
- def initialize(entity)
73
- @entity = entity
74
- end
75
- end
76
-
77
- class EntitiesNotFound < RuntimeError
78
- attr_reader :entity_ids
79
- def initialize(entity_ids)
80
- @entity_ids = entity_ids
81
- end
82
- end
83
-
84
- class EntityCheckNotFound < RuntimeError
85
- attr_reader :entity, :check
86
- def initialize(entity, check)
87
- @entity = entity
88
- @check = check
89
- end
90
- end
91
-
92
- class EntityChecksNotFound < RuntimeError
93
- attr_reader :entity_checks
94
- def initialize(entity_checks)
95
- @entity_checks = entity_checks
96
- end
97
- end
98
-
99
- class ResourceLocked < RuntimeError
100
- attr_reader :resource
101
- def initialize(resource)
102
- @resource = resource
103
- end
104
- end
49
+ # TODO clean up media type handling for variable character sets
50
+ # append charset in use
105
51
 
106
- set :dump_errors, false
52
+ # http://jsonapi.org/extensions/bulk/
53
+ # http://www.iana.org/assignments/media-types/application/vnd.api+json
54
+ JSONAPI_MEDIA_TYPE = 'application/vnd.api+json'
55
+ JSONAPI_MEDIA_TYPE_BULK = 'application/vnd.api+json; ext=bulk'
56
+
57
+ # # http://tools.ietf.org/html/rfc6902
58
+ # JSON_PATCH_MEDIA_TYPE = 'application/json-patch+json; charset=utf-8'
59
+
60
+ RESOURCE_CLASSES = [
61
+ Flapjack::Data::Acknowledgement,
62
+ Flapjack::Data::Check,
63
+ Flapjack::Data::Contact,
64
+ Flapjack::Data::Medium,
65
+ Flapjack::Data::Rule,
66
+ Flapjack::Data::ScheduledMaintenance,
67
+ Flapjack::Data::State,
68
+ Flapjack::Data::Statistic,
69
+ Flapjack::Data::Tag,
70
+ Flapjack::Data::TestNotification,
71
+ Flapjack::Data::UnscheduledMaintenance
72
+ ]
73
+
74
+ set :root, File.dirname(__FILE__)
75
+
76
+ set :raise_errors, false
77
+ set :show_exceptions, false
107
78
 
108
79
  set :protection, :except => :path_traversal
109
80
 
110
- @rescue_exception = Proc.new {|env, e|
81
+ use Flapjack::Gateways::JSONAPI::Middleware::RequestTimestamp
82
+ use ::Rack::MethodOverride
83
+ use Flapjack::Gateways::JSONAPI::Middleware::ArrayParamFixer
84
+ use Flapjack::Gateways::JSONAPI::Middleware::JsonParamsParser
111
85
 
112
- rescue_error = Proc.new {|status, exception, request_info, *msg|
113
- if !msg || msg.empty?
114
- trace = exception.backtrace.join("\n")
115
- msg = "#{exception.class} - #{exception.message}"
116
- msg_str = "#{msg}\n#{trace}"
117
- else
118
- msg_str = msg.join(", ")
119
- end
120
- case
121
- when status < 500
122
- @logger.warn "Error: #{msg_str}"
123
- else
124
- @logger.error "Error: #{msg_str}"
125
- end
86
+ class << self
126
87
 
127
- response_body = Flapjack.dump_json(:errors => msg)
88
+ @@lock = Monitor.new
128
89
 
129
- query_string = (request_info[:query_string].respond_to?(:length) &&
130
- request_info[:query_string].length > 0) ? "?#{request_info[:query_string]}" : ""
131
- if @logger.debug?
132
- @logger.debug("Returning #{status} for #{request_info[:request_method]} " +
133
- "#{request_info[:path_info]}#{query_string}, body: #{response_body}")
134
- elsif @logger.info?
135
- @logger.info("Returning #{status} for #{request_info[:request_method]} " +
136
- "#{request_info[:path_info]}#{query_string}")
137
- end
90
+ def start
91
+ Flapjack.logger.info "starting jsonapi - class"
138
92
 
139
- headers = if 'DELETE'.eql?(request_info[:request_method])
140
- # not set by default for delete, but the error structure is JSON
141
- {'Content-Type' => "#{JSONAPI_MEDIA_TYPE}; charset=#{Encoding.default_external}"}
142
- else
143
- {}
93
+ if access_log = (@config && @config['access_log'])
94
+ unless File.directory?(File.dirname(access_log))
95
+ raise "Parent directory for log file #{access_log} doesn't exist"
96
+ end
97
+
98
+ @access_log = ::Logger.new(@config['access_log'])
99
+ use Rack::CommonLogger, @access_log
144
100
  end
145
101
 
146
- [status, headers, response_body]
147
- }
148
-
149
- request_info = {
150
- :path_info => env['REQUEST_PATH'],
151
- :request_method => env['REQUEST_METHOD'],
152
- :query_string => env['QUERY_STRING']
153
- }
154
- case e
155
- when Flapjack::Gateways::JSONAPI::ContactNotFound
156
- rescue_error.call(404, e, request_info, "could not find contact '#{e.contact_id}'")
157
- when Flapjack::Gateways::JSONAPI::ContactsNotFound
158
- rescue_error.call(404, e, request_info, "could not find contacts '" + e.contact_ids.join(', ') + "'")
159
- when Flapjack::Gateways::JSONAPI::NotificationRuleNotFound
160
- rescue_error.call(404, e, request_info,"could not find notification rule '#{e.notification_rule_id}'")
161
- when Flapjack::Gateways::JSONAPI::NotificationRulesNotFound
162
- rescue_error.call(404, e, request_info, "could not find notification rules '" + e.notification_rule_ids.join(', ') + "'")
163
- when Flapjack::Gateways::JSONAPI::EntityNotFound
164
- rescue_error.call(404, e, request_info, "could not find entity '#{e.entity}'")
165
- when Flapjack::Gateways::JSONAPI::EntitiesNotFound
166
- entity_ids = "'" + e.entity_ids.join("', '") + "'"
167
- rescue_error.call(404, e, request_info, "could not find entities: #{entity_ids}")
168
- when Flapjack::Gateways::JSONAPI::EntityCheckNotFound
169
- rescue_error.call(404, e, request_info, "could not find entity check '#{e.check}'")
170
- when Flapjack::Gateways::JSONAPI::EntityChecksNotFound
171
- checks = "'" + e.entity_checks.join("', '") + "'"
172
- rescue_error.call(404, e, request_info, "could not find entity checks: #{checks}")
173
- when Flapjack::Gateways::JSONAPI::ResourceLocked
174
- rescue_error.call(423, e, request_info, "unable to obtain lock for resource '#{e.resource}'")
175
- else
176
- rescue_error.call(500, e, request_info)
177
102
  end
178
- }
179
- use ::Rack::FiberPool, :size => 25, :rescue_exception => @rescue_exception
180
103
 
181
- use ::Rack::MethodOverride
182
- use Flapjack::Gateways::JSONAPI::Rack::JsonParamsParser
183
-
184
- class << self
185
- def start
186
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2, :logger => @logger)
187
-
188
- @logger.info "starting jsonapi - class"
189
-
190
- if @config && @config['access_log']
191
- access_logger = Flapjack::AsyncLogger.new(@config['access_log'])
192
- use Flapjack::CommonLogger, access_logger
104
+ def media_type_produced(options = {})
105
+ unless options[:with_charset].is_a?(TrueClass)
106
+ return 'application/vnd.api+json; supported-ext=bulk'
193
107
  end
194
108
 
195
- @base_url = @config['base_url']
196
- dummy_url = "http://api.example.com"
197
- if @base_url
198
- @base_url = $1 if @base_url.match(/^(.+)\/$/)
199
- else
200
- @logger.error "base_url must be a valid http or https URI (not configured), setting to dummy value (#{dummy_url})"
201
- # FIXME: at this point I'd like to stop this pikelet without bringing down the whole
202
- @base_url = dummy_url
203
- end
204
- if (@base_url =~ /^#{URI::regexp(%w(http https))}$/).nil?
205
- @logger.error "base_url must be a valid http or https URI (#{@base_url}), setting to dummy value (#{dummy_url})"
206
- # FIXME: at this point I'd like to stop this pikelet without bringing down the whole
207
- # flapjack process
208
- # For now, set a dummy value
209
- @base_url = dummy_url
109
+ media_type = nil
110
+ @@lock.synchronize do
111
+ encoding = Encoding.default_external
112
+ media_type = if encoding.nil?
113
+ 'application/vnd.api+json; supported-ext=bulk'
114
+ else
115
+ "application/vnd.api+json; supported-ext=bulk; charset=#{encoding.name.downcase}"
116
+ end
210
117
  end
118
+ media_type
211
119
  end
212
120
  end
213
121
 
214
- def redis
215
- self.class.instance_variable_get('@redis')
216
- end
217
-
218
- def logger
219
- self.class.instance_variable_get('@logger')
122
+ def config
123
+ self.class.instance_variable_get("@config")
220
124
  end
221
125
 
222
- def base_url
223
- self.class.instance_variable_get('@base_url')
126
+ def media_type_produced(options = {})
127
+ self.class.media_type_produced(options)
224
128
  end
225
129
 
226
130
  before do
131
+ # needs to be done per-thread...
132
+ Flapjack.configure_log('jsonapi', config['logger'])
133
+
134
+ # ... as does this
135
+ Zermelo.redis ||= Flapjack.redis
136
+
227
137
  input = nil
228
138
  query_string = (request.query_string.respond_to?(:length) &&
229
- (request.query_string.length > 0)) ? "?#{request.query_string}" : ""
230
- if logger.debug?
139
+ request.query_string.length > 0) ? "?#{request.query_string}" : ""
140
+ if Flapjack.logger.debug?
231
141
  input = env['rack.input'].read
232
- logger.debug("#{request.request_method} #{request.path_info}#{query_string} Headers: #{headers.inspect}, Body: #{input}")
233
- elsif logger.info?
142
+ Flapjack.logger.debug("#{request.request_method} #{request.path_info}#{query_string} Headers: #{headers.inspect}, Body: #{input}")
143
+ elsif Flapjack.logger.info?
234
144
  input = env['rack.input'].read
235
145
  input_short = input.gsub(/\n/, '').gsub(/\s+/, ' ')
236
- logger.info("#{request.request_method} #{request.path_info}#{query_string} #{input_short[0..80]}")
146
+ Flapjack.logger.info("#{request.request_method} #{request.path_info}#{query_string} #{input_short[0..80]}")
237
147
  end
238
148
  env['rack.input'].rewind unless input.nil?
239
149
  end
@@ -243,7 +153,7 @@ module Flapjack
243
153
 
244
154
  query_string = (request.query_string.respond_to?(:length) &&
245
155
  request.query_string.length > 0) ? "?#{request.query_string}" : ""
246
- if logger.debug?
156
+ if Flapjack.logger.debug?
247
157
  body_debug = case
248
158
  when response.body.respond_to?(:each)
249
159
  response.body.each_with_index {|r, i| "body[#{i}]: #{r}"}.join(', ')
@@ -251,181 +161,41 @@ module Flapjack
251
161
  response.body.to_s
252
162
  end
253
163
  headers_debug = response.headers.to_s
254
- logger.debug("Returning #{response.status} for #{request.request_method} " +
164
+ Flapjack.logger.debug("Returning #{response.status} for #{request.request_method} " +
255
165
  "#{request.path_info}#{query_string}, headers: #{headers_debug}, body: #{body_debug}")
256
- elsif logger.info?
257
- logger.info("Returning #{response.status} for #{request.request_method} " +
166
+ elsif Flapjack.logger.info?
167
+ Flapjack.logger.info("Returning #{response.status} for #{request.request_method} " +
258
168
  "#{request.path_info}#{query_string}")
259
169
  end
260
170
  end
261
171
 
262
- module Helpers
263
-
264
- def cors_headers
265
- allow_headers = %w(* Content-Type Accept AUTHORIZATION Cache-Control)
266
- allow_methods = %w(GET POST PUT PATCH DELETE OPTIONS)
267
- expose_headers = %w(Cache-Control Content-Language Content-Type Expires Last-Modified Pragma)
268
- cors_headers = {
269
- 'Access-Control-Allow-Origin' => '*',
270
- 'Access-Control-Allow-Methods' => allow_methods.join(', '),
271
- 'Access-Control-Allow-Headers' => allow_headers.join(', '),
272
- 'Access-Control-Expose-Headers' => expose_headers.join(', '),
273
- 'Access-Control-Max-Age' => '1728000'
274
- }
275
- headers(cors_headers)
276
- end
277
-
278
- def err(status, *msg)
279
- msg_str = msg.join(", ")
280
- logger.info "Error: #{msg_str}"
281
- [status, {}, Flapjack.dump_json(:errors => msg)]
282
- end
283
-
284
- def charset_for_content_type(ct)
285
- "#{ct}; charset=#{Encoding.default_external}"
286
- end
287
-
288
- def is_json_request?
289
- Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type.split(/\s*[;,]\s*/, 2).first)
290
- end
291
-
292
- def is_jsonapi_request?
293
- return false if request.content_type.nil?
294
- 'application/vnd.api+json'.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first)
295
- end
296
-
297
- def is_jsonpatch_request?
298
- return false if request.content_type.nil?
299
- 'application/json-patch+json'.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first)
300
- end
301
-
302
- def wrapped_params(name, error_on_nil = true)
303
- result = params[name.to_sym]
304
- if result.nil?
305
- if error_on_nil
306
- logger.debug("No '#{name}' object found in the following supplied JSON:")
307
- logger.debug(request.body.is_a?(StringIO) ? request.body.read : request.body)
308
- halt err(403, "No '#{name}' object received")
309
- else
310
- result = [{}]
311
- end
312
- end
313
- unless result.is_a?(Array)
314
- halt err(403, "The received '#{name}'' object is not an Array")
315
- end
316
- result
317
- end
318
-
319
- def find_contact(contact_id)
320
- contact = Flapjack::Data::Contact.find_by_id(contact_id, :logger => logger, :redis => redis)
321
- raise Flapjack::Gateways::JSONAPI::ContactNotFound.new(contact_id) if contact.nil?
322
- contact
323
- end
324
-
325
- def find_rule(rule_id)
326
- rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :logger => logger, :redis => redis)
327
- raise Flapjack::Gateways::JSONAPI::NotificationRuleNotFound.new(rule_id) if rule.nil?
328
- rule
329
- end
330
-
331
- def find_tags(tags)
332
- halt err(400, "no tags given") if tags.nil? || tags.empty?
333
- tags
334
- end
335
-
336
- def find_entity(entity_name)
337
- entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
338
- raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_name) if entity.nil?
339
- entity
340
- end
341
-
342
- def find_entity_by_id(entity_id)
343
- entity = Flapjack::Data::Entity.find_by_id(entity_id, :redis => redis)
344
- raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_id) if entity.nil?
345
- entity
346
- end
347
-
348
- def find_entity_check(entity, check_name)
349
- entity_check = Flapjack::Data::EntityCheck.for_entity(entity, check_name, :redis => redis)
350
- raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity.name, check_name) if entity_check.nil?
351
- entity_check
352
- end
353
-
354
- def find_entity_check_by_name(entity_name, check_name)
355
- entity_check = Flapjack::Data::EntityCheck.for_entity_name(entity_name, check_name, :redis => redis)
356
- raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity_name, check_name) if entity_check.nil?
357
- entity_check
358
- end
359
-
360
- def apply_json_patch(object_path, &block)
361
- ops = params[:ops]
362
-
363
- if ops.nil? || !ops.is_a?(Array)
364
- halt err(400, "Invalid JSON-Patch request")
365
- end
366
-
367
- ops.each do |operation|
368
- linked = nil
369
- property = nil
370
-
371
- op = operation['op']
372
- operation['path'] =~ /\A\/#{object_path}\/0\/([^\/]+)(?:\/([^\/]+)(?:\/([^\/]+))?)?\z/
373
- if 'links'.eql?($1)
374
- linked = $2
375
-
376
- value = case op
377
- when 'add'
378
- operation['value']
379
- when 'remove'
380
- $3
381
- end
382
- elsif 'replace'.eql?(op)
383
- property = $1
384
- value = operation['value']
385
- else
386
- next
387
- end
388
-
389
- yield(op, property, linked, value)
390
- end
391
- end
392
-
393
- # NB: casts to UTC before converting to a timestamp
394
- def validate_and_parsetime(value)
395
- return unless value
396
- Time.iso8601(value).getutc.to_i
397
- rescue ArgumentError => e
398
- logger.error "Couldn't parse time from '#{value}'"
399
- nil
400
- end
401
-
402
- end
403
-
404
172
  options '*' do
405
173
  cors_headers
406
174
  204
407
175
  end
408
176
 
177
+ # FIXME enforce that Accept header must allow defined return type for the method
178
+
409
179
  # The following catch-all routes act as impromptu filters for their method types
410
180
  get '*' do
411
- content_type charset_for_content_type(JSONAPI_MEDIA_TYPE)
412
181
  cors_headers
182
+ content_type media_type_produced(:with_charset => true)
413
183
  pass
414
184
  end
415
185
 
416
186
  # bare 'params' may have splat/captures for regex route, see
417
187
  # https://github.com/sinatra/sinatra/issues/453
418
188
  post '*' do
419
- halt(405) unless request.params.empty? || is_json_request? || is_jsonapi_request?
420
- content_type charset_for_content_type(JSONAPI_MEDIA_TYPE)
189
+ halt(405) unless request.params.empty? || is_jsonapi_request?
421
190
  cors_headers
191
+ content_type media_type_produced(:with_charset => true)
422
192
  pass
423
193
  end
424
194
 
425
195
  patch '*' do
426
- halt(405) unless is_jsonpatch_request?
427
- content_type charset_for_content_type(JSONAPI_MEDIA_TYPE)
196
+ halt(405) unless request.params.empty? || is_jsonapi_request?
428
197
  cors_headers
198
+ content_type media_type_produced(:with_charset => true)
429
199
  pass
430
200
  end
431
201
 
@@ -434,21 +204,65 @@ module Flapjack
434
204
  pass
435
205
  end
436
206
 
437
- register Flapjack::Gateways::JSONAPI::CheckMethods
438
- register Flapjack::Gateways::JSONAPI::ContactMethods
439
- register Flapjack::Gateways::JSONAPI::EntityMethods
440
- register Flapjack::Gateways::JSONAPI::MediumMethods
441
- register Flapjack::Gateways::JSONAPI::MetricsMethods
442
- register Flapjack::Gateways::JSONAPI::NotificationRuleMethods
443
- register Flapjack::Gateways::JSONAPI::PagerdutyCredentialMethods
444
- register Flapjack::Gateways::JSONAPI::ReportMethods
445
-
446
- not_found do
447
- err(404, "not routable")
207
+ include Swagger::Blocks
208
+ include Flapjack::Gateways::JSONAPI::Helpers::SwaggerDocs
209
+
210
+ # hacky, but trying to avoid too much boilerplate -- association paths
211
+ # must be before resource ones to avoid greedy path captures
212
+ %w[metrics association_post resource_post association_get resource_get
213
+ association_patch resource_patch association_delete
214
+ resource_delete].each do |method|
215
+
216
+ require "flapjack/gateways/jsonapi/methods/#{method}"
217
+ eval "register Flapjack::Gateways::JSONAPI::Methods::#{method.camelize}"
448
218
  end
449
219
 
450
- end
220
+ Flapjack::Gateways::JSONAPI::RESOURCE_CLASSES.each do |resource_class|
221
+ endpoint = resource_class.short_model_name.plural
222
+ swagger_wrappers(endpoint, resource_class)
223
+ end
451
224
 
452
- end
225
+ SWAGGERED_CLASSES = [self] + Flapjack::Gateways::JSONAPI::RESOURCE_CLASSES +
226
+ [Flapjack::Data::Metrics]
453
227
 
228
+ get '/doc' do
229
+ Flapjack.dump_json(Swagger::Blocks.build_root_json(SWAGGERED_CLASSES))
230
+ end
231
+
232
+ # error Zermelo::LockNotAcquired do
233
+ # # TODO
234
+ # end
235
+
236
+ error Zermelo::Records::Errors::RecordInvalid do
237
+ e = env['sinatra.error']
238
+ err(403, *e.record.errors.full_messages)
239
+ end
240
+
241
+ error Zermelo::Records::Errors::RecordNotSaved do
242
+ e = env['sinatra.error']
243
+ err(403, *e.record.errors.full_messages)
244
+ end
245
+
246
+ error Zermelo::Records::Errors::RecordNotFound do
247
+ e = env['sinatra.error']
248
+ type = e.klass.name.split('::').last
249
+ err(404, "could not find #{type} record, id: '#{e.id}'")
250
+ end
251
+
252
+ error Zermelo::Records::Errors::RecordsNotFound do
253
+ e = env['sinatra.error']
254
+ type = e.klass.name.split('::').last
255
+ err_ids = e.ids.join("', '")
256
+ err(404, "could not find #{type} records, ids: '#{err_ids}'")
257
+ end
258
+
259
+ error do
260
+ e = env['sinatra.error']
261
+ # trace = e.backtrace.join("\n")
262
+ # puts trace
263
+ err(response.status, "#{e.class} - #{e.message}")
264
+ end
265
+
266
+ end
267
+ end
454
268
  end