use crate::{
InternalError,
config::{
ComponentTopology, Config, ConfigError, ConfigModel,
schema::{
BindingConfig, CanisterConfig, ComponentSpecConfig, DelegatedTokenConfig,
FleetInitMode, LogConfig, RoleAttestationConfig, ScalingConfig,
implicit_root_canister_config, implicit_wasm_store_canister_config,
},
},
ids::{CanisterRole, ComponentSpecId},
model::cycles_funding::FundingLimits,
ops::{OpsError, prelude::*, runtime::env::EnvOps},
storage::stable::state::fleet::FleetMode,
};
use std::{collections::BTreeSet, sync::Arc};
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum ConfigOpsError {
#[error(transparent)]
Config(#[from] ConfigError),
#[error("Component Spec {0} not found in configuration")]
ComponentSpecNotFound(String),
#[error("canister {0} not defined in Component Spec {1}")]
CanisterNotFound(String, String),
#[error(
"canister role {0} belongs to multiple Component Specs; an exact Component Spec binding is required"
)]
CanisterRoleAmbiguous(String),
}
impl From<ConfigOpsError> for InternalError {
fn from(err: ConfigOpsError) -> Self {
OpsError::from(err).into()
}
}
pub struct ConfigOps;
impl ConfigOps {
pub fn export_toml() -> Result<String, InternalError> {
let toml = Config::to_toml()?;
Ok(toml)
}
pub(crate) fn try_get_component_spec(
component_spec: &ComponentSpecId,
) -> Result<ComponentSpecConfig, InternalError> {
let cfg = Config::get()?;
cfg.get_component_spec(component_spec)
.ok_or_else(|| ConfigOpsError::ComponentSpecNotFound(component_spec.to_string()).into())
}
pub(crate) fn try_get_canister(
component_spec: &ComponentSpecId,
canister_role: &CanisterRole,
) -> Result<CanisterConfig, InternalError> {
let component_spec_cfg = Self::try_get_component_spec(component_spec)?;
component_spec_cfg
.get_canister(canister_role)
.ok_or_else(|| {
ConfigOpsError::CanisterNotFound(
canister_role.to_string(),
component_spec.to_string(),
)
.into()
})
}
pub fn component_topology() -> Result<ComponentTopology, InternalError> {
Config::get()?
.compile_component_topology()
.map_err(ConfigError::from)
.map_err(InternalError::from)
}
pub fn try_get_canister_by_role(
canister_role: &CanisterRole,
) -> Result<CanisterConfig, InternalError> {
if canister_role.is_root() {
return Ok(implicit_root_canister_config());
}
if canister_role.is_wasm_store() {
return Ok(implicit_wasm_store_canister_config());
}
let component_spec = Self::try_get_component_spec_id_by_role(canister_role)?;
Self::try_get_canister(&component_spec, canister_role)
}
pub(crate) fn try_get_component_spec_id_by_role(
canister_role: &CanisterRole,
) -> Result<ComponentSpecId, InternalError> {
let config = Config::get()?;
let mut matches = config.component_specs_for_role(canister_role);
let (component_spec, _component_spec_config) = matches.next().ok_or_else(|| {
ConfigOpsError::CanisterNotFound(
canister_role.to_string(),
"Component Topology".to_string(),
)
})?;
if matches.next().is_some() {
return Err(ConfigOpsError::CanisterRoleAmbiguous(canister_role.to_string()).into());
}
Ok(component_spec.clone())
}
pub(crate) fn get() -> Result<Arc<ConfigModel>, InternalError> {
let cfg = Config::get()?;
Ok(cfg)
}
pub(crate) fn controllers() -> Result<Vec<Principal>, InternalError> {
Ok(Config::get()?.controllers.clone())
}
pub(crate) fn is_whitelisted(caller: &Principal) -> Result<bool, InternalError> {
Ok(Config::get()?.is_whitelisted(caller))
}
pub(crate) fn log_config() -> Result<LogConfig, InternalError> {
Ok(Config::get()?.log.clone())
}
pub(crate) fn delegated_tokens_config() -> Result<DelegatedTokenConfig, InternalError> {
Ok(Config::get()?.auth.delegated_tokens.clone())
}
pub(crate) fn role_attestation_config() -> Result<RoleAttestationConfig, InternalError> {
Ok(Config::get()?.auth.role_attestation.clone())
}
pub(crate) fn app_init_mode() -> Result<FleetMode, InternalError> {
let mode = match Config::get()?.app.init_mode {
FleetInitMode::Enabled => FleetMode::Enabled,
FleetInitMode::Readonly => FleetMode::Readonly,
FleetInitMode::Disabled => FleetMode::Disabled,
};
Ok(mode)
}
pub fn current_component_spec() -> Result<ComponentSpecConfig, InternalError> {
let component_spec = EnvOps::component_spec()?;
Self::try_get_component_spec(&component_spec)
}
pub(crate) fn current_subnet_directory_roles() -> Result<BTreeSet<CanisterRole>, InternalError>
{
let canister_role = EnvOps::canister_role()?;
if canister_role.is_wasm_store() {
return Ok(BTreeSet::new());
}
if canister_role.is_root() {
return Ok(Config::get()?.fleet_directory_roles());
}
Ok(Self::current_component_spec()?.component_directory_roles())
}
pub(crate) fn current_canister() -> Result<CanisterConfig, InternalError> {
let canister_role = EnvOps::canister_role()?;
if canister_role.is_root() || canister_role.is_wasm_store() {
return Self::try_get_canister_by_role(&canister_role);
}
let component_spec = EnvOps::component_spec()?;
Self::try_get_canister(&component_spec, &canister_role)
}
pub(crate) fn current_scaling_config() -> Result<Option<ScalingConfig>, InternalError> {
Ok(Self::current_canister()?.scaling)
}
pub(crate) fn current_binding_config() -> Result<Option<BindingConfig>, InternalError> {
Ok(Self::current_canister()?.binding)
}
pub(crate) fn current_component_canister(
canister_role: &CanisterRole,
) -> Result<CanisterConfig, InternalError> {
let component_spec = EnvOps::component_spec()?;
Self::try_get_canister(&component_spec, canister_role)
}
pub(crate) fn canister_for_provisioning(
canister_role: &CanisterRole,
) -> Result<CanisterConfig, InternalError> {
if EnvOps::is_root() {
Self::try_get_canister_by_role(canister_role)
} else {
Self::current_component_canister(canister_role)
}
}
pub(crate) fn cycles_funding_limits_for_root_child_role(
child_role: &CanisterRole,
) -> Result<FundingLimits, InternalError> {
let cfg = Self::try_get_canister_by_role(child_role)?;
Ok(funding_limits(&cfg))
}
pub(crate) fn cycles_funding_limits_for_component_child_role(
child_role: &CanisterRole,
) -> Result<FundingLimits, InternalError> {
let cfg = Self::current_component_canister(child_role)?;
Ok(funding_limits(&cfg))
}
}
const fn funding_limits(cfg: &CanisterConfig) -> FundingLimits {
FundingLimits {
max_per_request: cfg.cycles_funding.max_per_request.to_u128(),
max_per_child: cfg.cycles_funding.max_per_child.to_u128(),
cooldown_secs: cfg.cycles_funding.cooldown_secs,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::schema::CanisterKind;
#[test]
fn role_lookup_resolves_implicit_infrastructure_outside_component_topology() {
let root =
ConfigOps::try_get_canister_by_role(&CanisterRole::ROOT).expect("implicit root config");
let wasm_store = ConfigOps::try_get_canister_by_role(&CanisterRole::WASM_STORE)
.expect("implicit Wasm Store config");
assert_eq!(root.kind, CanisterKind::Root);
assert_eq!(wasm_store.kind, CanisterKind::Singleton);
}
}