use async_trait::async_trait;
use greentic_deploy_spec::{DeploymentId, Environment, RevisionId, RuntimeConfig};
use thiserror::Error;
use crate::environment::runtime_config::materialize_runtime_config;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StageOutcome {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WarmOutcome {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DrainOutcome {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ArchiveOutcome {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrafficSplitOutcome {
pub applied_deployment_id: DeploymentId,
pub applied_entries: Vec<greentic_deploy_spec::TrafficSplitEntry>,
}
#[derive(Debug, Error)]
pub enum DeployerError {
#[error("revision `{revision_id}` not found in env `{env_id}`")]
RevisionNotFound {
env_id: greentic_deploy_spec::EnvId,
revision_id: RevisionId,
},
#[error("no TrafficSplit recorded for deployment `{deployment_id}` in env `{env_id}`")]
SplitNotFound {
env_id: greentic_deploy_spec::EnvId,
deployment_id: DeploymentId,
},
#[error(
"TrafficSplit for deployment `{deployment_id}` violates the sum=10000bps invariant: actual={sum}"
)]
InvalidSplit {
deployment_id: DeploymentId,
sum: u64,
},
#[error("provider failure: {0}")]
Provider(String),
}
pub fn require_revision(env: &Environment, revision_id: RevisionId) -> Result<(), DeployerError> {
if env.revisions.iter().any(|r| r.revision_id == revision_id) {
Ok(())
} else {
Err(DeployerError::RevisionNotFound {
env_id: env.environment_id.clone(),
revision_id,
})
}
}
pub fn enforce_split_invariants(
env: &Environment,
deployment_id: DeploymentId,
) -> Result<TrafficSplitOutcome, DeployerError> {
let split = env
.traffic_splits
.iter()
.find(|s| s.deployment_id == deployment_id)
.ok_or_else(|| DeployerError::SplitNotFound {
env_id: env.environment_id.clone(),
deployment_id,
})?;
let sum: u64 = split.entries.iter().map(|e| u64::from(e.weight_bps)).sum();
if sum != 10_000 {
return Err(DeployerError::InvalidSplit { deployment_id, sum });
}
Ok(TrafficSplitOutcome {
applied_deployment_id: deployment_id,
applied_entries: split.entries.clone(),
})
}
#[async_trait]
pub trait Deployer: std::fmt::Debug + Send + Sync {
async fn stage_revision(
&self,
env: &Environment,
revision_id: RevisionId,
) -> Result<StageOutcome, DeployerError>;
async fn warm_revision(
&self,
env: &Environment,
revision_id: RevisionId,
) -> Result<WarmOutcome, DeployerError>;
async fn drain_revision(
&self,
env: &Environment,
revision_id: RevisionId,
) -> Result<DrainOutcome, DeployerError>;
async fn archive_revision(
&self,
env: &Environment,
revision_id: RevisionId,
) -> Result<ArchiveOutcome, DeployerError>;
async fn apply_traffic_split(
&self,
env: &Environment,
deployment_id: DeploymentId,
) -> Result<TrafficSplitOutcome, DeployerError>;
fn report_runtime_config(&self, env: &Environment) -> RuntimeConfig {
materialize_runtime_config(env)
}
}