Skip to Content
核心概念JSONL IaC用 REST 補完

IaC 宣告不了的部分,以及怎麼用 REST 補完

一份 JSONL 文件可以宣告系統的大部分,但不是全部。有六項設定沒有對應的行類型,或是雖然有、卻只能寫下綁環境的值,所以一次完整的部署是「一份文件」加上「一小串 REST 呼叫」。

這一頁就是那串呼叫。每一項都會說要跑什麼、相對於 plan / apply 該在什麼時候跑,以及最容易踩到的那件事:之後這個設定由哪一邊擁有,因為只要文件也宣告了同一件事,重新 apply 就會直接蓋掉。

清單

設定文件為什麼裝不了怎麼補
require_approval 由誰核准規則引用的是覆核範本的原始 id,而範本與群組屬於覆核模組先建立覆核範本
Callback token沒有行類型。密鑰只顯示一次,無法在文件裡來回apply 之後鑄造
帶密鑰的公開讀取權杖public_read v1 只支援無密鑰,理由同上apply 之後鑄造
跨表的 create_record / update_record 動作它的 data 鍵一律對照觸發器自身的資料表解析;只有 materialize_slots 接受 $table:<ref> 目標用 REST 宣告該觸發器
系統 tag 以外的 tag沒有 tag 行類型;command.tag_id 只吃同 scope 的原始 id先建立 tag
移除 client_access 設定這個 kind 沒有 state,所以沒有 absent 可宣告在文件裡停用它

1. 先建立覆核範本,再第一次 apply

require_approval 規則如果沒有一個能解析到同公司、可用範本的 template_id,根本存不進去,所以這是前置條件,不是後續補充。請先在 Review 模組建立範本,該模組不在本手冊範圍內;最低限度的說明見核准流程指南。

然後把範本 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 中間。

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. 用 REST 宣告跨表寫入的觸發器

寫入另一張表的 create_record 或 update_record 動作無法寫進文件。Plan 會把每個 data 鍵都對照觸發器自身的資料表解析,所以另一張表的欄位會以 unknown column ref 失敗——或者,當觸發器的資料表剛好有同名 ref 的欄位時,悄悄對應到那個欄位。只有 materialize_slots 有跨表寫法:$table:<ref>。

請把那個觸發器留在文件之外,apply 之後用 REST 加上。先從 IaC state 讀出目標資料表的 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 }

然後用觸發器的 PUT 寫入;它的 body 是該表完整的觸發器清單:把受 IaC 管理的觸發器原樣送回,再加上新的那個,其 table_id 設為上面的 resource_id,data 以目標資料表的欄位為鍵。

擁有權: 這個觸發器屬於 REST。Apply 以 merge-upsert 寫入自己的觸發器行,從不抹除它沒有管理的觸發器,所以重新 apply 不會動到它。但 export 會拒絕帶有這種觸發器的資料表(export: internal key '{key}' has no ref mapping (legacy/unmanaged column)),所以在該觸發器存在期間,請用 table_ids= 只匯出其他資料表。

見 iac.state 與 triggers.set。

5. 先建立非系統 tag,再第一次 apply

header 的 system 會自動管理一個 tag,並把文件裡的資料表都掛上去。這個同步只增不減:離開文件或已刪除的資料表仍保有這個 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}}

有保存 hash 時,export 會把 passphrase 寫回 "<REDACTED>",否則不輸出它。在同一個 scope apply 這個 redacted 值會保留既有 hash。沒有 hash 時,"<REDACTED>" 在你換成真實 passphrase 之前都會失敗:若該行在 live 資料表上啟用 passphrase access,失敗發生在 plan;其他情況——例如在新 scope 中由文件建立資料表——則發生在 apply。

文件帶得走、卻搬不動的值

有幾種值在文件裡有位置,但不可攜。把 export 套用到別處之前,請先檢查它們:

  • 觸發器憑證匯出為 "__SECRET__";新的 scope 沒有可保留的值,請補上真實值。
  • Rule policy 的 link_target leaf 會原樣複製其 target 欄位;那是被連結資料表的內部 key,請透過 REST 編輯。
  • 私人 view 可以宣告,但 export 只輸出共享 view,所以它們永遠不會出現在 export 裡。

Column ACL key 現在可攜:settings.column_acl 會解析同表邏輯 ref 與沒有歧義的顯示名稱 alias,export 則輸出 symbolic ref。原始 native key 仍只指名自己的 live 欄位。Policy 驗證與型別化參照錯誤見 table line。

順序整理

面對一個全新的環境:

  1. 為每一條 require_approval 規則建立覆核範本,以及任何非系統 tag,然後把這些 id 換進文件,並把 export 中每個 secret 佔位符換成真實值:client passphrase 與 command callback 的 "<REDACTED>",以及 trigger webhook/api_call 憑證的 "__SECRET__"。新的 scope 沒有可保留的儲存值,所以它們會被拒絕——在 plan(trigger 的代碼是 trigger_secret_keep_refused),或者對文件所建立資料表上的 client passphrase 而言,在 apply。
  2. plan、讀差異、apply。
  3. 讀 IaC state 取得你需要的線上 id,並用 REST 加上跨表寫入的觸發器。
  4. 再 plan 一次,應該是乾淨的。第二次 plan 還有變更表示文件裡有東西沒有收斂,怎麼讀那個結果見 plan 與 apply。
  5. 鑄造 callback token 與需要密鑰的公開讀取權杖,並保存它們的密鑰。 第 1 步與第 3 步是讓文件變成「綁環境」的兩件事。格式裡其他東西都可以原樣搬動,因為授權、洞察系統選擇與身分欄位的儲存格都接受可攜身分代號——只有密鑰例外:export 永遠不會帶出它們,必須由第 1 步補上。
Last updated on