canic-host 0.70.6

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use crate::deployment_truth::{
    PromotionArtifactIdentityGroupV1, PromotionArtifactIdentityKindV1,
    PromotionArtifactIdentitySummaryV1, PromotionMaterializationOutputGroupV1,
    RoleArtifactSourceKindV1, RolePromotionArtifactIdentityV1,
    RolePromotionMaterializationIdentityV1,
};
use std::collections::BTreeMap;

pub(super) fn promotion_artifact_identity_groups(
    roles: &[RolePromotionArtifactIdentityV1],
) -> Vec<PromotionArtifactIdentityGroupV1> {
    let mut groups = BTreeMap::<String, PromotionArtifactIdentityGroupV1>::new();
    for role in roles {
        let identity_key = artifact_identity_key_for_role(role);
        let group = groups.entry(identity_key.clone()).or_insert_with(|| {
            PromotionArtifactIdentityGroupV1 {
                identity_key,
                identity_kind: role.identity_kind,
                roles: Vec::new(),
                source_kinds: Vec::new(),
                source_locators: Vec::new(),
                digest_pinned: role.digest_pinned,
                wasm_sha256: role.wasm_sha256.clone(),
                wasm_gz_sha256: role.wasm_gz_sha256.clone(),
                candid_sha256: role.candid_sha256.clone(),
                canonical_embedded_config_sha256: role.canonical_embedded_config_sha256.clone(),
            }
        });
        if !group.source_kinds.contains(&role.source_kind) {
            group.source_kinds.push(role.source_kind);
        }
        if let Some(locator) = &role.source_locator
            && !group.source_locators.contains(locator)
        {
            group.source_locators.push(locator.clone());
        }
        group.roles.push(role.role.clone());
    }
    groups.into_values().collect()
}

pub(super) fn promotion_artifact_identity_summary(
    roles: &[RolePromotionArtifactIdentityV1],
    groups: &[PromotionArtifactIdentityGroupV1],
) -> PromotionArtifactIdentitySummaryV1 {
    PromotionArtifactIdentitySummaryV1 {
        role_count: roles.len(),
        identity_group_count: groups.len(),
        shared_identity_group_count: groups.iter().filter(|group| group.roles.len() > 1).count(),
        digest_pinned_role_count: roles.iter().filter(|role| role.digest_pinned).count(),
        source_build_role_count: roles
            .iter()
            .filter(|role| role.identity_kind == PromotionArtifactIdentityKindV1::SourceBuild)
            .count(),
        deferred_identity_role_count: roles
            .iter()
            .filter(|role| role.identity_kind == PromotionArtifactIdentityKindV1::Deferred)
            .count(),
    }
}

pub(in crate::deployment_truth::promotion) fn promotion_materialization_output_groups(
    roles: &[RolePromotionMaterializationIdentityV1],
) -> Vec<PromotionMaterializationOutputGroupV1> {
    let mut groups = BTreeMap::<String, PromotionMaterializationOutputGroupV1>::new();
    for role in roles {
        let output_identity_key = materialization_output_key_for_role(role);
        let group = groups
            .entry(output_identity_key.clone())
            .or_insert_with(|| PromotionMaterializationOutputGroupV1 {
                output_identity_key,
                roles: Vec::new(),
                wasm_sha256: role.wasm_sha256.clone(),
                wasm_gz_sha256: role.wasm_gz_sha256.clone(),
                installed_module_hash: role.installed_module_hash.clone(),
                candid_sha256: role.candid_sha256.clone(),
            });
        group.roles.push(role.role.clone());
    }
    groups.into_values().collect()
}

