use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use xbp_oci::OciRef;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeployTarget {
Service(String),
Group(String),
All,
}
impl DeployTarget {
pub fn label(&self) -> String {
match self {
Self::Service(s) | Self::Group(s) => s.clone(),
Self::All => "all".into(),
}
}
pub fn kind_str(&self) -> &'static str {
match self {
Self::Service(_) => "service",
Self::Group(_) => "group",
Self::All => "all",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
pub project_name: String,
pub version: String,
pub project_root: PathBuf,
pub services: Vec<ServiceConfigView>,
pub groups: HashMap<String, DeployGroupView>,
pub default_env: Option<String>,
pub kubernetes: Option<KubernetesDefaults>,
pub history_dir: PathBuf,
pub lock_file: PathBuf,
pub git_sha: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployGroupView {
pub description: Option<String>,
pub services: Vec<String>,
pub order: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KubernetesDefaults {
pub default_context: Option<String>,
pub default_namespace: Option<String>,
pub require_context_confirmation_for_apply: bool,
pub kubeconfig: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConfigView {
pub name: String,
pub root_directory: Option<String>,
pub version: Option<String>,
pub depends_on: Vec<String>,
pub oci: Option<ServiceOciView>,
pub deploy: Option<ServiceDeployView>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceOciView {
pub image: String,
pub tag: Option<String>,
pub tag_from: Option<String>,
pub dockerfile: Option<String>,
pub platforms: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceDeployView {
pub provider: String,
pub envs: HashMap<String, ServiceDeployEnvView>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceDeployEnvView {
pub namespace: Option<String>,
pub replicas: Option<u32>,
pub health: Vec<String>,
pub kubernetes: Option<ServiceK8sView>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceK8sView {
pub manifests_base: Option<String>,
pub manifests_overlay: Option<String>,
pub workload: Option<String>,
pub service: Option<String>,
pub crds_path: Option<String>,
pub install_path: Option<String>,
pub selector: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployPlan {
pub target: DeployTarget,
pub env: String,
pub project: String,
pub project_version: String,
pub git_sha: Option<String>,
pub services: Vec<ServicePlan>,
pub order: Vec<String>,
pub oci_plan: OciPlan,
pub k8s_plan: K8sPlanView,
pub hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServicePlan {
pub name: String,
pub provider: String,
pub version: String,
pub image: Option<OciRef>,
pub image_ref: Option<String>,
pub digest: Option<String>,
pub deploy: ServiceDeployPlan,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceDeployPlan {
pub namespace: Option<String>,
pub workload: Option<String>,
pub health: Vec<String>,
pub manifest_paths: Vec<PathBuf>,
pub crds_path: Option<PathBuf>,
pub install_path: Option<PathBuf>,
pub selector: Option<String>,
pub actions: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OciPlan {
pub images: BTreeMap<String, OciImagePlan>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OciImagePlan {
pub ref_str: String,
pub digest: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct K8sPlanView {
pub context: Option<String>,
pub default_namespace: Option<String>,
pub services: Vec<xbp_k8s::K8sServicePlan>,
}
impl DeployPlan {
pub fn render_human(&self) -> String {
let mut lines = vec![
format!(
"project={} version={} env={} target={} ({})",
self.project,
self.project_version,
self.env,
self.target.label(),
self.target.kind_str()
),
format!("hash={}", self.hash),
format!("order: {}", self.order.join(" → ")),
];
if let Some(sha) = &self.git_sha {
lines.push(format!("git_sha={sha}"));
}
for (idx, svc) in self.services.iter().enumerate() {
lines.push(format!(
"{}. {} [{}] v{}",
idx + 1,
svc.name,
svc.provider,
svc.version
));
if let Some(ns) = &svc.deploy.namespace {
lines.push(format!(" namespace: {ns}"));
}
if let Some(image) = &svc.image_ref {
lines.push(format!(" image: {image}"));
}
if let Some(d) = &svc.digest {
lines.push(format!(" digest: {d}"));
}
for a in &svc.deploy.actions {
lines.push(format!(" • {a}"));
}
}
lines.join("\n")
}
pub fn to_k8s_plan(&self) -> xbp_k8s::K8sPlan {
xbp_k8s::K8sPlan {
services: self.k8s_plan.services.clone(),
}
}
}