canic-host 0.100.17

Host-side App build, Fleet install, deployment, and release-set library for Canic workspaces
Documentation
use super::operations::InstallPhaseLabel;
use super::receipt_io::write_install_deployment_truth_receipt;
use crate::deployment_truth::{
    DeploymentCheckV1, DeploymentCommandResultV1, DeploymentExecutionContextV1,
    DeploymentExecutionStatusV1, DeploymentReceiptV1, ObservationStatusV1, RolePhaseReceiptV1,
    RolePhaseResultV1, deployment_receipt_from_check_with_status, phase_receipt,
};
use canic_core::ids::FleetKey;
use std::path::{Path, PathBuf};

#[derive(Clone, Copy)]
pub(super) struct InstallReceiptScope<'a> {
    pub(super) icp_root: &'a Path,
    pub(super) fleet: FleetKey,
    pub(super) check: &'a DeploymentCheckV1,
    pub(super) execution_context: Option<&'a DeploymentExecutionContextV1>,
}

pub(super) struct CompletedInstallPhase {
    pub(super) phase: InstallPhaseLabel,
    pub(super) attempted_action: &'static str,
    pub(super) started_at: String,
    pub(super) finished_at: Option<String>,
    pub(super) evidence: Vec<String>,
    pub(super) role_names: Vec<String>,
}

pub(super) fn write_completed_install_phase_receipt(
    receipt_scope: InstallReceiptScope<'_>,
    completed: CompletedInstallPhase,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let role_phase_receipts = completed
        .role_names
        .iter()
        .filter_map(|role| {
            completed_phase_role_receipt(
                receipt_scope.check,
                completed.phase,
                role,
                RolePhaseResultV1::Applied,
                None,
            )
        })
        .collect();
    let receipt =
        receipt_scope.with_execution_context(install_deployment_truth_phase_receipt_with_result(
            receipt_scope.check,
            PhaseReceiptInput {
                phase: completed.phase,
                started_at: completed.started_at,
                finished_at: completed.finished_at,
                attempted_action: completed.attempted_action,
                status: ObservationStatusV1::Observed,
                evidence: completed.evidence,
                role_phase_receipts,
                operation_status: DeploymentExecutionStatusV1::Complete,
                command_result: DeploymentCommandResultV1::Succeeded,
            },
        ));
    receipt_scope.write_receipt(&receipt)
}

pub(super) fn completed_phase_role_receipt(
    check: &DeploymentCheckV1,
    phase: InstallPhaseLabel,
    role: &str,
    result: RolePhaseResultV1,
    error: Option<String>,
) -> Option<RolePhaseReceiptV1> {
    let planned = check
        .plan
        .role_artifacts
        .iter()
        .find(|artifact| artifact.role == role)?;
    let observed = check
        .inventory
        .observed_artifacts
        .iter()
        .find(|artifact| artifact.role == role);
    let artifact_digest = observed
        .and_then(|artifact| artifact.file_sha256.clone())
        .or_else(|| observed.and_then(|artifact| artifact.payload_sha256.clone()))
        .or_else(|| planned.observed_wasm_gz_file_sha256.clone())
        .or_else(|| planned.wasm_gz_sha256.clone());

    Some(RolePhaseReceiptV1 {
        role: role.to_string(),
        phase: phase.as_str().to_string(),
        result,
        previous_module_hash: None,
        target_module_hash: planned.installed_module_hash.clone(),
        observed_module_hash_after: None,
        artifact_digest,
        canonical_embedded_config_sha256: planned.canonical_embedded_config_sha256.clone(),
        error,
    })
}

#[cfg(test)]
pub(super) fn install_deployment_truth_phase_receipt(
    check: &DeploymentCheckV1,
    phase: InstallPhaseLabel,
    started_at: String,
    finished_at: Option<String>,
    attempted_action: &str,
    status: ObservationStatusV1,
    evidence: Vec<String>,
) -> DeploymentReceiptV1 {
    install_deployment_truth_phase_receipt_with_result(
        check,
        PhaseReceiptInput {
            phase,
            started_at,
            finished_at,
            attempted_action,
            status,
            evidence,
            role_phase_receipts: Vec::new(),
            operation_status: DeploymentExecutionStatusV1::Complete,
            command_result: DeploymentCommandResultV1::Succeeded,
        },
    )
}

fn install_deployment_truth_phase_receipt_with_result(
    check: &DeploymentCheckV1,
    input: PhaseReceiptInput<'_>,
) -> DeploymentReceiptV1 {
    deployment_receipt_from_check_with_status(
        check,
        format!("{}:{}", check.check_id, input.phase.as_str()),
        input.operation_status,
        input.started_at.clone(),
        input.finished_at.clone(),
        vec![phase_receipt(
            input.phase.as_str(),
            input.started_at,
            input.finished_at,
            input.attempted_action,
            input.status,
            input.evidence,
        )],
        input.role_phase_receipts,
        input.command_result,
    )
}

pub(super) fn receipt_with_execution_context(
    mut receipt: DeploymentReceiptV1,
    execution_context: &DeploymentExecutionContextV1,
) -> DeploymentReceiptV1 {
    receipt.execution_context = Some(execution_context.clone());
    receipt
}

struct PhaseReceiptInput<'a> {
    phase: InstallPhaseLabel,
    started_at: String,
    finished_at: Option<String>,
    attempted_action: &'a str,
    status: ObservationStatusV1,
    evidence: Vec<String>,
    role_phase_receipts: Vec<RolePhaseReceiptV1>,
    operation_status: DeploymentExecutionStatusV1,
    command_result: DeploymentCommandResultV1,
}

impl InstallReceiptScope<'_> {
    pub(super) fn with_execution_context(
        self,
        receipt: DeploymentReceiptV1,
    ) -> DeploymentReceiptV1 {
        match self.execution_context {
            Some(context) => receipt_with_execution_context(receipt, context),
            None => receipt,
        }
    }

    pub(super) fn write_receipt(
        self,
        receipt: &DeploymentReceiptV1,
    ) -> Result<PathBuf, Box<dyn std::error::Error>> {
        let path = write_install_deployment_truth_receipt(self.icp_root, self.fleet, receipt)?;
        println!("Deployment truth receipt JSON: {}", path.display());
        Ok(path)
    }
}