canic-host 0.101.53

Host-side App build, Fleet install, deployment, and release-set library for Canic workspaces
Documentation
use super::build_network::ensure_icp_environment_ready;
use super::build_snapshot::ValidatedInstallSnapshot;
use super::build_targets::{progress_bar, wasm_artifact_size};
use super::current_execution::{
    ensure_current_install_executor_capabilities, run_install_deployment_truth_safety_gate,
    run_install_early_authority_preflight,
};
use super::icp_context::InstallIcpContext;
use super::operations::{BuildInstallTargetsOperation, InstallPhaseLabel};
use super::output::{TerminalActivity, TerminalStyle};
use super::phase_receipts::CompletedInstallPhase;
use super::plan_artifacts::{PreparedPlanArtifacts, prepare_plan_artifacts_with_phase};
use super::timing::InstallTimingSummary;
use super::{clock::current_unix_timestamp_label, options::InstallRootOptions};
use crate::deployment_truth::{
    DeploymentCheckV1, DeploymentExecutionContextV1, DeploymentReceiptV1,
};
use crate::{
    canister_build::{
        CurrentCanisterArtifactBuildOutput, WorkspaceBuildContext,
        build_workspace_canister_artifact,
    },
    release_set::{CanicInfrastructureArtifactBuildOutput, CanicInfrastructureRole},
    table::{ColumnAlign, render_bordered_table},
};
use std::{
    path::Path,
    time::{Duration, Instant},
};

pub(super) struct PreparedInstallTruth {
    pub(super) deployment_truth_check: DeploymentCheckV1,
    pub(super) pre_activation_receipts: Vec<DeploymentReceiptV1>,
    pub(super) build_phase: CompletedInstallPhase,
    pub(super) timings: InstallTimingSummary,
    pub(super) build_outputs: Vec<CurrentCanisterArtifactBuildOutput>,
    pub(super) infrastructure_build_outputs: Vec<CanicInfrastructureArtifactBuildOutput>,
    pub(super) plan_artifacts: Option<PreparedPlanArtifacts>,
}

struct PreparedInstallBuild {
    phase: CompletedInstallPhase,
    duration: Duration,
    outputs: Vec<CurrentCanisterArtifactBuildOutput>,
    infrastructure_outputs: Vec<CanicInfrastructureArtifactBuildOutput>,
    plan_artifacts: Option<PreparedPlanArtifacts>,
}

pub(super) fn prepare_install_deployment_truth(
    options: &InstallRootOptions,
    icp: &InstallIcpContext,
    config_path: &Path,
    fleet_name: &str,
    execution_context: &DeploymentExecutionContextV1,
    build_context: &WorkspaceBuildContext,
    install_snapshot: &ValidatedInstallSnapshot,
) -> Result<PreparedInstallTruth, Box<dyn std::error::Error>> {
    let mut timings = InstallTimingSummary::default();
    let icp_root = icp.root();
    ensure_current_install_executor_capabilities(execution_context)?;
    ensure_icp_environment_ready(icp)?;
    run_install_early_authority_preflight(
        options,
        &build_context.workspace_root,
        icp_root,
        config_path,
        fleet_name,
        execution_context,
    )?;
    let build =
        build_install_targets_with_phase(options, build_context, icp_root, install_snapshot)?;
    timings.build_all = build.duration;

    let safety_gate = run_install_deployment_truth_safety_gate(
        options,
        &build_context.workspace_root,
        icp_root,
        config_path,
        fleet_name,
        execution_context,
        build
            .plan_artifacts
            .as_ref()
            .map(PreparedPlanArtifacts::plan),
    )?;
    Ok(PreparedInstallTruth {
        deployment_truth_check: safety_gate.check,
        pre_activation_receipts: safety_gate.receipts,
        build_phase: build.phase,
        timings,
        build_outputs: build.outputs,
        infrastructure_build_outputs: build.infrastructure_outputs,
        plan_artifacts: build.plan_artifacts,
    })
}

