webhook.td 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. $ set-arg-default replicas=2
  10. $ set-arg-default default-storage-size=1
  11. # Exercises Webhook sources.
  12. > CREATE CLUSTER webhook_cluster REPLICAS (r1 (SIZE '${arg.default-storage-size}'), r2 (SIZE '${arg.default-storage-size}'));
  13. > CREATE CLUSTER webhook_compute REPLICAS (r1 (SIZE '${arg.default-storage-size}'), r2 (SIZE '${arg.default-storage-size}'));
  14. > CREATE SOURCE webhook_text IN CLUSTER webhook_cluster FROM WEBHOOK
  15. BODY FORMAT TEXT;
  16. > SHOW COLUMNS FROM webhook_text;
  17. name nullable type comment
  18. ------------------------------
  19. body false text ""
  20. > SHOW CREATE SOURCE webhook_text;
  21. materialize.public.webhook_text "CREATE SOURCE materialize.public.webhook_text IN CLUSTER webhook_cluster FROM WEBHOOK BODY FORMAT TEXT;"
  22. > SELECT name, type FROM mz_objects WHERE name = 'webhook_text';
  23. webhook_text source
  24. $ webhook-append database=materialize schema=public name=webhook_text
  25. a
  26. $ webhook-append database=materialize schema=public name=webhook_text
  27. b
  28. $ webhook-append database=materialize schema=public name=webhook_text
  29. c
  30. > SELECT * FROM webhook_text;
  31. a
  32. b
  33. c
  34. > CREATE VIEW webhook_text_ascii_code AS SELECT ascii(body) FROM webhook_text WHERE ascii(body) % 2 = 0;
  35. > SELECT * FROM webhook_text_ascii_code;
  36. 98
  37. > ALTER SOURCE webhook_text RENAME TO webhook_text_renamed;
  38. ! SELECT * FROM webhook_text;
  39. contains: unknown catalog item 'webhook_text'
  40. $ webhook-append database=materialize schema=public name=webhook_text status=404
  41. d
  42. > SELECT * FROM webhook_text_renamed;
  43. a
  44. b
  45. c
  46. $ webhook-append database=materialize schema=public name=webhook_text_renamed
  47. d
  48. > SELECT * FROM webhook_text_renamed;
  49. a
  50. b
  51. c
  52. d
  53. > SELECT * FROM webhook_text_ascii_code;
  54. 98
  55. 100
  56. > CREATE SOURCE webhook_json_with_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  57. BODY FORMAT JSON
  58. INCLUDE HEADERS;
  59. > SHOW COLUMNS FROM webhook_json_with_headers;
  60. name nullable type comment
  61. ----------------------------------
  62. body false jsonb ""
  63. headers false map ""
  64. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=test_drive
  65. {
  66. "hello": "world"
  67. }
  68. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=other
  69. {
  70. "goodbye": "world"
  71. }
  72. > SELECT body FROM webhook_json_with_headers WHERE headers -> 'app' = 'test_drive';
  73. "{\"hello\":\"world\"}"
  74. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=
  75. {
  76. "empty": "world"
  77. }
  78. > SELECT body FROM webhook_json_with_headers WHERE headers -> 'app' = '';
  79. "{\"empty\":\"world\"}"
  80. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=list
  81. [1, 2, 3]
  82. > SELECT body FROM webhook_json_with_headers WHERE headers -> 'app' = 'list';
  83. "[1,2,3]"
  84. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=list2
  85. { "foo": [1, 2, 3] }
  86. > SELECT body FROM webhook_json_with_headers WHERE headers -> 'app' = 'list2';
  87. "{\"foo\":[1,2,3]}"
  88. $ webhook-append database=materialize schema=public name=webhook_json_with_headers content-type=application/json app=string
  89. "Hellö String"
  90. > SELECT body FROM webhook_json_with_headers WHERE headers -> 'app' = 'string';
  91. "\"Hellö String\""
  92. ! SELECT * FROM webhook_json_with_headers;
  93. contains: binary encoding of map types is not implemented
  94. # An invalid body should return a 400.
  95. $ webhook-append database=materialize schema=public name=webhook_json_with_headers status=400 content-type=application/json
  96. invalid-json
  97. # A source that doesn't exist should return a 404.
  98. $ webhook-append database=materialize schema=public name=non_existent_source status=404
  99. x
  100. # Trying to append to an object that isn't a webhook should fail.
  101. > CREATE TABLE not_a_webhook ( a int8 );
  102. $ webhook-append database=materialize schema=public name=not_a_webhook status=404
  103. d
  104. > CREATE SOURCE webhook_bytes IN CLUSTER webhook_cluster FROM WEBHOOK
  105. BODY FORMAT BYTES;
  106. > SHOW COLUMNS FROM webhook_bytes;
  107. name nullable type comment
  108. -------------------------------
  109. body false bytea ""
  110. $ webhook-append database=materialize schema=public name=webhook_bytes
  111. 和製漢語
  112. $ webhook-append database=materialize schema=public name=webhook_bytes
  113. null
  114. $ webhook-append database=materialize schema=public name=webhook_bytes
  115. 123
  116. > SELECT * FROM webhook_bytes;
  117. "\\xe5\\x92\\x8c\\xe8\\xa3\\xbd\\xe6\\xbc\\xa2\\xe8\\xaa\\x9e"
  118. null
  119. 123
  120. > CREATE SOURCE webhook_bytes_with_validation IN CLUSTER webhook_cluster FROM WEBHOOK
  121. BODY FORMAT BYTES
  122. CHECK (
  123. WITH (HEADERS)
  124. headers->'signature' = 'test'
  125. );
  126. $ webhook-append database=materialize schema=public name=webhook_bytes_with_validation signature=test
  127. 123
  128. $ webhook-append database=materialize schema=public name=webhook_bytes_with_validation signature=invalid status=400
  129. 456
  130. > CREATE SOURCE webhook_bytes_with_hmac IN CLUSTER webhook_cluster FROM WEBHOOK
  131. BODY FORMAT BYTES
  132. CHECK (
  133. WITH (HEADERS, BODY)
  134. decode(headers->'x-signature', 'base64') = hmac('body=' || body, 'test_key', 'sha256')
  135. );
  136. $ webhook-append name=webhook_bytes_with_hmac x-signature=HA0rQdPkCiNNNAladA0eTI8x5WZp5z8rBawQHiywznI=
  137. hello world
  138. $ webhook-append name=webhook_bytes_with_hmac x-signature=1cDmmXBhApqXZebb2u6WtdwHc2UtkMf7N11Zjk66wzo=
  139. another_request
  140. $ webhook-append name=webhook_bytes_with_hmac status=400
  141. did_not_include_necessary_header
  142. > SELECT * FROM webhook_bytes_with_hmac;
  143. "hello world"
  144. "another_request"
  145. > CREATE SECRET webhook_secret AS 'shared_key';
  146. > CREATE SOURCE webhook_bytes_with_secret IN CLUSTER webhook_cluster FROM WEBHOOK
  147. BODY FORMAT TEXT
  148. CHECK (
  149. WITH ( HEADERS, BODY BYTES, SECRET webhook_secret BYTES )
  150. decode(headers->'x-signature', 'base64') = hmac(body, webhook_secret, 'sha256')
  151. )
  152. $ webhook-append name=webhook_bytes_with_secret x-signature=VNCe6bTKrlFO46GfiUYR/xFpeZ2H/KbLfR9oJKYAwkc=
  153. using an mz secret
  154. > SELECT * FROM webhook_bytes_with_secret;
  155. "using an mz secret"
  156. > CREATE SOURCE webhook_buildkite IN CLUSTER webhook_cluster FROM WEBHOOK
  157. BODY FORMAT TEXT
  158. CHECK (
  159. WITH (HEADERS, BODY)
  160. decode(split_part(headers->'x-buildkite-signature', 'signature::', 2), 'hex')
  161. =
  162. hmac(split_part(split_part(headers->'x-buildkite-signature', 'timestamp::', 2), ',', 1) || '.' || body, 'test_key', 'sha256')
  163. );
  164. $ webhook-append name=webhook_buildkite x-buildkite-signature=timestamp::42,signature::b610a43432fe965eb8e2a3ce4939a6bafaad3f35583c596e2f7271125a346d95
  165. i hope this works
  166. > SELECT * FROM webhook_buildkite;
  167. "i hope this works"
  168. > CREATE SOURCE webhook_hex IN CLUSTER webhook_cluster FROM WEBHOOK
  169. BODY FORMAT TEXT
  170. CHECK ( WITH (BODY) decode(body, 'hex') = '\x42' );
  171. $ webhook-append name=webhook_hex status=400
  172. # 'z' is an invalid character in hex which causes an evaluation failure.
  173. z
  174. # Can use SECRETs as both Bytes and Strings.
  175. > CREATE SECRET webhook_secret_bytes AS 'this_key_is_bytes';
  176. > CREATE SOURCE webhook_double_validation IN CLUSTER webhook_cluster FROM WEBHOOK
  177. BODY FORMAT TEXT
  178. CHECK (
  179. WITH (
  180. HEADERS,
  181. BODY BYTES,
  182. SECRET webhook_secret,
  183. SECRET webhook_secret_bytes BYTES
  184. )
  185. decode(headers->'x-signature-sha256', 'hex') = hmac(convert_from(body, 'utf-8'), webhook_secret, 'sha256')
  186. AND
  187. decode(headers->'x-signature-md5', 'hex') = hmac(body, webhook_secret_bytes, 'md5')
  188. )
  189. $ webhook-append name=webhook_double_validation x-signature-sha256=20460da764521c155989f9ede00d6047c459c87bca6712eef27f72ae32c62d3f x-signature-md5=c34fd128f787067796212d31fced1881
  190. materialize space monkey
  191. > SELECT * FROM webhook_double_validation;
  192. "materialize space monkey"
  193. # Webhooks should support special characters like a /
  194. > CREATE SOURCE "webhook_with_/" IN CLUSTER webhook_cluster FROM WEBHOOK BODY FORMAT TEXT;
  195. $ webhook-append name=webhook_with_/ status=404
  196. wont_work
  197. $ webhook-append name=webhook_with_%2F
  198. will_work
  199. > SELECT * FROM "webhook_with_/"
  200. "will_work"
  201. > CREATE SOURCE webhook_text_headers_block_list IN CLUSTER webhook_cluster FROM WEBHOOK
  202. BODY FORMAT TEXT
  203. INCLUDE HEADERS (NOT 'accept', NOT 'host')
  204. $ webhook-append name=webhook_text_headers_block_list
  205. foo
  206. > SELECT body, headers::text FROM webhook_text_headers_block_list
  207. foo "{content-length=>3}"
  208. > CREATE SOURCE webhook_text_headers_allow_list IN CLUSTER webhook_cluster FROM WEBHOOK
  209. BODY FORMAT TEXT
  210. INCLUDE HEADERS ('x-random')
  211. $ webhook-append name=webhook_text_headers_allow_list x-random=bar
  212. anotha_one
  213. > SELECT body, headers::text FROM webhook_text_headers_allow_list
  214. anotha_one "{x-random=>bar}"
  215. > CREATE SOURCE webhook_text_filtering_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  216. BODY FORMAT TEXT
  217. INCLUDE HEADER 'x-timestamp' as event_timestamp
  218. INCLUDE HEADER 'x-id' as event_id
  219. INCLUDE HEADERS (NOT 'accept', NOT 'content-length', NOT 'host', NOT 'x-api-key')
  220. CHECK ( WITH (HEADERS) headers->'x-api-key' = 'abc123' )
  221. $ webhook-append name=webhook_text_filtering_headers x-timestamp=100 x-id=a x-api-key=abc123 content-type=text/example
  222. request_1
  223. $ webhook-append name=webhook_text_filtering_headers x-api-key=wrong_key status=400
  224. request_bad
  225. $ webhook-append name=webhook_text_filtering_headers x-api-key=abc123 x-random=foo
  226. request_missing_some_mapped_headers
  227. > SELECT body, headers::text, event_timestamp, event_id FROM webhook_text_filtering_headers
  228. request_1 "{content-type=>text/example,x-id=>a,x-timestamp=>100}" 100 a
  229. request_missing_some_mapped_headers "{x-random=>foo}" <null> <null>
  230. > CREATE SOURCE webhook_with_time_based_rejection IN CLUSTER webhook_cluster FROM WEBHOOK
  231. BODY FORMAT TEXT
  232. CHECK (
  233. WITH (HEADERS)
  234. (headers->'timestamp'::text)::timestamp + INTERVAL '30s' >= now()
  235. )
  236. $ webhook-append name=webhook_with_time_based_rejection timestamp=2020-01-01 status=400
  237. this_will_get_rejected
  238. $ set-from-sql var=current_ts
  239. SELECT now()::text
  240. $ webhook-append name=webhook_with_time_based_rejection timestamp=${current_ts}
  241. this_will_work
  242. > SELECT body FROM webhook_with_time_based_rejection
  243. this_will_work
  244. # Unnest batch requests, with a materialize view.
  245. > CREATE SOURCE webhook_for_batch_events IN CLUSTER webhook_cluster FROM WEBHOOK
  246. BODY FORMAT JSON
  247. $ webhook-append name=webhook_for_batch_events
  248. [
  249. { "event_id": 1 },
  250. { "event_id": 2 },
  251. { "event_id": 3 },
  252. { "event_id": 4 }
  253. ]
  254. > CREATE MATERIALIZED VIEW webhook_for_batch_events_flattened (body)
  255. IN CLUSTER webhook_compute
  256. AS (
  257. SELECT jsonb_array_elements(body) as body FROM webhook_for_batch_events
  258. );
  259. > SELECT body FROM webhook_for_batch_events_flattened
  260. "{\"event_id\":1}"
  261. "{\"event_id\":2}"
  262. "{\"event_id\":3}"
  263. "{\"event_id\":4}"
  264. $ webhook-append name=webhook_for_batch_events
  265. [
  266. { "event_id": 5 },
  267. { "event_id": 6 },
  268. { "event_id": 7 },
  269. { "event_id": 8 }
  270. ]
  271. > SELECT COUNT(*) FROM webhook_for_batch_events_flattened
  272. 8
  273. # Unnest batch requests with FORMAT JSON ARRAY.
  274. > CREATE SOURCE webhook_json_array IN CLUSTER webhook_cluster FROM WEBHOOK
  275. BODY FORMAT JSON ARRAY
  276. $ webhook-append name=webhook_json_array
  277. [
  278. { "event_name": "a" },
  279. { "event_name": "b" },
  280. { "event_name": "c" },
  281. { "event_name": "a" }
  282. ]
  283. > SELECT body FROM webhook_json_array
  284. "{\"event_name\":\"a\"}"
  285. "{\"event_name\":\"a\"}"
  286. "{\"event_name\":\"b\"}"
  287. "{\"event_name\":\"c\"}"
  288. $ webhook-append name=webhook_json_array
  289. { "event_type": "i am a single event" }
  290. > SELECT body FROM webhook_json_array
  291. "{\"event_name\":\"a\"}"
  292. "{\"event_name\":\"a\"}"
  293. "{\"event_name\":\"b\"}"
  294. "{\"event_name\":\"c\"}"
  295. "{\"event_type\":\"i am a single event\"}"
  296. $ webhook-append name=webhook_json_array
  297. [ { "nested_array": "a_1" }, { "nested_array": "a_2" } ]
  298. [ { "nested_array": "b_1" }, { "nested_array": "b_2" } ]
  299. > SELECT body FROM webhook_json_array
  300. "{\"event_name\":\"a\"}"
  301. "{\"event_name\":\"a\"}"
  302. "{\"event_name\":\"b\"}"
  303. "{\"event_name\":\"c\"}"
  304. "{\"event_type\":\"i am a single event\"}"
  305. "{\"nested_array\":\"a_1\"}"
  306. "{\"nested_array\":\"a_2\"}"
  307. "{\"nested_array\":\"b_1\"}"
  308. "{\"nested_array\":\"b_2\"}"
  309. > CREATE SOURCE webhook_ndjson IN CLUSTER webhook_cluster FROM WEBHOOK
  310. BODY FORMAT JSON
  311. $ webhook-append name=webhook_ndjson
  312. { "name": "bill" }
  313. { "name": "john" }
  314. { "name": "alex" }
  315. > SELECT body FROM webhook_ndjson
  316. "{\"name\":\"bill\"}"
  317. "{\"name\":\"john\"}"
  318. "{\"name\":\"alex\"}"
  319. > CREATE SOURCE webhook_json_array_with_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  320. BODY FORMAT JSON ARRAY
  321. INCLUDE HEADER 'x-timestamp' as event_timestamp
  322. CHECK (
  323. WITH ( HEADERS, BODY )
  324. decode(headers->'x-signature', 'base64') = hmac(body, 'super_secret_123', 'sha256')
  325. )
  326. $ webhook-append name=webhook_json_array_with_headers x-timestamp=200 x-signature=a5nfOLZK7Xp5yuJeukC7A3XhNo5Y0gPQmg9KVDo9hU8=
  327. [
  328. { "event": "coolio_thing" },
  329. { "event": "oof_not_good" },
  330. { "event": "recovered" }
  331. ]
  332. > SELECT body, event_timestamp FROM webhook_json_array_with_headers
  333. "{\"event\":\"coolio_thing\"}" 200
  334. "{\"event\":\"oof_not_good\"}" 200
  335. "{\"event\":\"recovered\"}" 200
  336. $ webhook-append name=webhook_json_array_with_headers x-timestamp=202 x-signature=aUyjzZBzSvGGZVwMncpZ8mZABmgr2L13quRZy6uaTgA=
  337. { "event": "ugh_wish_i_was_batch" }
  338. > SELECT body FROM webhook_json_array_with_headers WHERE event_timestamp = '202'
  339. "{\"event\":\"ugh_wish_i_was_batch\"}"
  340. # Renaming a webhook source.
  341. > CREATE SOURCE webhook_foo IN CLUSTER webhook_cluster FROM WEBHOOK
  342. BODY FORMAT TEXT
  343. $ webhook-append name=webhook_foo
  344. aaa_1
  345. > SELECT body FROM webhook_foo;
  346. aaa_1
  347. > ALTER SOURCE webhook_foo RENAME TO webhook_bar
  348. $ webhook-append name=webhook_foo status=404
  349. aaa_2
  350. $ webhook-append name=webhook_bar
  351. bbb_1
  352. > SELECT body FROM webhook_bar;
  353. aaa_1
  354. bbb_1
  355. # Renaming the schema that contains a webhook source.
  356. > CREATE SCHEMA my_webhooks;
  357. > CREATE SOURCE my_webhooks.foo IN CLUSTER webhook_cluster FROM WEBHOOK
  358. BODY FORMAT TEXT
  359. > CREATE SOURCE my_webhooks.bar IN CLUSTER webhook_cluster FROM WEBHOOK
  360. BODY FORMAT TEXT
  361. $ webhook-append schema=my_webhooks name=foo
  362. foo-foo
  363. $ webhook-append schema=my_webhooks name=bar
  364. bar-bar
  365. > SELECT body FROM my_webhooks.foo
  366. foo-foo
  367. > SELECT body FROM my_webhooks.bar
  368. bar-bar
  369. > BEGIN;
  370. > ALTER SCHEMA my_webhooks RENAME TO other_webhooks;
  371. > ALTER SOURCE other_webhooks.bar RENAME TO baz;
  372. > COMMIT;
  373. $ webhook-append schema=other_webhooks name=foo
  374. foo-after
  375. $ webhook-append schema=other_webhooks name=baz
  376. baz-after
  377. > SELECT body FROM other_webhooks.foo
  378. foo-foo
  379. foo-after
  380. > SELECT body FROM other_webhooks.baz
  381. bar-bar
  382. baz-after
  383. # Dropping a webhook source should drop the underlying persist shards.
  384. $ set-from-sql var=webhook-source-id
  385. SELECT id FROM mz_sources WHERE name = 'webhook_bytes';
  386. $ set-from-sql var=webhook-source-shard-id
  387. SELECT shard_id FROM mz_internal.mz_storage_shards WHERE object_id = '${webhook-source-id}';
  388. > SELECT COUNT(*) FROM mz_internal.mz_storage_shards WHERE object_id = '${webhook-source-id}';
  389. 1
  390. > DROP SOURCE webhook_bytes CASCADE;
  391. > SELECT COUNT(*) FROM mz_internal.mz_storage_shards WHERE object_id = '${webhook-source-id}';
  392. 0
  393. $ check-shard-tombstone shard-id=${webhook-source-shard-id}
  394. # Cleanup.
  395. > DROP CLUSTER webhook_cluster CASCADE;
  396. $ set-from-sql var=current-cluster
  397. SHOW cluster;
  398. > CREATE SOURCE webhook_in_current FROM WEBHOOK BODY FORMAT JSON;
  399. > SELECT c.name = '${current-cluster}' FROM mz_clusters c JOIN mz_sources s ON c.id = s.cluster_id WHERE s.name = 'webhook_in_current';
  400. true
  401. > CREATE TABLE webhook_as_table FROM WEBHOOK BODY FORMAT TEXT;
  402. $ webhook-append name=webhook_as_table
  403. aaa_1
  404. > SELECT * FROM webhook_as_table;
  405. aaa_1
  406. > SHOW CREATE TABLE webhook_as_table;
  407. materialize.public.webhook_as_table "CREATE TABLE materialize.public.webhook_as_table FROM WEBHOOK BODY FORMAT TEXT;"
  408. > SHOW COLUMNS FROM webhook_as_table;
  409. name nullable type comment
  410. ------------------------------
  411. body false text ""
  412. > SHOW TABLES
  413. not_a_webhook ""
  414. webhook_as_table ""
  415. > SELECT name, type FROM mz_objects WHERE name = 'webhook_as_table';
  416. webhook_as_table table
  417. $ set-from-sql var=webhook-table-id
  418. SELECT id FROM mz_tables WHERE name = 'webhook_as_table';
  419. $ set-from-sql var=webhook-table-shard-id
  420. SELECT shard_id FROM mz_internal.mz_storage_shards WHERE object_id = '${webhook-table-id}';
  421. > DROP TABLE webhook_as_table;
  422. $ check-shard-tombstone shard-id=${webhook-table-shard-id}