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