IaC 宣告不了的部分,以及怎麼用 REST 補完
一份 JSONL 文件可以宣告系統的大部分,但不是全部。有六項設定沒有對應的行類型,或是雖然有、卻只能寫下綁環境的值,所以一次完整的部署是「一份文件」加上「一小串 REST 呼叫」。
這一頁就是那串呼叫。每一項都會說要跑什麼、相對於 plan / apply 該在什麼時候跑,以及最容易踩到的那件事:之後這個設定由哪一邊擁有,因為只要文件也宣告了同一件事,重新 apply 就會直接蓋掉。
清單
| 設定 | 文件為什麼裝不了 | 怎麼補 |
|---|---|---|
require_approval 由誰核准 | 規則引用的是覆核範本的原始 id,而範本與群組屬於覆核模組 | 先建立覆核範本 |
| Callback token | 沒有行類型。密鑰只顯示一次,無法在文件裡來回 | apply 之後鑄造 |
| 帶密鑰的公開讀取權杖 | public_read v1 只支援無密鑰,理由同上 | apply 之後鑄造 |
跨表的 create_record / update_record 動作 | 它的 table_id 必須是線上 id;只有 materialize_slots 接受 $table:<ref> | 讀出 id,再寫回文件 |
| 系統 tag 以外的 tag | 沒有 tag 行類型;command.tag_id 只吃同 scope 的原始 id | 先建立 tag |
移除 client_access 設定 | 這個 kind 沒有 state,所以沒有 absent 可宣告 | 在文件裡停用它 |
1. 先建立覆核範本,再第一次 apply
require_approval 規則如果沒有一個能解析到同公司、可用範本的 template_id,根本存不進去,所以這是前置條件,不是後續補充。先建立人的群組,再建立以它為關卡的範本:
# a) 覆核人員
curl -X POST "$BASE_URL/private/module/review/groups" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "訂單覆核人員", "description": "負責審核高金額訂單", "member_ids": ["<user-uuid>"] }'
# b) 以該群組為關卡的範本
curl -X POST "$BASE_URL/private/module/review/templates" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "高金額訂單覆核",
"description": "單一關卡",
"gates": [{ "name": "主管覆核", "condition": { "type": "group", "group_id": "<group-uuid>", "mode": "any" } }],
"require_signature": false
}'然後把回傳的範本 id 貼進規則那一行:
{"kind":"rule","table":"orders","ref":"big_orders_need_review","spec":{"type":"require_approval","events":["created","updated"],"template_id":"<template-uuid>","when":[{"column":"total","op":"gt","value":100000}]}}擁有權: 規則屬於文件,範本屬於覆核模組。重新 apply 不會動到範本,但會重新套用規則,所以檔案裡那個 id 才是算數的那個。
搬到別的環境: 那個範本 id 在別的環境不存在。請先在目標環境建立範本,apply 之前只改這一個值。這是整個格式裡最不可攜的一行,所以請把核准規則放在你預期會逐環境修改的小文件裡,不要埋在上百行的大 bundle 中間。
見 review.groups.create 與 review.templates.create。
2. apply 之後鑄造 callback token
資料表要先存在才會有權杖,所以這是 apply 之後的步驟:
curl -X POST "$BASE_URL/private/module/custom_tables/chatroom/$CHATROOM_ID/tables/$TABLE_ID/callback-tokens" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "訂單系統回呼", "allowed_ops": "create,update", "valid_until": null }'201 只會帶一次 secret。請立刻放進密鑰管理系統;列表路由不會再給你。
擁有權: 完全屬於 REST。沒有行類型就沒有 drift,重新 apply 也撤不掉它。這是雙面的:export 同樣不會輸出它,所以權杖在你的文件裡是看不見的,必須另外記錄。
見 callbackTokens.mint 與外部回呼,包含公開表單用來長出欄位的 form-schema 路由。
3. apply 之後鑄造帶密鑰的公開讀取權杖
IaC 可以宣告無密鑰的公開權杖,也就是 URL 本身就是憑證。如果你要 bearer 密鑰,請用 REST 鑄造:
curl -X POST "$BASE_URL/private/module/custom_tables/chatroom/tables/$TABLE_ID/public-read-tokens" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "夥伴資料流", "view_id": "<view-uuid>", "secretless": false, "visible_columns": ["品項", "單價"], "rpm": 120 }'注意這個路徑沒有 scope id 那一段。檢視必須已經存在,所以先 apply 文件,再從 IaC state 讀出 view id(做法見第 4 步)。
擁有權: 屬於 REST,而 IaC 永遠不會接管它。用同一個 ref 寫一行 public_read 只會再鑄一個另一個權杖,不會把這個接過去。同一個已公開的檢視,請只選一條路。
見 publicRead.mint 與公開讀取。
4. 解出跨表觸發器的目標
會寫入另一張表的觸發器動作需要那張表的線上 id。只有 materialize_slots 接受 $table:<ref>,所以 create_record 與 update_record 的做法是:先 apply 一次、讀出 id、寫回文件。
# 這份文件管理的每一個資源的 state 列
curl "$BASE_URL/private/module/custom_tables/chatroom/$CHATROOM_ID/tables/iac/state?system=fulfilment" \
-H "Authorization: Bearer $TOKEN"{ "rows": [ { "system": "fulfilment", "resource_type": "table", "ref": "shipments", "resource_id": "<table-uuid>", "table_id": null } ], "total": 1 }取你要的那個 ref 對應的 resource_id,寫進動作裡:
{"kind":"trigger","table":"orders","ref":"open_shipment","spec":{"name":"開立出貨單","on":"created","actions":[{"type":"create_record","table_id":"<table-uuid>","data":{"訂單":"$row.訂單編號"}}]}}擁有權: 觸發器屬於文件。請不要用 REST PUT 觸發器來「修好」這件事:下一次 plan 會把你的 REST 修改視為 drift 並改回去。那個 id 該待在檔案裡。
見 iac.state 與 triggers.set。
5. 先建立非系統 tag,再第一次 apply
header 的 system 會自動管理一個 tag,並把文件裡的資料表都掛上去。對大多數 bundle 這就夠了,包含 command:省略 tag_id 時它會綁到系統 tag。如果你需要另一個 tag,請用 REST 建立並掛表:
curl -X POST "$BASE_URL/private/module/custom_tables/chatroom/$CHATROOM_ID/table-tags" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "營運", "description": "日常營運相關資料表", "color": "#2563eb" }'
curl -X POST "$BASE_URL/private/module/custom_tables/chatroom/$CHATROOM_ID/table-tags/$TAG_ID/tables/$TABLE_ID" \
-H "Authorization: Bearer $TOKEN"擁有權: 系統 tag 以外的 tag 都屬於 REST。Command 指名了它就會讓文件綁住那個環境,和範本 id 一樣,所以能省略 tag_id 就省略。
見 tags.create 與 tags.assignTable。
6. 結束 client access
client_access 沒有 state 欄位,所以沒有 absent 那一行可以寫。請改成在文件裡停用它:
{"kind":"client_access","table":"orders","spec":{"passphrase_enabled":false}}Export 一律把 passphrase 寫回 "<REDACTED>"。在同一個 scope apply 這個 redacted 值會保留既有 hash;在沒有 live hash 的新 scope 啟用時,換成真實 passphrase 之前都會是 plan 錯誤。
順序整理
面對一個全新的環境:
- 為每一條
require_approval規則建立覆核群組與範本,以及任何非系統 tag,然後把這些 id 換進文件。 plan、讀差異、apply。- 讀 IaC state 取得你需要的線上 id,把跨表觸發器的目標寫回文件。
- 再
plan一次,應該是乾淨的。第二次 plan 還有變更表示文件裡有東西沒有收斂,怎麼讀那個結果見 plan 與 apply。 - 鑄造 callback token 與需要密鑰的公開讀取權杖,並保存它們的密鑰。 第 1 步與第 3 步是讓文件變成「綁環境」的兩件事。格式裡其他東西都可以原樣搬動,因為授權、洞察系統選擇與身分欄位的儲存格都接受可攜身分代號。