pub(super) fn artifact_identity_key_for_role(role: &RolePromotionArtifactIdentityV1) -> String {
    match role.identity_kind {
        PromotionArtifactIdentityKindV1::SealedWasm
        | PromotionArtifactIdentityKindV1::SealedCompressedWasm
        | PromotionArtifactIdentityKindV1::SealedWasmAndCompressedWasm => sealed_identity_key(
            role.wasm_sha256.as_deref(),
            role.wasm_gz_sha256.as_deref(),
            role.candid_sha256.as_deref(),
            role.canonical_embedded_config_sha256.as_deref(),
        ),
        PromotionArtifactIdentityKindV1::SourceBuild => format!(
            "source_build:source_kind={:?}:locator={}:candid={}:config={}",
            role.source_kind,
            optional_identity_part(role.source_locator.as_deref()),
            optional_identity_part(role.candid_sha256.as_deref()),
            optional_identity_part(role.canonical_embedded_config_sha256.as_deref())
        ),
        PromotionArtifactIdentityKindV1::Deferred => format!(
            "deferred:source_kind={:?}:locator={}",
            role.source_kind,
            optional_identity_part(role.source_locator.as_deref())
        ),
    }
}

pub(super) fn artifact_identity_key_for_group(group: &PromotionArtifactIdentityGroupV1) -> String {
    match group.identity_kind {
        PromotionArtifactIdentityKindV1::SealedWasm
        | PromotionArtifactIdentityKindV1::SealedCompressedWasm
        | PromotionArtifactIdentityKindV1::SealedWasmAndCompressedWasm => sealed_identity_key(
            group.wasm_sha256.as_deref(),
            group.wasm_gz_sha256.as_deref(),
            group.candid_sha256.as_deref(),
            group.canonical_embedded_config_sha256.as_deref(),
        ),
        PromotionArtifactIdentityKindV1::SourceBuild => format!(
            "source_build:source_kind={}:locator={}:candid={}:config={}",
            source_kind_identity_part(single_group_source_kind(group)),
            optional_identity_part(single_group_source_locator(group)),
            optional_identity_part(group.candid_sha256.as_deref()),
            optional_identity_part(group.canonical_embedded_config_sha256.as_deref())
        ),
        PromotionArtifactIdentityKindV1::Deferred => format!(
            "deferred:source_kind={}:locator={}",
            source_kind_identity_part(single_group_source_kind(group)),
            optional_identity_part(single_group_source_locator(group))
        ),
    }
}

pub(in crate::deployment_truth::promotion) fn materialization_output_key_for_role(
    role: &RolePromotionMaterializationIdentityV1,
) -> String {
    materialization_output_key(
        &role.wasm_sha256,
        &role.wasm_gz_sha256,
        &role.installed_module_hash,
        &role.candid_sha256,
    )
}

pub(in crate::deployment_truth::promotion) fn materialization_output_key_for_group(
    group: &PromotionMaterializationOutputGroupV1,
) -> String {
    materialization_output_key(
        &group.wasm_sha256,
        &group.wasm_gz_sha256,
        &group.installed_module_hash,
        &group.candid_sha256,
    )
}

fn materialization_output_key(
    wasm_sha256: &str,
    wasm_gz_sha256: &str,
    installed_module_hash: &str,
    candid_sha256: &str,
) -> String {
    format!(
        "materialized:wasm={wasm_sha256}:wasm_gz={wasm_gz_sha256}:installed={installed_module_hash}:candid={candid_sha256}"
    )
}

fn source_kind_identity_part(kind: Option<RoleArtifactSourceKindV1>) -> String {
    kind.map_or_else(|| "not-recorded".to_string(), |kind| format!("{kind:?}"))
}

fn single_group_source_kind(
    group: &PromotionArtifactIdentityGroupV1,
) -> Option<RoleArtifactSourceKindV1> {
    group.source_kinds.first().copied()
}

fn single_group_source_locator(group: &PromotionArtifactIdentityGroupV1) -> Option<&str> {
    group.source_locators.first().map(String::as_str)
}

fn sealed_identity_key(
    wasm_sha256: Option<&str>,
    wasm_gz_sha256: Option<&str>,
    candid_sha256: Option<&str>,
    canonical_embedded_config_sha256: Option<&str>,
) -> String {
    format!(
        "sealed:wasm={}:wasm_gz={}:candid={}:config={}",
        optional_identity_part(wasm_sha256),
        optional_identity_part(wasm_gz_sha256),
        optional_identity_part(candid_sha256),
        optional_identity_part(canonical_embedded_config_sha256)
    )
}

const fn optional_identity_part(value: Option<&str>) -> &str {
    match value {
        Some(value) => value,
        None => "not-recorded",
    }
}