Skip to Content
核心概念JSONL IaC標準編寫流程

標準編寫流程

JSONL IaC 的標準流程是:export 既有系統 → 編輯 JSONL → plan → 審查 diff → apply。Export 讓作者從伺服器可重新匯入的事實開始;plan 則在任何寫入前,把宣告、IaC state 與 live resource 的差異合併成可審查結果。

標準迴圈

export existing system → edit refs/specs → local validation → server plan → review diff → apply reviewed hash → re-plan

不要從記憶手寫一份既有系統,也不要把 apply 當成驗證器。工作台處理本機 parse/validate;伺服器 plan 才能解析 scope 內的 live IDs、state claims、drift 與採用條件。

1. Export 既有系統

對要維護的 chatroom 先呼叫唯讀 export。system 決定輸出的 header 命名空間;include_records 應只有在確實要把資料列納入宣告時才開啟。

curl --fail-with-body \ --get \ --header 'Authorization: Bearer <token>' \ --data-urlencode 'system=crm' \ --data-urlencode 'include_records=false' \ 'https://api.example.com/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/iac/export' \ --output crm.jsonl

Export 是 read-only,不會建立 state。它會重用已存在的 refs,替未管理資源產生可匯入的 refs,並把內部欄位 key 反向翻成 authored ref。相同 live 狀態的輸出具有決定性。

目前的行順序是:headertablecolumnruletriggerviewgrantinsight_selectionclient_accesspublic_readcommandrecordinsight_selection 只在「department scope 且 system 非空」的 export 中出現;public_read 只針對 live、未撤銷、且其綁定 view 也一併匯出的 token 出現。假設舊有九種 kind 順序的工具需要更新位置。

Export 還會設定兩個值得先讀的回應標頭:

  • X-IaC-Truncated 列出 record 被截斷的資料表 ref。
  • X-IaC-Dangling-Link-Targets 列出落在選取範圍之外的 live link/rollup/lookup 目標資料表 ID。這是「先加寬 table_ids= 再重新匯出」的可行動訊號。

Export 現在會帶回什麼

除了原本的九種 kind,一份 export 還可能帶回 table 的 spec.moderatorsinsight_selection 行、public_read 行、command 行、grant principal 與 principal record cell 中的可攜身分 token,以及 rule 與 trigger spec 內的 $row.{column ref} 樣板。其中三件事會改變「未變更的 export 應該長什麼樣」:

  • v1 command 可能以 version: 2 回來。 只要安全,export 就會把 v1 程式升級成保持行為的 v2 形式;會失真時則原樣保留 v1。比較時兩者會被正規化,因此重新匯入是 noop。不要把 v1 文字「還原」回去。
  • Export 中的原始 ID 是刻意的。 無法無歧義 token 化的名稱——重複的聊天室名稱、含有 / 的名稱、存在於多個聊天室的 social client——都會以原始 ID 匯出。那正是套用到別處之前必須人工修正的那幾行。
  • public_read 的往返並非無損。 REST 發行的密鑰 token 會匯出 secretless: false 並依設計無法重新匯入;綁定到私有或未受管理 view 的 token 會被靜默略過;而且因為沒有認領機制,REST 發行的 token 會以自創 ref 重複發行。套用一份 export 前,請逐行讀過所有 public_read 行。

moderators 只在 live 的 moderator 集合非空時才會輸出,因此 export 永遠無法清空 moderator;要清空必須手寫 moderators: []

2. 編輯 ref,不編輯內部 ID

保留穩定的 systemtable.ref 與子資源 ref。跨表關係使用 target/source 表格 ref;欄位關係使用欄位 ref。不要把 table_id、欄位 internal key、column_mapping、rule ID 或 view ID 寫回 authored JSONL。

若要替既有資源換 ref,新增 renamed_from;這會規劃 move,保留同一 live resource,而不是刪除再建立。若要刪除,加入 state: "absent";單純移除整行不會刪除 live resource。

3. 先在本機驗證

IaC 工作台會在瀏覽器中解析文件,檢查 kind、欄位型別、ref 衝突、可解析的欄位關係、filtered grant 與 record key 前置條件。這一步不會把文件傳到伺服器,也不會改動任何資源。

警告: 本機成功不代表 live plan 一定成功。工作台無法看見 state、live resource、採用候選、live drift 或 scope policy;這些必須交給伺服器 plan。

4. 對 live 狀態執行 Plan

將完整 JSONL 以 NDJSON body 送到 plan endpoint。Plan 是 read-only,會回傳 action、before/after、drifted fields、warning/error 與 plan_hash

curl --fail-with-body \ --request POST \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/x-ndjson' \ --data-binary @crm.jsonl \ 'https://api.example.com/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/iac/plan' \ --output crm.plan.json

5. 審查 Diff

