Skip to main content

canic_host/deployment_truth/
plan.rs

1use super::*;
2use crate::release_set::{configured_fleet_name, configured_fleet_roles};
3use std::path::PathBuf;
4
5///
6/// LocalDeploymentPlanRequest
7///
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct LocalDeploymentPlanRequest {
10    pub deployment_name: String,
11    pub network: String,
12    pub workspace_root: PathBuf,
13    pub icp_root: PathBuf,
14    pub config_path: Option<PathBuf>,
15    pub runtime_variant: String,
16    pub build_profile: String,
17}
18
19/// Build a local deployment plan from resolved host config and local artifact
20/// observations without querying or mutating IC state.
21#[must_use]
22pub fn build_local_deployment_plan(request: &LocalDeploymentPlanRequest) -> DeploymentPlanV1 {
23    let config = deployment_config_path(&request.workspace_root, request.config_path.as_deref());
24    let mut unresolved_assumptions = Vec::new();
25    let fleet_template = configured_fleet_name(&config).unwrap_or_else(|err| {
26        unresolved_assumptions.push(assumption(
27            "local_config.fleet_name",
28            format!(
29                "could not resolve fleet template name from {}: {err}",
30                config.display()
31            ),
32        ));
33        request.deployment_name.clone()
34    });
35    let roles = configured_fleet_roles(&config).unwrap_or_else(|err| {
36        unresolved_assumptions.push(assumption(
37            "local_config.roles",
38            format!(
39                "could not resolve configured roles from {}: {err}",
40                config.display()
41            ),
42        ));
43        Vec::new()
44    });
45    let deployment_manifest_digest = config_sha256_assumption(&config, &mut unresolved_assumptions);
46    let artifact_manifest = collect_local_role_artifact_manifest(&LocalArtifactManifestRequest {
47        network: request.network.clone(),
48        workspace_root: request.workspace_root.clone(),
49        icp_root: request.icp_root.clone(),
50        config_path: Some(config),
51    });
52    unresolved_assumptions.extend(
53        artifact_manifest
54            .unresolved_artifacts
55            .into_iter()
56            .map(|gap| assumption(gap.key, gap.description)),
57    );
58
59    DeploymentPlanV1 {
60        schema_version: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
61        plan_id: format!("local:{}:{}:plan", request.network, request.deployment_name),
62        deployment_identity: DeploymentIdentityV1 {
63            deployment_name: request.deployment_name.clone(),
64            network: request.network.clone(),
65            root_principal: None,
66            authority_profile_hash: None,
67            role_topology_hash: None,
68            deployment_manifest_digest,
69            canonical_runtime_config_digest: None,
70            role_embedded_config_set_digest: None,
71            artifact_set_digest: None,
72            pool_identity_set_digest: None,
73            canic_version: Some(env!("CARGO_PKG_VERSION").to_string()),
74            ic_memory_version: None,
75        },
76        trust_domain: TrustDomainV1 {
77            root_trust_anchor: None,
78            migration_from: None,
79        },
80        fleet_template,
81        runtime_variant: request.runtime_variant.clone(),
82        authority_profile: AuthorityProfileV1 {
83            profile_id: format!(
84                "local:{}:{}:authority",
85                request.network, request.deployment_name
86            ),
87            expected_controllers: Vec::new(),
88            staging_controllers: Vec::new(),
89            emergency_controllers: Vec::new(),
90        },
91        role_artifacts: artifact_manifest
92            .role_artifacts
93            .into_iter()
94            .map(|mut artifact| {
95                artifact.build_profile.clone_from(&request.build_profile);
96                artifact
97            })
98            .collect(),
99        expected_canisters: roles
100            .into_iter()
101            .map(|role| ExpectedCanisterV1 {
102                role,
103                canister_id: None,
104                control_class: CanisterControlClassV1::DeploymentControlled,
105            })
106            .collect(),
107        expected_pool: Vec::new(),
108        expected_verifier_readiness: VerifierReadinessExpectationV1 {
109            required: false,
110            expected_role_epochs: Vec::new(),
111        },
112        unresolved_assumptions,
113    }
114}
115
116fn assumption(key: impl Into<String>, description: impl Into<String>) -> DeploymentAssumptionV1 {
117    DeploymentAssumptionV1 {
118        key: key.into(),
119        description: description.into(),
120    }
121}
122
123fn config_sha256_assumption(
124    path: &std::path::Path,
125    assumptions: &mut Vec<DeploymentAssumptionV1>,
126) -> Option<String> {
127    match file_sha256_hex(path) {
128        Ok(hash) => Some(hash),
129        Err(err) => {
130            assumptions.push(assumption(
131                "local_config.raw_sha256",
132                format!("could not hash config {}: {err}", path.display()),
133            ));
134            None
135        }
136    }
137}