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