逐 action 核對 createadoptmoveupdatenoopdeleteorphanedinsertskip。尤其確認:

  • delete 都對應文件中明確的 state: "absent"
  • adopt 指向預期的既有 live resource,沒有因名稱相近而誤認。
  • moverenamed_from 是 ref 遷移,不是資源替換。
  • update 只改宣告擁有的欄位;drifted_fields 的 live 差異確實應被覆寫。
  • recordinsert/skipon_drift 符合資料管理策略。
  • plan 沒有 error;warning 已有人作出明確判斷。

6. Apply 已審查的 Hash

Apply 會在執行前重新計算 plan。把剛審查的 plan_hash 放進 apply query parameter;伺服器會拒絕與目前 live 狀態不一致的舊 plan。

釘住 hash 現在買到的東西比以前更多。grant principal 與 insight_selection.chatroom 解析後的身分 token 綁定會折入 hash,因此 $user:$room: 名稱在審查與 apply 之間易主時,hash 會被重新蓋章、該次 apply 會被拒絕。record cell、trigger when 值與 link 自然鍵裡的 token 不會綁定,也永遠不會重新蓋章。省略 plan_hash 等於放棄這層保護:文件會綁到 apply 當下持有該名稱的人。

curl --fail-with-body \ --request POST \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/x-ndjson' \ --data-binary @crm.jsonl \ 'https://api.example.com/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/iac/apply?plan_hash=<reviewed-plan-hash>'

如果 live system 在 plan 後改變,apply 回傳 409 plan_stale。不要重送舊 hash;重新 plan、重新審查,再 apply 新 hash。

7. 驗證收斂

Apply 成功後,用同一份文件再 plan。已管理資源應為 noop,已存在的 record 應為 skip。接著可查詢 state,確認每個 qualified ref 已綁到預期 resource ID。這個收斂檢查才是完整流程的結尾。

失敗與重試規則

  • Parse 或 validate 錯誤會在任何 action 執行前拒絕文件;修正整份文件再 plan。
  • 個別 diff action 的 live 限制會顯示在 plan error;不要跳過 plan 直接嘗試 apply。
  • 通過 gate 後是 per-line best-effort;downstream CRUD validator 仍可能產生 apply-phase line error,該行的 transaction 會回滾,其他行則繼續。Column/rule dependency refresh 對非資料庫 failure 採降級或略過,讓 mutation 優先並讓 command 保持 stale。Trigger refresh 則 fail-closed:trigger、history、IaC state 與每個 selected refreshed command definition 構成同一 rollback bundle。逐項讀取 apply result,再 export/plan 目前 live 狀態。
  • 409 plan_stale 表示審查依據已不再與重算 plan 相符,不是網路重試訊號,也不是以時間計算的過期。它是文件層級且完全沒有執行——與 200 回應內帶 code: "iac_plan_stale" 的 per-line 錯誤不同;後者代表 command 或 trigger 的 before-fingerprint 漂移,而且其他行可能已經套用。
  • 成功的 column、rule 或 trigger 行都可能推進相依 command 的 dependency digest。同一次 apply 中較後面的 command 行只會接受已記錄、精確且由伺服器擁有的推進,因此不會產生假的 iac_plan_stale;外部或其他非預期 drift 仍會產生該代碼。
  • Per-line code: "retryable_lock_conflict" 用來分類 MySQL 1205 lock-wait timeout 或 1213 deadlock。失敗的行已回滾,但其他行可能已套用;應先讀取目前狀態,產生並審查新的 plan,再 apply 新 hash,不要重送舊 request。
  • Export 中的 client passphrase 會被遮罩為 <REDACTED>;套用到同一 scope 可保留 live secret,套用到新的 scope 前必須替換為真實值。
  • Export 中的 command callback 密鑰遵循同一規則、同一失敗形狀。帶 <REDACTED> callback 值的 command 行可以更新既有的受管理 command,但永遠無法建立一個——在新 scope 不行,重建懸空的也不行。請先替換成真實值。Export 無法把密鑰帶進新環境。

這個流程做不到的事

有六項設定沒有對應的行類型,或只吃綁環境的 id:核准閘門引用的覆核範本、callback token、帶密鑰的公開讀取權杖、跨表觸發器目標、非系統 tag,以及結束 client access。其中兩項是要在第一次 plan 之前就做完的前置條件。Table moderator 已不再屬於這個缺口:在 table.spec.moderators 使用 $user:<username>,即可做可攜的 full-set 宣告。用 REST 補完 有剩餘呼叫順序,並逐項說明重新 apply 會不會蓋掉它。

動手試試

先從測試 chatroom export 一份文件,在 IaC 工作台修改一個非破壞性欄位,執行唯讀 plan,並把 plan diff 與 JSONL 一起交付審查。

Last updated on