fn build_install_targets_with_phase(
    options: &InstallRootOptions,
    build_context: &WorkspaceBuildContext,
    icp_root: &Path,
    install_snapshot: &ValidatedInstallSnapshot,
) -> Result<PreparedInstallBuild, Box<dyn std::error::Error>> {
    if let Some(plan) = &options.deployment_plan_override {
        let (plan_artifacts, phase, duration) =
            prepare_plan_artifacts_with_phase(plan, icp_root, &options.environment)?;
        return Ok(PreparedInstallBuild {
            phase,
            duration,
            outputs: Vec::new(),
            infrastructure_outputs: Vec::new(),
            plan_artifacts: Some(plan_artifacts),
        });
    }

    let complete_build = install_snapshot
        .complete_build
        .as_ref()
        .ok_or_else(|| "normal install is missing its complete-build snapshot".to_string())?;
    let operation = BuildInstallTargetsOperation::new(build_context, &complete_build.targets);
    let started_at = current_unix_timestamp_label()?;
    let started = Instant::now();
    let outputs = operation.execute()?;
    let infrastructure_outputs = qualify_infrastructure_outputs(options, build_context, &outputs)?;
    let duration = started.elapsed();
    let phase = CompletedInstallPhase {
        phase: InstallPhaseLabel::BUILD_ARTIFACTS,
        attempted_action: "build configured and built-in infrastructure targets",
        started_at,
        finished_at: Some(current_unix_timestamp_label()?),
        evidence: operation.evidence(),
        role_names: operation.role_names(),
    };
    Ok(PreparedInstallBuild {
        phase,
        duration,
        outputs,
        infrastructure_outputs,
        plan_artifacts: None,
    })
}

fn qualify_infrastructure_outputs(
    options: &InstallRootOptions,
    build_context: &WorkspaceBuildContext,
    outputs: &[CurrentCanisterArtifactBuildOutput],
) -> Result<Vec<CanicInfrastructureArtifactBuildOutput>, Box<dyn std::error::Error>> {
    let release_build_id = build_context
        .release_build_id
        .ok_or("infrastructure build is missing its durable release-build identity")?;
    let root_output = outputs
        .iter()
        .find(|output| output.role == options.root_canister)
        .ok_or("complete install build has no Fleet Subnet Root output")?;
    let style = TerminalStyle::detected();
    style.print_section("Build infrastructure Wasm", "2 built-in canisters");
    let coordinator_started_at = Instant::now();
    let coordinator_activity =
        TerminalActivity::start(format!("{}  fleet_coordinator", progress_bar(1, 2, 12)));
    let coordinator =
        build_workspace_canister_artifact(&build_context.with_role("fleet_coordinator"))?;
    coordinator_activity.finish();
    let coordinator_elapsed = coordinator_started_at.elapsed();

    let wasm_store_started_at = Instant::now();
    let wasm_store_activity =
        TerminalActivity::start(format!("{}  wasm_store", progress_bar(2, 2, 12)));
    let wasm_store = build_workspace_canister_artifact(&build_context.with_role("wasm_store"))?;
    wasm_store_activity.finish();
    let wasm_store_elapsed = wasm_store_started_at.elapsed();

    let rows = [
        [
            "fleet_coordinator".to_string(),
            style.success("done"),
            wasm_artifact_size(&coordinator.wasm_path, &coordinator.wasm_gz_path)?,
            format!("{:.2}s", coordinator_elapsed.as_secs_f64()),
        ],
        [
            "wasm_store".to_string(),
            style.success("done"),
            wasm_artifact_size(&wasm_store.wasm_path, &wasm_store.wasm_gz_path)?,
            format!("{:.2}s", wasm_store_elapsed.as_secs_f64()),
        ],
    ];
    println!(
        "{}",
        render_bordered_table(
            &["CANISTER", "STATUS", "WASM", "ELAPSED"],
            &rows,
            &[
                ColumnAlign::Left,
                ColumnAlign::Left,
                ColumnAlign::Right,
                ColumnAlign::Right,
            ],
        )
    );
    println!();

    Ok(vec![
        CanicInfrastructureArtifactBuildOutput {
            role: CanicInfrastructureRole::FleetCoordinator,
            package: coordinator.package_name,
            release_build_id,
            wasm_path: coordinator.wasm_path,
            wasm_gz_path: coordinator.wasm_gz_path,
        },
        CanicInfrastructureArtifactBuildOutput {
            role: CanicInfrastructureRole::FleetSubnetRoot,
            package: root_output.output.package_name.clone(),
            release_build_id,
            wasm_path: root_output.output.wasm_path.clone(),
            wasm_gz_path: root_output.output.wasm_gz_path.clone(),
        },
        CanicInfrastructureArtifactBuildOutput {
            role: CanicInfrastructureRole::WasmStore,
            package: wasm_store.package_name,
            release_build_id,
            wasm_path: wasm_store.wasm_path,
            wasm_gz_path: wasm_store.wasm_gz_path,
        },
    ])
}