Skip to main content

canic_host/install_root/
mod.rs

1use crate::{
2    canister_build::cache::DefaultCanisterBuildCacheCleanup,
3    release_set::{icp_root, workspace_root},
4};
5use config_selection::resolve_install_config_path;
6use std::{path::PathBuf, time::Instant};
7use thiserror::Error as ThisError;
8
9mod activation;
10mod artifact_promotion;
11mod build_environment;
12mod build_targets;
13mod capabilities;
14mod clock;
15mod commands;
16mod config_selection;
17mod current_execution;
18mod deployment_registration;
19mod deployment_truth_gate;
20mod execution_preflight;
21mod identity;
22mod install_state;
23mod operations;
24mod options;
25mod output;
26mod phase_receipts;
27mod plan_artifacts;
28mod preparation;
29mod readiness;
30mod receipt_io;
31mod root_canister;
32mod root_cycles;
33mod root_verification;
34mod staging;
35mod state;
36mod timing;
37mod truth_check;
38
39use activation::run_root_activation_phases;
40use artifact_promotion::write_artifact_promotion_execution_receipt_for_install;
41use build_environment::resolve_install_build_context;
42pub use config_selection::{
43    current_canic_project_root, discover_canic_config_choices, discover_canic_project_root_from,
44    discover_project_canic_config_choices, project_fleet_roots,
45};
46use current_execution::current_install_execution_context;
47pub use deployment_registration::{
48    RegisterDeploymentStateOptions, VerifyDeploymentRootOptions, register_deployment_state,
49    verify_registered_deployment_root,
50};
51use identity::resolve_install_identity;
52use install_state::{build_install_state, write_install_state_with_deployment_truth_receipt};
53pub use options::InstallRootOptions;
54use output::{print_install_result_summary, print_install_timing_summary};
55use phase_receipts::InstallReceiptScope;
56use plan_artifacts::emit_manifest_with_deployment_truth_receipt;
57use preparation::prepare_install_deployment_truth;
58pub use receipt_io::latest_deployment_truth_receipt_path_from_root;
59pub use state::{
60    InstallState, InstallStateError, RootVerificationStatus, read_named_deployment_install_state,
61    read_named_deployment_install_state_from_root,
62};
63pub(crate) use state::{decode_install_state, validate_network_name};
64use timing::InstallTimingSummary as CurrentInstallTimingSummary;
65pub use truth_check::{check_install_deployment_truth, check_install_execution_preflight};
66
67#[cfg(test)]
68mod tests;
69
70///
71/// InstallRootBlockKind
72///
73/// Machine-readable reason that a fresh root install stopped before mutation.
74///
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub enum InstallRootBlockKind {
78    DeploymentExecutionPreflight,
79    DeploymentTruth,
80}
81
82///
83/// InstallRootBlockedError
84///
85/// Typed install block retained through the host/CLI error boundary.
86///
87
88#[derive(Debug, ThisError)]
89#[error("{message}")]
90pub struct InstallRootBlockedError {
91    kind: InstallRootBlockKind,
92    message: String,
93}
94
95impl InstallRootBlockedError {
96    pub(super) const fn new(kind: InstallRootBlockKind, message: String) -> Self {
97        Self { kind, message }
98    }
99
100    #[must_use]
101    pub const fn kind(&self) -> InstallRootBlockKind {
102        self.kind
103    }
104}
105
106/// Discover installable Canic config choices under the current workspace.
107pub fn discover_current_canic_config_choices() -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
108    let project_root = current_canic_project_root()?;
109    let choices = config_selection::discover_workspace_canic_config_choices(&project_root)?;
110    if !choices.is_empty() {
111        return Ok(choices);
112    }
113
114    if let Ok(icp_root) = icp_root()
115        && icp_root != project_root
116    {
117        return config_selection::discover_workspace_canic_config_choices(&icp_root);
118    }
119
120    Ok(choices)
121}
122
123// Execute the local thin-root install flow against an already running replica.
124pub fn install_root(options: InstallRootOptions) -> Result<(), Box<dyn std::error::Error>> {
125    let workspace_root = workspace_root()?;
126    let _build_cache_cleanup = DefaultCanisterBuildCacheCleanup::for_install(&workspace_root);
127    let icp_root = match &options.icp_root {
128        Some(path) => path.canonicalize()?,
129        None => icp_root()?,
130    };
131    let config_path = resolve_install_config_path(
132        &icp_root,
133        options.config_path.as_deref(),
134        options.interactive_config_selection,
135    )?;
136    let build_context =
137        current_install_build_context(&workspace_root, &icp_root, &config_path, &options)?;
138    let (fleet_name, deployment_name) = resolve_install_identity(&options, &config_path)?;
139    let total_started_at = Instant::now();
140    let mut timings = CurrentInstallTimingSummary::default();
141    let network = options.network.as_str();
142    let execution_context = current_install_execution_context(&workspace_root, &icp_root, network);
143
144    println!("Installing deployment {deployment_name}");
145    println!("Fleet template {fleet_name}");
146    println!();
147    let prepared = prepare_install_deployment_truth(
148        &options,
149        &workspace_root,
150        &icp_root,
151        &config_path,
152        &deployment_name,
153        &execution_context,
154        &build_context,
155    )?;
156    timings.create_canisters = prepared.timings.create_canisters;
157    timings.build_all = prepared.timings.build_all;
158
159    let (manifest_path, emit_manifest_duration) = emit_manifest_with_deployment_truth_receipt(
160        &workspace_root,
161        &icp_root,
162        &options,
163        &config_path,
164        &deployment_name,
165        &prepared.deployment_truth_check,
166        &execution_context,
167    )?;
168    timings.emit_manifest = emit_manifest_duration;
169    let activation_timings = run_root_activation_phases(
170        InstallReceiptScope {
171            icp_root: &icp_root,
172            network,
173            deployment_name: &deployment_name,
174            check: &prepared.deployment_truth_check,
175            execution_context: Some(&execution_context),
176        },
177        &options,
178        &prepared.root_canister_id,
179        &manifest_path,
180        total_started_at,
181        &build_context,
182    )?;
183    timings.install_root = activation_timings.install_root;
184    timings.fund_root = activation_timings.fund_root;
185    timings.stage_release_set = activation_timings.stage_release_set;
186    timings.resume_bootstrap = activation_timings.resume_bootstrap;
187    timings.wait_ready = activation_timings.wait_ready;
188    timings.finalize_root_funding = activation_timings.finalize_root_funding;
189
190    print_install_timing_summary(&timings, total_started_at.elapsed());
191    let state = build_install_state(
192        &options,
193        &workspace_root,
194        &icp_root,
195        &config_path,
196        &manifest_path,
197        (&deployment_name, &fleet_name),
198        &prepared.root_canister_id,
199    )?;
200    let state_path = write_install_state_with_deployment_truth_receipt(
201        InstallReceiptScope {
202            icp_root: &icp_root,
203            network,
204            deployment_name: &deployment_name,
205            check: &prepared.deployment_truth_check,
206            execution_context: Some(&execution_context),
207        },
208        &options.network,
209        &state,
210    )?;
211    write_artifact_promotion_execution_receipt_for_install(
212        &options,
213        &icp_root,
214        network,
215        &deployment_name,
216        &prepared.deployment_truth_check,
217        &execution_context,
218    )?;
219    print_install_result_summary(
220        &options.network,
221        &state.deployment_name,
222        &state.fleet_template,
223        &state_path,
224    );
225    Ok(())
226}
227
228fn current_install_build_context(
229    workspace_root: &std::path::Path,
230    icp_root: &std::path::Path,
231    config_path: &std::path::Path,
232    options: &InstallRootOptions,
233) -> Result<crate::canister_build::WorkspaceBuildContext, Box<dyn std::error::Error>> {
234    resolve_install_build_context(
235        workspace_root,
236        icp_root,
237        config_path,
238        &options.network,
239        &options.root_build_target,
240        options.build_profile,
241    )
242}