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
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sinatra/base'
4
+
5
+ # klass and type are set for those relationships that call methods rather
6
+ # than regular Zermelo associations. Maybe this should be a separate descriptor type?
7
+
8
+ module Flapjack
9
+ module Gateways
10
+ class JSONAPI < Sinatra::Base
11
+ module Data
12
+ class JoinDescriptor
13
+ attr_reader :post, :get, :patch, :delete, :number, :link, :includable, :klass, :descriptions
14
+ attr_accessor :association_data
15
+
16
+ def initialize(opts = {})
17
+ @association_data = nil
18
+ %w{post get patch delete number link includable type klass callback_classes descriptions}.each do |a|
19
+ instance_variable_set("@#{a}", opts[a.to_sym])
20
+ end
21
+ @callback_classes ||= []
22
+ end
23
+
24
+ def lock_klasses
25
+ if @association_data.nil?
26
+ [@klass] | @callback_classes
27
+ else
28
+ [@association_data.data_klass] |
29
+ @association_data.related_klasses | @callback_classes
30
+ end
31
+ end
32
+
33
+ def data_klass
34
+ @association_data.nil? ? @klass : @association_data.data_klass
35
+ end
36
+
37
+ def type
38
+ @association_data.nil? ? @type : data_klass.short_model_name.singular
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sinatra/base'
4
+
5
+ module Flapjack
6
+ module Gateways
7
+ class JSONAPI < Sinatra::Base
8
+ module Data
9
+ class MethodDescriptor
10
+ attr_reader :attributes, :lock_klasses, :descriptions
11
+
12
+ def initialize(opts = {})
13
+ %w{attributes lock_klasses descriptions}.each do |a|
14
+ instance_variable_set("@#{a}", opts[a.to_sym])
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Flapjack
4
+ module Gateways
5
+ class JSONAPI < Sinatra::Base
6
+ module Helpers
7
+ module Headers
8
+
9
+ def cors_headers
10
+ allow_headers = %w(* Content-Type Accept Authorization Cache-Control)
11
+ allow_methods = %w(GET POST PATCH DELETE OPTIONS)
12
+ expose_headers = %w(Cache-Control Content-Language Content-Type Expires Last-Modified Pragma)
13
+ ch = {
14
+ 'Access-Control-Allow-Origin' => '*',
15
+ 'Access-Control-Allow-Methods' => allow_methods.join(', '),
16
+ 'Access-Control-Allow-Headers' => allow_headers.join(', '),
17
+ 'Access-Control-Expose-Headers' => expose_headers.join(', '),
18
+ 'Access-Control-Max-Age' => '1728000'
19
+ }
20
+ ch.each_pair {|k, v| response[k] = v}
21
+ end
22
+
23
+ def err(status_code, *msg)
24
+ Flapjack.logger.info "Error: #{msg.inspect}"
25
+
26
+ if 'DELETE'.eql?(request.request_method)
27
+ # not set by default for delete, but the error structure is JSON
28
+ response['Content-Type'] = media_type_produced(:with_charset => true)
29
+ end
30
+
31
+ # TODO include more relevant data
32
+ error_data = {
33
+ :errors => msg.collect {|m|
34
+ {
35
+ :status => status_code.to_s,
36
+ :detail => m
37
+ }
38
+ }
39
+ }
40
+
41
+ # Rack::CommonLogger doesn't log requests which result in exceptions.
42
+ # If you want something done properly, do it yourself...
43
+ result = Flapjack.dump_json(error_data)
44
+ access_log = self.class.instance_variable_get('@middleware').detect {|mw|
45
+ mw.first.is_a?(::Rack::CommonLogger)
46
+ }
47
+ unless access_log.nil?
48
+ access_log.first.send(:log, status_code,
49
+ ::Rack::Utils::HeaderHash.new(headers), result,
50
+ env['request_timestamp'])
51
+ end
52
+ [status_code, headers, result]
53
+ end
54
+
55
+ def is_jsonapi_request?
56
+ return false if request.content_type.nil?
57
+ Flapjack::Gateways::JSONAPI::JSONAPI_MEDIA_TYPE.eql?(request.content_type.split(/\s*[;,]\s*/, 2).first)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Flapjack
4
+ module Gateways
5
+ class JSONAPI < Sinatra::Base
6
+ module Helpers
7
+ module Miscellaneous
8
+
9
+ def request_url
10
+ rurl = request.url.split('?').first
11
+ rurl << "?#{request.params.to_param}" unless request.params.empty?
12
+ rurl
13
+ end
14
+
15
+ def wrapped_params(options = {})
16
+ result = params['data']
17
+
18
+ if result.nil?
19
+ return([nil, false]) if options[:allow_nil].is_a?(TrueClass)
20
+ logger.debug("No 'data' object found in the supplied JSON:")
21
+ logger.debug(request.body.is_a?(StringIO) ? request.body.read : request.body)
22
+ halt(err(403, "Data object must be provided"))
23
+ end
24
+
25
+ ext_bulk = !(request.content_type =~ /^(?:.+;)?\s*ext=bulk(?:\s*;\s*.+)?$/).nil?
26
+ case result
27
+ when Array
28
+ halt(err(406, "JSONAPI Bulk Extension not set in headers")) unless ext_bulk
29
+ [result, false]
30
+ when Hash
31
+ halt(err(406, "JSONAPI Bulk Extension was set in headers")) if ext_bulk
32
+ [[result], true]
33
+ else
34
+ halt(err(403, "The received data object is not an Array or a Hash"))
35
+ end
36
+ end
37
+
38
+ def wrapped_link_params(options = {})
39
+ result = params['data']
40
+
41
+ assoc = options[:association]
42
+ number = assoc.number
43
+
44
+ if result.nil?
45
+ return([nil, false]) if :singular.eql?(number) && options[:allow_nil].is_a?(TrueClass)
46
+ logger.debug("No 'data' object found in the following supplied JSON:")
47
+ logger.debug(request.body.is_a?(StringIO) ? request.body.read : request.body)
48
+ halt err(403, "No 'data' object received")
49
+ end
50
+
51
+ case number
52
+ when :multiple
53
+ unless result.is_a?(Array) && result.all? {|r| assoc.type.eql?(r['type']) && !r['id'].nil? }
54
+ halt(err(403, "Malformed data for multiple link endpoint"))
55
+ end
56
+ [result.map {|r| r[:id]}, false]
57
+ when :singular
58
+ unless result.is_a?(Hash) && assoc.type.eql?(result['type'])
59
+ halt(err(403, "Malformed data for singular link endpoint"))
60
+ end
61
+ [[result[:id]], true]
62
+ else
63
+ halt(err(403, "Invalid endpoint definition"))
64
+ end
65
+ end
66
+
67
+ def validate_data(data, options = {})
68
+ valid_keys = ['id', 'type', 'attributes', 'relationships']
69
+ valid_attrs = (options[:attributes] || []).map(&:to_s)
70
+ sl = options[:singular_links] ? options[:singular_links].keys.map(&:to_s) : []
71
+ ml = options[:multiple_links] ? options[:multiple_links].keys.map(&:to_s) : []
72
+ all_links = sl + ml
73
+
74
+ # FIXME check error codes against JSONAPI spec
75
+
76
+ data.each do |d|
77
+ invalid_keys = d.keys - valid_keys
78
+ halt(err(403, "Invalid key(s): #{invalid_keys.join(', ')}")) unless invalid_keys.empty?
79
+ if d.has_key?('attributes')
80
+ attrs = d['attributes']
81
+ halt(err(403, "Attributes must be a Hash with String keys")) unless attrs.is_a?(Hash) &&
82
+ attrs.keys.all? {|k| k.is_a?(String)}
83
+ invalid_attrs = attrs.keys - valid_attrs
84
+ halt(err(403, "Invalid attribute(s): #{invalid_attrs.join(', ')}")) unless invalid_attrs.empty?
85
+ end
86
+ if d.has_key?('relationships')
87
+ links = d['relationships']
88
+ halt(err(403, "Relationships(s) must be a Hash with String keys")) unless links.is_a?(Hash) &&
89
+ links.keys.all? {|k| k.is_a?(String)}
90
+ invalid_links = links.keys - all_links
91
+ halt(err(404, "Unknown relationship type(s): #{invalid_links.join(', ')}")) unless invalid_links.empty?
92
+ links.each_pair do |k, v|
93
+ halt(err(403, "Related data for '#{k}' must be a Hash with String keys")) unless v.is_a?(Hash) && v.keys.all? {|vk| vk.is_a?(String)}
94
+ halt(err(403, "Related data for '#{k}' must have data references")) unless links[k].has_key?('data')
95
+
96
+ if sl.include?(k)
97
+ halt(err(403, "Data reference for '#{k}' must be a Hash with 'id' & 'type' String values")) unless links[k]['data'].is_a?(Hash) &&
98
+ v['data'].has_key?('id') &&
99
+ v['data'].has_key?('type') &&
100
+ v['data']['id'].is_a?(String) &&
101
+ v['data']['type'].is_a?(String)
102
+
103
+ unless options[:singular_links][k.to_sym].type == v['data']['type']
104
+ halt(err(403, "Related '#{k}' has wrong type #{v['data']['type']}"))
105
+ end
106
+ else
107
+ halt(err(403, "Data reference for '#{k}' must be an Array of Hashes with 'id' & 'type' String values")) unless v['data'].is_a?(Array) &&
108
+ v['data'].all? {|l| l.has_key?('id') } &&
109
+ v['data'].all? {|l| l.has_key?('type') } &&
110
+ v['data'].all? {|l| l['id'].is_a?(String) } &&
111
+ v['data'].all? {|l| l['type'].is_a?(String) }
112
+
113
+ t = options[:multiple_links][k.to_sym].type
114
+ bad_type = v['data'].detect {|l| !t.eql?(l['type'])}
115
+ unless bad_type.nil?
116
+ halt(403, "Data reference for '#{k}' has invalid type '#{bad_type}', should be '#{t}")
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def validate_and_parsetime(value)
125
+ return unless value
126
+ Time.iso8601(value).getutc
127
+ rescue ArgumentError
128
+ logger.error "Couldn't parse time from '#{value}'"
129
+ nil
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module Flapjack
6
+ module Gateways
7
+ class JSONAPI < Sinatra::Base
8
+ module Helpers
9
+ module Resources
10
+
11
+ ISO8601_PAT = "(?:[1-9][0-9]*)?[0-9]{4}-" \
12
+ "(?:1[0-2]|0[1-9])-" \
13
+ "(?:3[0-1]|0[1-9]|[1-2][0-9])T" \
14
+ "(?:2[0-3]|[0-1][0-9]):" \
15
+ "[0-5][0-9]:[0-5][0-9](?:\\.[0-9]+)?" \
16
+ "(?:Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?"
17
+
18
+ # TODO refactor some of these methods into a module, included in data/ classes
19
+
20
+ def normalise_json_data(attribute_types, data)
21
+ record_data = data.dup
22
+ attribute_types.each_pair do |name, type|
23
+ t = record_data[name.to_s]
24
+ next unless t.is_a?(String) && :timestamp.eql?(type)
25
+ begin
26
+ record_data[name.to_s] = DateTime.parse(t)
27
+ rescue ArgumentError
28
+ record_data[name.to_s] = nil
29
+ end
30
+ end
31
+ symbolize(record_data)
32
+ end
33
+
34
+ def parse_range_or_value(name, pattern, v, opts = {}, &block)
35
+ rangeable = opts[:rangeable]
36
+
37
+ if rangeable && (v =~ /\A(?:(#{pattern})\.\.(#{pattern})|(#{pattern})\.\.|\.\.(#{pattern}))\z/)
38
+ start = nil
39
+ start_s = Regexp.last_match(1) || Regexp.last_match(3)
40
+ unless start_s.nil?
41
+ start = yield(start_s)
42
+ halt(err(403, "Couldn't parse #{name} '#{start_s}'")) if start.nil?
43
+ end
44
+
45
+ finish = nil
46
+ finish_s = Regexp.last_match(2) || Regexp.last_match(4)
47
+ unless finish_s.nil?
48
+ finish = yield(finish_s)
49
+ halt(err(403, "Couldn't parse #{name} '#{finish_s}'")) if finish.nil?
50
+ end
51
+
52
+ if !start.nil? && !finish.nil? && (start > finish)
53
+ halt(err(403, "Range order cannot be inversed"))
54
+ else
55
+ Zermelo::Filters::IndexRange.new(start, finish, :by_score => true)
56
+ end
57
+
58
+ elsif v =~ /\A#{pattern}\z/
59
+ t = yield(v)
60
+ halt(err(403, "Couldn't parse #{name} '#{v}'")) if t.nil?
61
+ t
62
+ else
63
+ halt(err(403, "Invalid #{name} parameter '#{v}'"))
64
+ end
65
+ end
66
+
67
+ def filter_params(klass, filter)
68
+ attributes = if klass.respond_to?(:jsonapi_methods)
69
+ (klass.jsonapi_methods[:get] || {}).attributes || []
70
+ else
71
+ []
72
+ end
73
+
74
+ attribute_types = klass.attribute_types
75
+
76
+ rangeable_attrs = []
77
+ attrs_by_type = {}
78
+
79
+ klass.send(:with_index_data) do |idx_data|
80
+ idx_data.each do |k, d|
81
+ next unless d.index_klass == ::Zermelo::Associations::RangeIndex
82
+ rangeable_attrs << k
83
+ end
84
+ end
85
+
86
+ Zermelo::ATTRIBUTE_TYPES.keys.each do |at|
87
+ attrs_by_type[at] = attributes.select do |a|
88
+ at.eql?(attribute_types[a])
89
+ end
90
+ end
91
+
92
+ filter.each_with_object({}) do |filter_str, memo|
93
+ k, v = filter_str.split(':', 2)
94
+ halt(err(403, "Single filter parameters must be 'key:value'")) if k.nil? || v.nil?
95
+
96
+ value = if attrs_by_type[:string].include?(k.to_sym) || :id.eql?(k.to_sym)
97
+ if v =~ %r{^/(.+)/$}
98
+ Regexp.new(Regexp.last_match(1))
99
+ elsif v =~ /\|/
100
+ v.split('|')
101
+ else
102
+ v
103
+ end
104
+ elsif attrs_by_type[:boolean].include?(k.to_sym)
105
+ case v.downcase
106
+ when '0', 'f', 'false', 'n', 'no'
107
+ false
108
+ when '1', 't', 'true', 'y', 'yes'
109
+ true
110
+ end
111
+
112
+ elsif attrs_by_type[:timestamp].include?(k.to_sym)
113
+ parse_range_or_value('timestamp', ISO8601_PAT, v, :rangeable => rangeable_attrs.include?(k.to_sym)) {|s|
114
+ begin; DateTime.parse(s); rescue ArgumentError; nil; end
115
+ }
116
+ elsif attrs_by_type[:integer].include?(k.to_sym)
117
+ parse_range_or_value('integer', '\d+', v, :rangeable => rangeable_attrs.include?(k.to_sym)) {|s|
118
+ s.to_i
119
+ }
120
+ elsif attrs_by_type[:float].include?(k.to_sym)
121
+ parse_range_or_value('float', '\d+(\.\d+)?', v, :rangeable => rangeable_attrs.include?(k.to_sym)) {|s|
122
+ s.to_f
123
+ }
124
+ end
125
+
126
+ halt(err(403, "Invalid filter key '#{k}'")) if value.nil?
127
+ memo[k.to_sym] = value
128
+ end
129
+ end
130
+
131
+ def resource_filter_sort(klass, options = {})
132
+ options[:sort] ||= 'id'
133
+ scope = klass
134
+
135
+ unless params[:filter].nil?
136
+ unless params[:filter].is_a?(Array)
137
+ halt(err(403, "Filter parameters must be passed as an Array"))
138
+ end
139
+ filter_ops = filter_params(klass, params[:filter])
140
+ scope = scope.intersect(filter_ops)
141
+ end
142
+
143
+ sort_opts = if params[:sort].nil?
144
+ options[:sort].to_sym
145
+ else
146
+ sort_params = params[:sort].split(',')
147
+
148
+ if sort_params.any? {|sp| sp !~ /^(?:\+|-)/ }
149
+ halt(err(403, "Sort parameters must start with +/-"))
150
+ end
151
+
152
+ sort_params.each_with_object({}) do |sort_param, memo|
153
+ sort_param =~ /^(\+|\-)([a-z_]+)$/i
154
+ rev = Regexp.last_match(1)
155
+ term = Regexp.last_match(2)
156
+ memo[term.to_sym] = (rev == '-') ? :desc : :asc
157
+ end
158
+ end
159
+
160
+ scope = scope.sort(sort_opts)
161
+ scope
162
+ end
163
+
164
+ def paginate_get(dataset, options = {})
165
+ return([[], {}, {}]) if dataset.nil?
166
+
167
+ total = dataset.count
168
+ return([[], {}, {}]) if total == 0
169
+
170
+ page = options[:page].to_i
171
+ page = (page > 0) ? page : 1
172
+
173
+ per_page = options[:per_page].to_i
174
+ per_page = (per_page > 0) ? per_page : 20
175
+
176
+ total_pages = (total.to_f / per_page).ceil
177
+
178
+ pages = set_page_numbers(page, total_pages)
179
+ links = create_links(pages)
180
+ headers['Link'] = create_link_header(links) unless links.empty?
181
+ headers['Total-Count'] = total_pages.to_s
182
+
183
+ [dataset.page(page, :per_page => per_page),
184
+ links,
185
+ {
186
+ :pagination => {
187
+ :page => page,
188
+ :per_page => per_page,
189
+ :total_pages => total_pages,
190
+ :total_count => total
191
+ }
192
+ }
193
+ ]
194
+ end
195
+
196
+ def set_page_numbers(page, total_pages)
197
+ pages = {}
198
+ pages[:first] = 1 # if (total_pages > 1) && (page > 1)
199
+ pages[:prev] = page - 1 if (page > 1)
200
+ pages[:next] = page + 1 if page < total_pages
201
+ pages[:last] = total_pages # if (total_pages > 1) && (page < total_pages)
202
+ pages
203
+ end
204
+
205
+ def create_links(pages)
206
+ url_without_params = request.url.split('?').first
207
+ per_page = params[:per_page] # ? params[:per_page].to_i : 20
208
+
209
+ links = {}
210
+ pages.each do |key, value|
211
+ page_params = {'page' => value }
212
+ page_params.update('per_page' => per_page) unless per_page.nil?
213
+ new_params = request.params.merge(page_params)
214
+ links[key] = "#{url_without_params}?#{new_params.to_query}"
215
+ end
216
+ links
217
+ end
218
+
219
+ def create_link_header(links)
220
+ links.collect {|(k, v)| "<#{v}; rel=\"#{k}\"" }.join(', ')
221
+ end
222
+
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end