Skip to main content

max / makenotwork

2.2 KB · 90 lines History Blame Raw
1 use anyhow::{Context, Result};
2 use serde::{Deserialize, Serialize};
3 use std::path::Path;
4
5 #[derive(Debug, Clone, Serialize, Deserialize)]
6 pub struct Topology {
7 pub repo: RepoConfig,
8 pub backup: BackupConfig,
9 #[serde(rename = "tier")]
10 pub tiers: Vec<Tier>,
11 }
12
13 #[derive(Debug, Clone, Serialize, Deserialize)]
14 pub struct RepoConfig {
15 pub bare_path: String,
16 pub branch: String,
17 }
18
19 #[derive(Debug, Clone, Serialize, Deserialize)]
20 pub struct BackupConfig {
21 pub source: String,
22 pub local_path: String,
23 }
24
25 #[derive(Debug, Clone, Serialize, Deserialize)]
26 pub struct Tier {
27 pub name: String,
28 #[serde(default)]
29 pub provisioned: bool,
30 pub gates: Vec<Gate>,
31 #[serde(default)]
32 pub canary: CanaryPolicy,
33 #[serde(default, rename = "node")]
34 pub nodes: Vec<Node>,
35 }
36
37 #[derive(Debug, Clone, Serialize, Deserialize)]
38 pub struct Node {
39 pub name: String,
40 pub ssh_target: String,
41 pub release_root: String,
42 }
43
44 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
45 #[serde(rename_all = "snake_case")]
46 pub enum CanaryPolicy {
47 #[default]
48 Sequential,
49 Parallel,
50 }
51
52 impl CanaryPolicy {
53 pub fn as_str(self) -> &'static str {
54 match self {
55 CanaryPolicy::Sequential => "sequential",
56 CanaryPolicy::Parallel => "parallel",
57 }
58 }
59 }
60
61 #[derive(Debug, Clone, Serialize, Deserialize)]
62 #[serde(tag = "kind", rename_all = "snake_case")]
63 pub enum Gate {
64 CargoTest,
65 MigrationDryRun,
66 BootSmoke,
67 BurnIn { hours: u32 },
68 ManualConfirm,
69 }
70
71 impl Topology {
72 pub fn load(path: &Path) -> Result<Self> {
73 let raw = std::fs::read_to_string(path)
74 .with_context(|| format!("reading topology at {}", path.display()))?;
75 let topo: Topology = toml::from_str(&raw)?;
76 topo.validate()?;
77 Ok(topo)
78 }
79
80 fn validate(&self) -> Result<()> {
81 anyhow::ensure!(!self.tiers.is_empty(), "topology must declare at least one tier");
82 for t in &self.tiers {
83 if t.provisioned && t.nodes.is_empty() && t.name != "mm" {
84 anyhow::bail!("tier {} is provisioned but has no nodes", t.name);
85 }
86 }
87 Ok(())
88 }
89 }
90