canic_host/install_root/deployment_registration/
mod.rs1use super::clock::current_unix_secs;
2use super::root_verification::{
3 RootVerificationReceiptInput, deployment_root_verification_state, file_sha256_hex,
4 root_verification_receipt_from_report, verified_root_state_transition,
5 write_verified_root_state_if_unchanged,
6};
7use super::state::{
8 INSTALL_STATE_SCHEMA_VERSION, InstallState, RootVerificationStatus,
9 deployment_install_state_path, read_deployment_install_state, validate_environment_name,
10 validate_state_name, write_install_state,
11};
12use crate::deployment_truth::{
13 DeploymentCheckV1, DeploymentRootVerificationEvidenceStatusV1,
14 DeploymentRootVerificationReceiptV1, DeploymentRootVerificationRequestV1,
15 DeploymentRootVerificationSourceV1, DeploymentRootVerificationStateV1,
16 deployment_root_verification_report_from_check, validate_deployment_root_verification_report,
17};
18use crate::release_set::{
19 artifact_root_path, icp_root, root_release_set_manifest_path, workspace_root,
20};
21use canic_core::cdk::types::Principal;
22use std::path::{Path, PathBuf};
23
24#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct RegisterDeploymentStateOptions {
30 pub deployment_name: String,
31 pub fleet_template: String,
32 pub root_canister_id: String,
33 pub environment: String,
34 pub allow_unverified: bool,
35 pub icp_root: Option<PathBuf>,
36 pub workspace_root: Option<PathBuf>,
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct VerifyDeploymentRootOptions {
45 pub deployment_name: String,
46 pub environment: String,
47 pub deployment_check: DeploymentCheckV1,
48 pub verified_at_unix_secs: Option<u64>,
49 pub icp_root: Option<PathBuf>,
50}
51
52pub fn register_deployment_state(
57 options: RegisterDeploymentStateOptions,
58) -> Result<PathBuf, Box<dyn std::error::Error>> {
59 validate_state_name(&options.deployment_name)?;
60 validate_state_name(&options.fleet_template)?;
61 validate_environment_name(&options.environment)?;
62 if !options.allow_unverified {
63 return Err(
64 "deployment registration requires explicit unverified-root acknowledgement; pass --allow-unverified"
65 .into(),
66 );
67 }
68 Principal::from_text(&options.root_canister_id).map_err(|err| {
69 format!(
70 "invalid root principal for deployment {}: {err}",
71 options.deployment_name
72 )
73 })?;
74
75 let workspace_root = match options.workspace_root {
76 Some(path) => path,
77 None => workspace_root()?,
78 };
79 let icp_root = match options.icp_root {
80 Some(path) => path,
81 None => icp_root()?,
82 };
83 let release_set_manifest_path =
84 registered_deployment_release_set_manifest_path(&icp_root, &options.environment);
85 let timestamp = current_unix_secs()?;
86 let state = InstallState {
87 schema_version: INSTALL_STATE_SCHEMA_VERSION,
88 deployment_name: options.deployment_name,
89 fleet_template: options.fleet_template.clone(),
90 created_at_unix_secs: timestamp,
91 updated_at_unix_secs: timestamp,
92 environment: options.environment.clone(),
93 root_target: options.root_canister_id.clone(),
94 root_canister_id: options.root_canister_id,
95 root_verification: RootVerificationStatus::NotVerified,
96 root_build_target: "root".to_string(),
97 workspace_root: workspace_root.display().to_string(),
98 icp_root: icp_root.display().to_string(),
99 config_path: workspace_root
100 .join("fleets")
101 .join(&options.fleet_template)
102 .join("canic.toml")
103 .display()
104 .to_string(),
105 release_set_manifest_path: release_set_manifest_path.display().to_string(),
106 };
107
108 Ok(write_install_state(
109 &icp_root,
110 &options.environment,
111 &state,
112 )?)
113}
114
115pub fn verify_registered_deployment_root(
118 options: VerifyDeploymentRootOptions,
119) -> Result<DeploymentRootVerificationReceiptV1, Box<dyn std::error::Error>> {
120 validate_state_name(&options.deployment_name)?;
121 validate_environment_name(&options.environment)?;
122 let verified_at_unix_secs = match options.verified_at_unix_secs {
123 Some(value) => value,
124 None => current_unix_secs()?,
125 };
126 let icp_root = match options.icp_root {
127 Some(path) => path,
128 None => icp_root()?,
129 };
130 let state_path =
131 deployment_install_state_path(&icp_root, &options.environment, &options.deployment_name);
132 let state =
133 read_deployment_install_state(&icp_root, &options.environment, &options.deployment_name)?
134 .ok_or_else(|| {
135 format!(
136 "no local deployment state exists for {}; run canic deploy register first",
137 options.deployment_name
138 )
139 })?;
140 let state_fleet_template = state.fleet_template.clone();
141 let state_root_canister_id = state.root_canister_id.clone();
142 let local_state_digest_before = file_sha256_hex(&state_path)?;
143 let previous_root_verification = deployment_root_verification_state(&state.root_verification);
144 let report =
145 deployment_root_verification_report_from_check(DeploymentRootVerificationRequestV1 {
146 report_id: format!(
147 "local:{}:{}:root-verification-report",
148 options.environment, options.deployment_name
149 ),
150 requested_at: format!("unix:{verified_at_unix_secs}"),
151 deployment_name: options.deployment_name.clone(),
152 environment: options.environment.clone(),
153 expected_fleet_template: state.fleet_template.clone(),
154 expected_root_principal: state.root_canister_id.clone(),
155 current_root_verification: previous_root_verification,
156 source: DeploymentRootVerificationSourceV1::DeploymentTruthCheck,
157 deployment_check: options.deployment_check,
158 });
159 validate_deployment_root_verification_report(&report)?;
160 if report.evidence_status != DeploymentRootVerificationEvidenceStatusV1::EvidenceSatisfied {
161 return Err(format!(
162 "deployment root verification failed for {}: {} blocker(s)",
163 options.deployment_name,
164 report.blockers.len()
165 )
166 .into());
167 }
168 let state_transition = verified_root_state_transition(previous_root_verification);
169 let local_state_digest_after = match previous_root_verification {
170 DeploymentRootVerificationStateV1::NotVerified => {
171 let mut verified_state = state;
172 verified_state.root_verification = RootVerificationStatus::Verified;
173 verified_state.updated_at_unix_secs = verified_at_unix_secs;
174 write_verified_root_state_if_unchanged(
175 &icp_root,
176 &options.environment,
177 &verified_state,
178 &local_state_digest_before,
179 )?
180 }
181 DeploymentRootVerificationStateV1::Verified => file_sha256_hex(&state_path)?,
182 };
183
184 root_verification_receipt_from_report(RootVerificationReceiptInput {
185 deployment_name: options.deployment_name,
186 environment: options.environment,
187 fleet_template: state_fleet_template,
188 root_principal: state_root_canister_id,
189 previous_root_verification,
190 state_transition,
191 report,
192 verified_at_unix_secs,
193 local_state_path: state_path.display().to_string(),
194 local_state_digest_before,
195 local_state_digest_after,
196 })
197}
198
199fn registered_deployment_release_set_manifest_path(icp_root: &Path, environment: &str) -> PathBuf {
200 root_release_set_manifest_path(&artifact_root_path(icp_root, environment))
201}