use super::fleet_catalog_publication::{
TerminalFleetCatalogPublicationRequest, publish_terminal_fleet_catalog,
validate_terminal_fleet_registry,
};
use super::icp_context::InstallIcpContext;
use crate::{
canister_protocol::{CanisterProtocolError, query_no_arg},
fleet_catalog::CommittedFleetCatalog,
fleet_install_plan::PersistedFleetInstallPlan,
icp::IcpCli,
release_set::AppConfigSnapshot,
};
use std::{path::Path, thread};
use candid::Principal;
use canic_core::{
dto::{
fleet_registry::{
FleetRegistry, FleetRegistryManifest, FleetRegistrySnapshotResponse,
FleetRegistryVersion,
},
fleet_subnet_root::FleetSubnetRootCanisterSummary,
},
ids::FleetName,
protocol,
};
use thiserror::Error as ThisError;
pub(super) struct PublishInstalledFleetCatalogRequest<'a> {
pub icp: &'a InstallIcpContext,
pub config_path: &'a Path,
pub fleet_name: FleetName,
pub fleet_install_plan: &'a PersistedFleetInstallPlan,
pub coordinator: Principal,
pub deployed_at_unix_secs: u64,
pub terminal_fleet_registry: &'a FleetRegistryVersion,
}
#[derive(Debug, ThisError)]
enum FleetCatalogCloseoutError {
#[error(transparent)]
Protocol(#[from] CanisterProtocolError),
#[error("terminal summary worker for Fleet Subnet Root {root} panicked")]
SummaryWorkerPanicked { root: Principal },
#[error("live Fleet Registry differs from terminal Component provisioning evidence")]
TerminalRegistryMismatch,
}
pub(super) fn publish_installed_fleet_catalog(
request: PublishInstalledFleetCatalogRequest<'_>,
) -> Result<CommittedFleetCatalog, Box<dyn std::error::Error>> {
let config = AppConfigSnapshot::load(request.config_path)?;
let component_topology = config.model().compile_component_topology()?;
let icp = request.icp.cli();
let registry = query_registry(icp, request.coordinator)?;
validate_terminal_fleet_registry(
&request.fleet_install_plan.plan,
&component_topology,
request.coordinator,
®istry,
)?;
if ®istry.version != request.terminal_fleet_registry {
return Err(FleetCatalogCloseoutError::TerminalRegistryMismatch.into());
}
let root_summaries = query_root_summaries(icp, ®istry)?;
publish_terminal_fleet_catalog(TerminalFleetCatalogPublicationRequest {
workspace_root: request.icp.root(),
fleet_name: request.fleet_name,
environment: request.icp.environment(),
deployed_at_unix_secs: request.deployed_at_unix_secs,
fleet_install_plan: &request.fleet_install_plan.plan,
component_topology: &component_topology,
coordinator: request.coordinator,
registry: ®istry,
root_summaries: &root_summaries,
})
.map_err(Into::into)
}
fn query_registry(
icp: &IcpCli,
coordinator: Principal,
) -> Result<FleetRegistrySnapshotResponse, FleetCatalogCloseoutError> {
Ok(FleetRegistrySnapshotResponse {
registry: query_no_arg::<FleetRegistry>(icp, coordinator, protocol::CANIC_FLEET_REGISTRY)?,
manifest: query_no_arg::<FleetRegistryManifest>(
icp,
coordinator,
protocol::CANIC_FLEET_REGISTRY_MANIFEST,
)?,
version: query_no_arg::<FleetRegistryVersion>(
icp,
coordinator,
protocol::CANIC_FLEET_REGISTRY_VERSION,
)?,
})
}
fn query_root_summaries(
icp: &IcpCli,
registry: &FleetRegistrySnapshotResponse,
) -> Result<Vec<FleetSubnetRootCanisterSummary>, FleetCatalogCloseoutError> {
let mut workers = Vec::with_capacity(registry.registry.fleet_subnet_roots.len());
for registered in ®istry.registry.fleet_subnet_roots {
let root = registered.fleet_subnet_root;
let worker_icp = icp.clone();
let worker = thread::spawn(move || {
query_no_arg::<FleetSubnetRootCanisterSummary>(
&worker_icp,
root,
protocol::CANIC_FLEET_SUBNET_ROOT_CANISTER_SUMMARY,
)
});
workers.push((root, worker));
}
workers
.into_iter()
.map(|(root, worker)| {
worker
.join()
.map_err(|_| FleetCatalogCloseoutError::SummaryWorkerPanicked { root })?
.map_err(Into::into)
})
.collect()
}