canic-host 0.68.2

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use crate::deployment_truth::{
    DEPLOYMENT_TRUTH_SCHEMA_VERSION, PromotionPolicyCheckV1, PromotionReadinessStatusV1,
    RolePromotionInputV1, RolePromotionPolicyV1, SafetySeverityV1,
};
use std::collections::BTreeSet;

use super::super::digest::promotion_policy_check_digest;
use super::super::ensure::ensure_policy_field;
use super::super::error::PromotionPolicyCheckError;
use super::super::request::PromotionPolicyCheckRequest;
use super::decision::{collect_policy_findings, role_promotion_policy_decision};
use super::validation::{validate_promotion_policy_check, validate_role_promotion_policy};

pub fn check_promotion_policy(
    request: PromotionPolicyCheckRequest,
) -> Result<PromotionPolicyCheckV1, PromotionPolicyCheckError> {
    ensure_policy_field("check_id", &request.check_id)?;
    let check =
        promotion_policy_check_from_inputs(&request.check_id, &request.inputs, &request.policies);
    validate_promotion_policy_check(&check)?;
    Ok(check)
}

#[must_use]
pub fn promotion_policy_check_from_inputs(
    check_id: impl Into<String>,
    inputs: &[RolePromotionInputV1],
    policies: &[RolePromotionPolicyV1],
) -> PromotionPolicyCheckV1 {
    let mut roles = Vec::with_capacity(inputs.len());
    let mut blockers = Vec::new();
    let mut seen_policy_roles = BTreeSet::new();
    for policy in policies {
        if !seen_policy_roles.insert(policy.role.as_str()) {
            blockers.push(super::super::promotion_finding(
                "promotion_policy_duplicate",
                format!("multiple promotion policies exist for role {}", policy.role),
                SafetySeverityV1::HardFailure,
                &policy.role,
            ));
        }
        if let Err(err) = validate_role_promotion_policy(policy) {
            blockers.push(super::super::promotion_finding(
                "promotion_policy_invalid",
                err.to_string(),
                SafetySeverityV1::HardFailure,
                &policy.role,
            ));
        }
    }
    for input in inputs {
        let matching_policies = policies
            .iter()
            .filter(|policy| policy.role == input.role)
            .collect::<Vec<_>>();
        match matching_policies.as_slice() {
            [] => {
                blockers.push(super::super::promotion_finding(
                    "promotion_policy_missing",
                    format!("no promotion policy exists for role {}", input.role),
                    SafetySeverityV1::HardFailure,
                    &input.role,
                ));
            }
            [policy] => {
                let decision = role_promotion_policy_decision(input, policy);
                collect_policy_findings(&decision, &mut blockers);
                roles.push(decision);
            }
            _ => blockers.push(super::super::promotion_finding(
                "promotion_policy_duplicate",
                format!("multiple promotion policies exist for role {}", input.role),
                SafetySeverityV1::HardFailure,
                &input.role,
            )),
        }
    }

    let mut check = PromotionPolicyCheckV1 {
        schema_version: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
        check_id: check_id.into(),
        promotion_policy_check_digest: String::new(),
        status: if blockers.is_empty() {
            PromotionReadinessStatusV1::Ready
        } else {
            PromotionReadinessStatusV1::Blocked
        },
        roles,
        blockers,
    };
    check.promotion_policy_check_digest = promotion_policy_check_digest(&check);
    check
}