環境¶
環境是機構受治理模型的一個具名副本。實體上,這個副本是一個獨立的 PostgreSQL 結構描述:真正的結構描述,沒有判別資料行,亦沒有前綴。所以現有的每個儲存庫查詢在環境內毋須改寫就是正確的,漏寫的述詞亦無法令一個環境的資料列被另一個環境讀到。
每個組織一開始都有一個名為 prod 的環境。它不能刪除,也不能改名。未指名環境的請求由 prod 服務;指名了不存在環境的請求則遭拒。
環境功能提供給採用付費方案的組織。
要為非正式環境填入可安全使用的數據,請參閱建構測試環境。
環境名稱¶
名稱必須符合 [a-z][a-z0-9_]{1,31}:2 至 32 個小寫字母、數字及底線,以字母開頭。prod 以及以 pg_ 開頭的名稱會被拒絕。某個機構的最大長度取決於該機構自身的 id。PostgreSQL 會悄悄截斷超過 63 位元組的識別碼,這個上限防止的就是環境推導出的最長結構描述名稱被截斷。
副本帶走什麼¶
機構結構描述中的每個資料表都恰好屬於一個類別。這種分類是白名單式的:後來新增的資料表在有人於此處指定其類別之前不會隨副本遷移,所以遺漏一個資料表會表現為測試變紅。
| 類別 | 表 | 複製時會發生什麼 |
|---|---|---|
| CARRIED | domains, naming_rules, registered_tables, table_columns, relationships, metrics, roles, rls_rules, tags, tag_param_values, tag_assignments, glossary terms, materialized_views, calendars, api_endpoints, tracked_functions, tracked_webhooks, table_meta_links | 整份複製 |
| IDENTITY_ONLY | sources, api_sources, kafka_sources | 身分與治理欄位隨行;連線值留在原處(見「繫結」) |
| SEEDED_AT_CREATION | roles, user_role_assignments | 只在環境首次建立時複製;日後的合併不動它們 |
| PARTIAL | org_settings | 逐索引鍵複製:治理設定隨行,而指名外部目標或逐環境執行階段的索引鍵留在原處 |
| NEVER_SENSITIVE | org_secrets, user_directory | 永不複製 |
| NEVER_RUNTIME | mv_refresh_log, relationship_candidates, admin_audit_log 及其他 | 永不複製 |
SEEDED_AT_CREATION 的存在是為了解一個特定的問題。新環境需要角色與指派,否則它一開張就沒有人能動手。但日後某次合併若把 prod 的 developer 那一行帶過來,就會覆寫掉某個受限分支可能正需要的受限版本,讓審查路徑反倒成了提權途徑。所以角色與指派只在建立時隨行一次,此後便是各環境自己的答案。
繫結¶
繫結是指明數據來源實際指向何處的那些資料行:host、port、database、username 等。它們從不隨副本遷移。尚未繫結的環境會標記為 unbound,而不是留空,因為空主機不等於沒有主機,連線建構器會把空主機讀成 localhost:5432。
以 Unbound 以外任何方式建立的環境,會按其父環境所寫的原樣複製每個數據來源的連線:
${secret:...} 或 ${env:...} 參照會被複製為同一個參照,從不解析。建立之後,副本便是該環境
自己的資料列,可於數據來源頁面編輯;建構環境的執行階段時,不會經由父環境解析任何內容。
每個數據來源的 binding 欄位說明其連線來自何處:copied、own(於此環境中提供)
或 unbound。編輯複製而來的連線會使其成為 own。從父環境重新複製
(POST /admin/orgs/{org}/environments/{name}/sources/recopy)會再次複製父環境的連線。
合併或部署不會攜帶連線,因此它們帶入的數據來源以 unbound 狀態到達。
git 投影¶
對模型的每次寫入都會把結果提交到該環境的 git 分支。部署一棵樹需要明確呼叫:在 git 託管平台上合併的 pull request 不會自行部署。
每個實體對應一個檔案。路徑是實體的 provisa:// 位址去掉協定及機構部分:provisa://acme/sales/tables/Order 變成 sales/tables/Order.yaml。數據來源放在 sources/,命令放在 commands/,指標放在 metrics/。依附於父物件的子列(資料行、關係、RLS 規則)寫在父物件的檔案中,不獨立成檔。
命令及其標籤指派能撐過整趟來回。命令上的標籤會被導向該命令自己的檔案(commands/<name>.yaml);不屬於任何檔案的標籤會從投影中消失,並在該樹的下一次部署時被刪除。
任何代理鍵都不會寫進檔案。registered_tables.id 是自動遞增整數,所以同一個模型在兩個環境中會得到不同的整數,簡單匯出的結果會與自己產生差異。所有代理鍵都被去掉,對它們的每個參照都寫成目標的路徑。
序列化是決定性的。索引鍵按字母序輸出,子集合按其位址排序,YAML 樣式固定。兩個持有相同模型的環境,產出的樹在位元組層面完全一致。
合併¶
把一個環境的模型合併到另一個環境時,按識別更新:來源中有的每個物件都會在目標中建立或更新。來源中已不存在的物件,只有在呼叫方明確要求刪除時才會被刪除。一次合併就是一個交易,所以中途失敗時目標保持原樣。
套用之前,請呼叫預覽端點(GET /{name}/merge-preview)或傳入 dry_run: true。預覽走的是合併所用的同一條程式碼路徑;它是一個 GET 端點,因此把旗標寫錯的 CI 指令碼不會誤把它本想查看的合併給套用下去。
合併會讓目標的繫結、角色與密鑰維持原封不動。開發環境不會因為從 prod 取得較新的模型而失去自己的資料庫連線;prod 也不會因此取得開發環境的授權。
報告點名了什麼¶
合併報告按路徑列出新增、修改、刪除及未變的內容。它亦會列出衝突:自上次共用某個提交以來,雙方都修改過的物件。衝突只作報告,由來源方勝出,因為合併到目標本來就是這個意思。Provisa 不提供衝突解決、合併標記或逐物件選擇。衝突清單的價值在於提醒:有兩個人在不知情的情況下改了同一個物件。
如果雙方把某個物件改成了相同的值,那就是一致,不算衝突。如果兩個環境完全沒有共同祖先,報告中的基線為 None,空的衝突清單表示根本沒有作比較,而不是沒有衝突。
合併以一個壓縮提交的形式落到目標分支上。提交訊息是必填的,不能為空,因為它是這個壓縮提交所代表的那段工作的唯一說明。來源方的提交保留在原處,之後仍可按 SHA 部署。
拉取¶
拉取會取來遠端為某環境所持有的內容,並讓它成為模型。它不會直接快轉本機分支;它把取回的樹經由一般的部署路徑套用,因此管束手動部署的那一套驗證與審計,同樣管束拉取。
與合併一樣,pull 會報告它覆寫了甚麼:傳入的樹修改過、而本機環境自兩條線最後一次共用提交以來亦修改過的物件。從未提交的本機變更會令環境處於偏離狀態(見下文「歷史」),pull 會在報告中把它列為一般變更。
當兩條線已經分叉,即雙方都有對方沒有的提交時,pull 會被拒絕。拒絕訊息會列出雙方都改動過的物件,令需要決定保留誰的工作的人知道該看哪些物件。
歷程¶
每次部署都會令環境的遊標沿着它自己的提交線向前移動。復原後退一個提交,重做再朝復原開始的位置前進。兩者都不會刪除提交:後退只是增加一個位置,歷史保持不變。
分支的種子是它所由建立的那個環境的頂端,因此復原會停在那個播種點,不會走進母環境的提交裡。
can_undo 及 can_redo 標記隨環境清單回應一併傳回。當投影中沒有控制平面所指的提交時,兩者都為 false,這種狀態是設計所容許的,稱為偏離。如果某個節點的儲存庫儲存從未收到某個提交,它仍會列出自己的環境,只有歷史相關的答案會變化。
授權¶
環境受兩項權限管束。預設情況下,分析師兩項都不持有。
| 權限 | 誰持有(播種時) | 它管束什麼 |
|---|---|---|
environment_management |
org_admin, developer | 建立與刪除環境 |
environment_switch |
org_admin, developer | 由 prod 以外的任何環境提供服務 |
prod 不需要任何權限。沒有指定環境的請求都由它來服務,拒絕它就等於拒絕所有請求。
檢查發生在選擇環境的那一刻,在到達任何路由之前。沒有 environment_switch 的成員會在所有入口同時被拒絕(HTTP、GraphQL、SQL 以及各線路協定),因為環境是在中介軟體中繫結的,而不是在各個處理常式中。
不持有任何環境權限的分析師可以查詢 prod,並且看不到環境切換器。獲授分析師角色的外包人員看不到環境介面,也無法建立或切換進生產環境以外的任何環境。
環境擁有者的權柄¶
建立環境是唯讀成員獲得模型編輯權的唯一途徑。在自己建立的環境中,建立者擁有 developer 角色的能力,但不含數據權限(write、full_results、usage):有建構模型的權限,沒有數據權限。
這項授予是在授權當下由 environments.created_by 推導而來,絕不寫進任何授權表。刪除該環境,在同一個動作中就把它一併移除。
網域成員資格仍然限制擁有者能改動什麼。開分支改變的是成員可以做什麼;它從不改變他們可以對哪些網域去做。
受保護的環境¶
環境可以設為受保護。合併或部署進受保護的環境時,不會在提出當下就套用;它會被提為一項提案,並且必須由請求者以外的人批准。
一旦機構成員多於一人,prod 會自動受保護。只有一個成員的機構無法滿足「申請人以外的另一個人」,所以規則在那裏不適用,否則就永遠無法合併到 prod。org_admin 可以把任何環境標記為受保護。
合併請求是一條儲存下來的記錄,不能用確認對話框代替。批准人按定義不是申請人本人,提出請求時亦不在場。一次性的確認框會迫使批准在申請人的工作階段中完成,而這恰恰是規則所禁止的。
請求資料行帶著合併報告,連同請求者的訊息。是否過時是在讀取時推導出來的,絕不儲存:在讀取時重新規劃並與儲存的報告比對,是唯一不會出錯的做法。過時的請求必須重新提出。請求者不能批准自己的請求。
請求的生命週期狀態:requested → approved/rejected → applied。stale 是推導而來的。
從儲存庫 ref 部署亦走同一道門,請求會在提出時鎖定 SHA。如果 ref 在提出及決定之間移動了,批准人看到的是鎖定提交的報告,而不是新提交的報告。
Note
合併請求的 UI 在環境面板的合併請求分頁下。報告欄以計數顯示會有什麼變動;展開該資料行可看到逐物件的細節。
env CLI 命令¶
provisa env deploy 把某個 ref 上的模型傳送到一個環境。部署已套用或是一次試行時,它以 0 結束;環境受保護、部署只是被提出時,它以 2 結束。把待批准當成已發佈部署的管線就錯了,結束代碼正好能區分這兩種情況。
provisa env fetch 把組織的遠端分支帶進本機存放庫。之後部署便可指名 origin/<branch>。
兩個命令都接受 --api(Provisa API URL)與 --token(bearer 權杖)。在環境中設定 PROVISA_API_URL 與 PROVISA_API_TOKEN,即可免去每次呼叫都要傳。
以存放庫為底的工作流程,典型的 CI 管線如下:
provisa env fetch --org acme --api "$PROVISA_API_URL" --token "$PROVISA_API_TOKEN"
provisa env deploy --org acme --env prod --ref "origin/main" \
--message "release: $GIT_COMMIT_MSG" \
--api "$PROVISA_API_URL" --token "$PROVISA_API_TOKEN"