use std::collections::BTreeMap;
use std::time::Duration;
use serde::Serialize;
use crate::def::{Namespace, StackDef};
use crate::engine::Step;
use crate::fault::{ErrorContext, Fault};
use crate::state::Checkpoint;
#[derive(Debug, Clone, Serialize)]
pub struct SpendInfo {
pub provider: String,
pub cap_usd: u32,
pub summary: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub struct SubstrateFault {
pub code: &'static str,
pub message: String,
pub remediation: String,
pub context: Box<ErrorContext>,
}
impl SubstrateFault {
pub fn from_fault(fault: &dyn Fault) -> Self {
Self {
code: fault.code(),
message: fault.to_string(),
remediation: fault.remediation(),
context: Box::new(fault.context()),
}
}
}
impl Fault for SubstrateFault {
fn code(&self) -> &'static str {
self.code
}
fn remediation(&self) -> String {
self.remediation.clone()
}
fn context(&self) -> ErrorContext {
self.context.as_ref().clone()
}
}
pub const ACTION_RESOURCE_KIND: &str = "action";
pub fn action_resource(step_id: &str) -> StepResource {
StepResource {
resource_kind: ACTION_RESOURCE_KIND.into(),
resource_id: step_id.to_owned(),
payload: "{}".into(),
}
}
pub fn present_or_gone(present: bool) -> Observation {
if present {
Observation::Present
} else {
Observation::Gone
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespacePurpose {
ServiceEnv,
OperatorPrepare,
Verify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Observation {
Present,
Gone,
}
#[derive(Debug, Clone)]
pub struct ServiceLog {
pub service: String,
pub source: &'static str,
pub log_path: Option<String>,
pub lines: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct StepResource {
pub resource_kind: String,
pub resource_id: String,
pub payload: String,
}
#[derive(Debug)]
pub struct StepContext<'a> {
pub instance: &'a str,
pub def: &'a StackDef,
pub step: &'a Step,
pub source_overrides: &'a BTreeMap<String, String>,
pub dirty: bool,
pub prior: &'a [Checkpoint],
}
#[async_trait::async_trait]
pub trait Substrate: Send + Sync {
fn name(&self) -> &str;
fn validate_definition(&self, def: &StackDef) -> Result<(), SubstrateFault>;
fn supports_source_override(&self) -> bool;
fn default_lease(&self) -> Duration;
fn service_origin(&self, def: &StackDef, instance: &str, service: &str) -> String;
fn build_namespace(
&self,
def: &StackDef,
instance: &str,
prior: &[Checkpoint],
secrets: &BTreeMap<String, String>,
purpose: NamespacePurpose,
) -> Namespace;
async fn execute(&self, ctx: StepContext<'_>) -> Result<StepResource, SubstrateFault>;
async fn observe(
&self,
instance: &str,
checkpoint: &Checkpoint,
) -> Result<Observation, SubstrateFault>;
async fn destroy(&self, instance: &str, checkpoint: &Checkpoint) -> Result<(), SubstrateFault>;
async fn finalize_teardown(&self, _instance: &str) -> Result<(), SubstrateFault> {
Ok(())
}
async fn spend(&self) -> Option<SpendInfo> {
None
}
async fn spend_line(&self) -> Option<String> {
self.spend().await.map(|info| info.summary)
}
async fn fetch_logs(
&self,
_def: &StackDef,
_instance: &str,
_services: &[String],
_tail: usize,
) -> Result<Option<Vec<ServiceLog>>, SubstrateFault> {
Ok(None)
}
}