1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, HashMap};
3use std::path::PathBuf;
4use xbp_oci::OciRef;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum DeployTarget {
10 Service(String),
11 Group(String),
12 All,
13}
14
15impl DeployTarget {
16 pub fn label(&self) -> String {
17 match self {
18 Self::Service(s) | Self::Group(s) => s.clone(),
19 Self::All => "all".into(),
20 }
21 }
22
23 pub fn kind_str(&self) -> &'static str {
24 match self {
25 Self::Service(_) => "service",
26 Self::Group(_) => "group",
27 Self::All => "all",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ProjectConfig {
35 pub project_name: String,
36 pub version: String,
37 pub project_root: PathBuf,
38 pub services: Vec<ServiceConfigView>,
39 pub groups: HashMap<String, DeployGroupView>,
40 pub default_env: Option<String>,
41 pub kubernetes: Option<KubernetesDefaults>,
42 pub history_dir: PathBuf,
43 pub lock_file: PathBuf,
44 pub git_sha: Option<String>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct DeployGroupView {
49 pub description: Option<String>,
50 pub services: Vec<String>,
51 pub order: Vec<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct KubernetesDefaults {
57 pub default_context: Option<String>,
58 pub default_namespace: Option<String>,
59 pub require_context_confirmation_for_apply: bool,
60 pub kubeconfig: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ServiceConfigView {
65 pub name: String,
66 pub root_directory: Option<String>,
67 pub version: Option<String>,
68 pub depends_on: Vec<String>,
69 pub oci: Option<ServiceOciView>,
70 pub deploy: Option<ServiceDeployView>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ServiceOciView {
75 pub image: String,
76 pub tag: Option<String>,
77 pub tag_from: Option<String>,
78 pub dockerfile: Option<String>,
79 pub platforms: Vec<String>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ServiceDeployView {
84 pub provider: String,
85 pub envs: HashMap<String, ServiceDeployEnvView>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ServiceDeployEnvView {
90 pub namespace: Option<String>,
91 pub replicas: Option<u32>,
92 pub health: Vec<String>,
93 pub kubernetes: Option<ServiceK8sView>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct ServiceK8sView {
98 pub manifests_base: Option<String>,
99 pub manifests_overlay: Option<String>,
100 pub workload: Option<String>,
101 pub service: Option<String>,
102 pub crds_path: Option<String>,
103 pub install_path: Option<String>,
104 pub selector: Option<String>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct DeployPlan {
109 pub target: DeployTarget,
110 pub env: String,
111 pub project: String,
112 pub project_version: String,
113 pub git_sha: Option<String>,
114 pub services: Vec<ServicePlan>,
115 pub order: Vec<String>,
117 pub oci_plan: OciPlan,
118 pub k8s_plan: K8sPlanView,
119 pub hash: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ServicePlan {
125 pub name: String,
126 pub provider: String,
127 pub version: String,
128 pub image: Option<OciRef>,
129 pub image_ref: Option<String>,
130 pub digest: Option<String>,
131 pub deploy: ServiceDeployPlan,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ServiceDeployPlan {
136 pub namespace: Option<String>,
137 pub workload: Option<String>,
138 pub health: Vec<String>,
139 pub manifest_paths: Vec<PathBuf>,
140 pub crds_path: Option<PathBuf>,
141 pub install_path: Option<PathBuf>,
142 pub selector: Option<String>,
143 pub actions: Vec<String>,
144}
145
146#[derive(Debug, Clone, Default, Serialize, Deserialize)]
147pub struct OciPlan {
148 pub images: BTreeMap<String, OciImagePlan>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct OciImagePlan {
153 pub ref_str: String,
154 pub digest: Option<String>,
155}
156
157#[derive(Debug, Clone, Default, Serialize, Deserialize)]
158pub struct K8sPlanView {
159 pub context: Option<String>,
160 pub default_namespace: Option<String>,
161 pub services: Vec<xbp_k8s::K8sServicePlan>,
162}
163
164impl DeployPlan {
165 pub fn render_human(&self) -> String {
166 let mut lines = vec![
167 format!(
168 "project={} version={} env={} target={} ({})",
169 self.project,
170 self.project_version,
171 self.env,
172 self.target.label(),
173 self.target.kind_str()
174 ),
175 format!("hash={}", self.hash),
176 format!("order: {}", self.order.join(" → ")),
177 ];
178 if let Some(sha) = &self.git_sha {
179 lines.push(format!("git_sha={sha}"));
180 }
181 for (idx, svc) in self.services.iter().enumerate() {
182 lines.push(format!(
183 "{}. {} [{}] v{}",
184 idx + 1,
185 svc.name,
186 svc.provider,
187 svc.version
188 ));
189 if let Some(ns) = &svc.deploy.namespace {
190 lines.push(format!(" namespace: {ns}"));
191 }
192 if let Some(image) = &svc.image_ref {
193 lines.push(format!(" image: {image}"));
194 }
195 if let Some(d) = &svc.digest {
196 lines.push(format!(" digest: {d}"));
197 }
198 for a in &svc.deploy.actions {
199 lines.push(format!(" • {a}"));
200 }
201 }
202 lines.join("\n")
203 }
204
205 pub fn to_k8s_plan(&self) -> xbp_k8s::K8sPlan {
206 xbp_k8s::K8sPlan {
207 services: self.k8s_plan.services.clone(),
208 }
209 }
210}