Skip to main content

lean_ctx_patterns/
terraform.rs

1// SPDX-License-Identifier: Apache-2.0
2
3macro_rules! static_regex {
4    ($pattern:expr_2021) => {{
5        static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
6        RE.get_or_init(|| {
7            regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
8        })
9    }};
10}
11
12fn plan_summary_re() -> &'static regex::Regex {
13    static_regex!(r"Plan:\s*(\d+)\s+to add,\s*(\d+)\s+to change,\s*(\d+)\s+to destroy")
14}
15
16fn apply_summary_re() -> &'static regex::Regex {
17    static_regex!(
18        r"Apply complete!\s*Resources:\s*(\d+)\s+added,\s*(\d+)\s+changed,\s*(\d+)\s+destroyed"
19    )
20}
21
22fn installed_provider_re() -> &'static regex::Regex {
23    static_regex!(r"-\s*Installed\s+([^\s]+)\s+v([0-9][^\s]*)")
24}
25
26fn provider_version_re() -> &'static regex::Regex {
27    static_regex!(r"\*\s*provider\[([^\]]+)\]\s+([0-9][^\s]*)")
28}
29
30fn is_provider_init_noise(line: &str) -> bool {
31    let t = line.trim_start();
32    let tl = t.to_ascii_lowercase();
33    tl.contains("initializing provider plugins")
34        || tl.contains("initializing the backend")
35        || tl.contains("finding ")
36            && (tl.contains("versions matching") || tl.contains("version of"))
37        || tl.starts_with("- finding ")
38        || tl.starts_with("- installing ")
39        || tl.contains("terraform init") && tl.contains("upgrade")
40        || tl.starts_with("╷")
41        || tl.starts_with("╵")
42        || tl.starts_with("│")
43}
44
45pub fn compress(command: &str, output: &str) -> Option<String> {
46    let c = command.trim();
47    let prefix = if c == "terraform" || c.starts_with("terraform ") {
48        "terraform"
49    } else if c == "tofu" || c.starts_with("tofu ") {
50        "tofu"
51    } else {
52        return None;
53    };
54    let sub = c.strip_prefix(prefix).map_or("", str::trim_start);
55    let sub_cmd = sub.split_whitespace().next().unwrap_or("");
56
57    match sub_cmd {
58        "plan" => Some(compress_plan(output)),
59        "apply" => Some(compress_apply(output)),
60        "init" => Some(compress_init(output)),
61        "validate" => Some(compress_validate(output)),
62        _ => Some(compress_generic(output)),
63    }
64}
65
66fn compress_plan(output: &str) -> String {
67    let mut kept = Vec::new();
68
69    for line in output.lines() {
70        if is_provider_init_noise(line) {
71            continue;
72        }
73        let tl = line.trim_start();
74        if tl.starts_with("- Installed ") || tl.starts_with("- Installing ") {
75            continue;
76        }
77
78        if let Some(caps) = plan_summary_re().captures(line) {
79            let add = caps.get(1).map_or("0", |m| m.as_str());
80            let chg = caps.get(2).map_or("0", |m| m.as_str());
81            let des = caps.get(3).map_or("0", |m| m.as_str());
82            kept.push(format!("+ {add} added, ~ {chg} changed, - {des} destroyed"));
83            continue;
84        }
85
86        let l = line.to_ascii_lowercase();
87        if l.contains("no changes.") || l.contains("infrastructure matches the configuration") {
88            kept.push("No changes.".to_string());
89            continue;
90        }
91
92        let is_diag = tl.contains('╷')
93            || tl.contains('│')
94            || tl.contains('╵')
95            || l.contains("error:")
96            || (l.contains("error ")
97                && (l.contains("terraform") || l.contains("plan") || l.contains("provider")))
98            || l.contains("warning:")
99            || l.contains("warning ");
100        if is_diag {
101            kept.push(line.trim().to_string());
102        }
103    }
104
105    if kept.is_empty() {
106        "terraform plan (no summary parsed)".to_string()
107    } else {
108        kept.join("\n")
109    }
110}
111
112fn compress_apply(output: &str) -> String {
113    let mut results = Vec::new();
114    let mut errors = Vec::new();
115
116    for line in output.lines() {
117        if is_provider_init_noise(line) {
118            continue;
119        }
120        let tl = line.trim();
121        if tl.is_empty() {
122            continue;
123        }
124
125        if let Some(caps) = apply_summary_re().captures(line) {
126            let a = caps.get(1).map_or("0", |m| m.as_str());
127            let c = caps.get(2).map_or("0", |m| m.as_str());
128            let d = caps.get(3).map_or("0", |m| m.as_str());
129            results.push(format!(
130                "Apply complete: +{a} added, ~{c} changed, -{d} destroyed"
131            ));
132            continue;
133        }
134
135        let ll = tl.to_ascii_lowercase();
136        if ll.contains("error")
137            && (ll.contains("apply") || ll.contains("terraform") || tl.contains('╷'))
138        {
139            errors.push(tl.to_string());
140        } else if ll.starts_with("creation complete")
141            || ll.starts_with("modification complete")
142            || ll.starts_with("destruction complete")
143            || ll.starts_with("destroy complete")
144        {
145            results.push(tl.to_string());
146        }
147    }
148
149    let mut out = Vec::new();
150    if !results.is_empty() {
151        out.push(results.join("\n"));
152    }
153    if !errors.is_empty() {
154        out.push(format!("errors:\n{}", errors.join("\n")));
155    }
156    if out.is_empty() {
157        "terraform apply (no summary parsed)".to_string()
158    } else {
159        out.join("\n\n")
160    }
161}
162
163fn compress_init(output: &str) -> String {
164    let mut providers: Vec<String> = Vec::new();
165    let mut success = false;
166
167    for line in output.lines() {
168        let tl = line.trim();
169        if tl.is_empty() {
170            continue;
171        }
172        let ll = tl.to_ascii_lowercase();
173        if ll.contains("terraform has been successfully initialized")
174            || ll.contains("initialization complete")
175        {
176            success = true;
177        }
178        if let Some(caps) = installed_provider_re().captures(tl) {
179            let name = caps.get(1).map_or("?", |m| m.as_str());
180            let ver = caps.get(2).map_or("?", |m| m.as_str());
181            providers.push(format!("{name} v{ver}"));
182            continue;
183        }
184        if let Some(caps) = provider_version_re().captures(tl) {
185            let reg = caps.get(1).map_or("?", |m| m.as_str());
186            let ver = caps.get(2).map_or("?", |m| m.as_str());
187            providers.push(format!("{reg} {ver}"));
188        }
189    }
190
191    let status = if success {
192        "Terraform initialized"
193    } else {
194        "terraform init"
195    };
196
197    if providers.is_empty() {
198        status.to_string()
199    } else {
200        format!("{status}\n{}", providers.join(", "))
201    }
202}
203
204fn compress_validate(output: &str) -> String {
205    let mut errs = Vec::new();
206    for line in output.lines() {
207        let tl = line.trim();
208        if tl.is_empty() {
209            continue;
210        }
211        let ll = tl.to_ascii_lowercase();
212        if ll.contains("success!") && ll.contains("configuration is valid") {
213            return "Success".to_string();
214        }
215        if ll.contains("error") || tl.starts_with('╷') || tl.starts_with('│') {
216            errs.push(tl.to_string());
217        }
218    }
219    if errs.is_empty() {
220        "Success".to_string()
221    } else {
222        errs.join("\n")
223    }
224}
225
226fn compress_generic(output: &str) -> String {
227    let mut lines: Vec<String> = output
228        .lines()
229        .filter(|l| !is_provider_init_noise(l))
230        .map(|l| l.trim().to_string())
231        .filter(|l| !l.is_empty())
232        .collect();
233    if lines.len() > 40 {
234        let n = lines.len();
235        lines = lines.split_off(n - 25);
236        format!("... (truncated)\n{}", lines.join("\n"))
237    } else {
238        lines.join("\n")
239    }
240}