use std::collections::BTreeMap;
use std::env;
use serde::{Deserialize, Serialize};
use greentic_types::deployment::DeploymentPlan;
use crate::config::DeployerConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanContext {
pub plan: DeploymentPlan,
pub messaging: MessagingContext,
pub telemetry: TelemetryContext,
pub channels: Vec<ChannelContext>,
pub secrets: Vec<SecretContext>,
pub deployment: DeploymentHints,
}
impl PlanContext {
pub fn summary(&self) -> String {
format!(
"Plan for {} @ {}: {} runners, {} channels, {} secrets, {} oauth clients",
self.plan.tenant,
self.plan.environment,
self.plan.runners.len(),
self.plan.channels.len(),
self.secrets.len(),
self.plan.oauth.len()
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagingContext {
pub logical_cluster: String,
pub replicas: u16,
pub admin_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryContext {
pub otlp_endpoint: String,
pub resource_attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelContext {
pub name: String,
pub kind: String,
pub ingress: Vec<String>,
pub oauth_required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretContext {
pub key: String,
pub scope: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentHints {
pub provider: String,
pub strategy: String,
}
pub fn build_telemetry_context(plan: &DeploymentPlan, config: &DeployerConfig) -> TelemetryContext {
let endpoint = plan
.telemetry
.as_ref()
.and_then(|t| t.suggested_endpoint.clone())
.or_else(|| env::var("GREENTIC_OTLP_ENDPOINT").ok())
.or_else(|| env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok())
.unwrap_or_else(|| "https://otel.greentic.ai".to_string());
let mut resource_attributes = BTreeMap::new();
resource_attributes.insert(
"service.name".to_string(),
format!("greentic-deployer-{}", config.provider.as_str()),
);
resource_attributes.insert(
"deployment.environment".to_string(),
config.environment.clone(),
);
resource_attributes.insert("greentic.tenant".to_string(), config.tenant.clone());
TelemetryContext {
otlp_endpoint: endpoint,
resource_attributes,
}
}
pub fn build_channel_context(
plan: &DeploymentPlan,
config: &DeployerConfig,
) -> Vec<ChannelContext> {
let base_domain =
env::var("GREENTIC_BASE_DOMAIN").unwrap_or_else(|_| "deploy.greentic.ai".to_string());
plan.channels
.iter()
.map(|channel| {
let ingress = format!(
"https://{}/ingress/{}/{}/{}",
base_domain, config.environment, config.tenant, channel.kind
);
ChannelContext {
name: channel.name.clone(),
kind: channel.kind.clone(),
ingress: vec![ingress],
oauth_required: matches!(
channel.kind.as_str(),
"slack" | "teams" | "webex" | "telegram" | "whatsapp"
),
}
})
.collect()
}
pub fn build_secret_context(plan: &DeploymentPlan) -> Vec<SecretContext> {
plan.secrets
.iter()
.map(|secret| SecretContext {
key: secret.key.clone(),
scope: secret.scope.clone(),
})
.collect()
}
pub fn build_messaging_context(plan: &DeploymentPlan) -> MessagingContext {
let logical_cluster = plan
.messaging
.as_ref()
.map(|m| m.logical_cluster.clone())
.unwrap_or_else(|| format!("nats-{}-{}", plan.environment, plan.tenant));
let replicas = if plan.environment.contains("prod") {
3
} else {
1
};
let admin_url = format!("https://nats.{}.{}.svc", plan.environment, plan.tenant);
MessagingContext {
logical_cluster,
replicas,
admin_url,
}
}
pub fn assemble_plan(
plan: DeploymentPlan,
config: &DeployerConfig,
deployment: DeploymentHints,
) -> PlanContext {
let telemetry = build_telemetry_context(&plan, config);
let messaging = build_messaging_context(&plan);
let channels = build_channel_context(&plan, config);
let secrets = build_secret_context(&plan);
PlanContext {
plan,
messaging,
telemetry,
channels,
secrets,
deployment,
}
}