ferro_cli/deploy/
app_yaml_existing.rs1use std::path::Path;
20
21#[derive(Debug, Default)]
24pub struct PreservedAppYamlIdentity {
25 pub name: Option<String>,
27 pub region: Option<String>,
29 pub repo: Option<String>,
31 pub branch: Option<String>,
33}
34
35pub fn parse_existing(path: &Path) -> Option<PreservedAppYamlIdentity> {
41 let src = std::fs::read_to_string(path).ok()?;
42 let mut out = PreservedAppYamlIdentity::default();
43
44 for line in src.lines() {
45 if out.name.is_none() {
47 if let Some(v) = line.strip_prefix("name: ") {
48 out.name = Some(v.trim().to_string());
49 continue;
50 }
51 }
52 if out.region.is_none() {
54 if let Some(v) = line.strip_prefix("region: ") {
55 out.region = Some(v.trim().to_string());
56 continue;
57 }
58 }
59 if out.repo.is_none() {
61 if let Some(v) = line.trim_start().strip_prefix("repo: ") {
62 out.repo = Some(v.trim().to_string());
63 continue;
64 }
65 }
66 if out.branch.is_none() {
68 if let Some(v) = line.trim_start().strip_prefix("branch: ") {
69 out.branch = Some(v.trim().to_string());
70 continue;
71 }
72 }
73 }
74
75 Some(out)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use std::fs;
82 use tempfile::TempDir;
83
84 fn write_yaml(td: &TempDir, body: &str) -> std::path::PathBuf {
85 let p = td.path().join("app.yaml");
86 fs::write(&p, body).unwrap();
87 p
88 }
89
90 #[test]
91 fn returns_none_for_missing_file() {
92 assert!(parse_existing(Path::new("/tmp/nonexistent-ferro-test-xyz/app.yaml")).is_none());
93 }
94
95 #[test]
96 fn parses_all_four_fields() {
97 let td = TempDir::new().unwrap();
98 let yaml = "# Generated by ferro do:init — edit to your needs\n\
99 name: my-custom-app\n\
100 region: nyc3\n\
101 \n\
102 services:\n\
103 - name: web\n\
104 github:\n\
105 repo: myorg/my-repo\n\
106 branch: production\n\
107 deploy_on_push: true\n";
108 let p = write_yaml(&td, yaml);
109 let id = parse_existing(&p).unwrap();
110 assert_eq!(id.name.as_deref(), Some("my-custom-app"));
111 assert_eq!(id.region.as_deref(), Some("nyc3"));
112 assert_eq!(id.repo.as_deref(), Some("myorg/my-repo"));
113 assert_eq!(id.branch.as_deref(), Some("production"));
114 }
115
116 #[test]
117 fn top_level_name_not_confused_with_service_name() {
118 let td = TempDir::new().unwrap();
119 let yaml = "name: top-level-app\nregion: fra1\n\nservices:\n - name: web\n github:\n repo: o/r\n branch: main\n";
121 let p = write_yaml(&td, yaml);
122 let id = parse_existing(&p).unwrap();
123 assert_eq!(id.name.as_deref(), Some("top-level-app"));
124 }
125
126 #[test]
127 fn top_level_region_not_confused_with_indented_region() {
128 let td = TempDir::new().unwrap();
129 let yaml = "name: app\nregion: ams3\n\nservices:\n - name: web\n region: inner-ignored\n github:\n repo: o/r\n branch: main\n";
130 let p = write_yaml(&td, yaml);
131 let id = parse_existing(&p).unwrap();
132 assert_eq!(id.region.as_deref(), Some("ams3"));
133 }
134
135 #[test]
136 fn missing_fields_return_none() {
137 let td = TempDir::new().unwrap();
138 let yaml = "services:\n - name: web\n http_port: 8080\n";
140 let p = write_yaml(&td, yaml);
141 let id = parse_existing(&p).unwrap();
142 assert!(id.name.is_none());
143 assert!(id.region.is_none());
144 assert!(id.repo.is_none());
145 assert!(id.branch.is_none());
146 }
147
148 #[test]
149 fn first_match_wins_for_repeated_top_level_name() {
150 let td = TempDir::new().unwrap();
151 let yaml = "name: first-name\nname: second-name\nregion: fra1\nservices: []\n";
152 let p = write_yaml(&td, yaml);
153 let id = parse_existing(&p).unwrap();
154 assert_eq!(id.name.as_deref(), Some("first-name"));
155 }
156}