Skip to main content

lean_ctx/core/patterns/
linkerd.rs

1//! linkerd (`linkerd check`) output compression.
2//!
3//! `linkerd check` prints a `√`/`×` line per check, grouped under section
4//! headers, ending with a `Status check results are …` line. Passing checks are
5//! noise once you know the total; failures (with their indented hint detail) and
6//! the final verdict are what matter. We keep failures + a pass/fail tally.
7
8use crate::core::compressor::strip_ansi;
9
10pub fn compress(command: &str, output: &str) -> Option<String> {
11    let sub = command
12        .trim()
13        .strip_prefix("linkerd")
14        .map_or("", str::trim_start)
15        .split_whitespace()
16        .next()
17        .unwrap_or("");
18    if sub != "check" {
19        return None;
20    }
21    Some(compress_check(output))
22}
23
24fn compress_check(output: &str) -> String {
25    let mut kept: Vec<String> = Vec::new();
26    let mut passed = 0usize;
27    let mut failed = 0usize;
28    let mut in_failure = false;
29
30    for raw in output.lines() {
31        let line = strip_ansi(raw);
32        let t = line.trim_end();
33        let probe = t.trim();
34        if probe.is_empty() {
35            in_failure = false;
36            continue;
37        }
38        // section underlines like "-----".
39        if probe.chars().all(|c| c == '-') {
40            continue;
41        }
42        if probe.starts_with('√') || probe.starts_with('✓') {
43            passed += 1;
44            in_failure = false;
45            continue;
46        }
47        if probe.starts_with('×') || probe.starts_with('✗') {
48            failed += 1;
49            in_failure = true;
50            kept.push(probe.to_string());
51            continue;
52        }
53        // indented hint/detail lines belong to the preceding failed check.
54        if in_failure && (t.starts_with(' ') || t.starts_with('\t')) {
55            kept.push(probe.to_string());
56            continue;
57        }
58        let pl = probe.to_ascii_lowercase();
59        if pl.starts_with("status check results") {
60            kept.push(probe.to_string());
61            in_failure = false;
62        }
63    }
64
65    let tally = format!("linkerd check: {passed} passed, {failed} failed");
66    if kept.is_empty() {
67        return tally;
68    }
69    format!("{tally}\n{}", kept.join("\n"))
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    const CHECK: &str = "kubernetes-api\n--------------\n√ can initialize the client\n√ can query the Kubernetes API\n\nlinkerd-existence\n-----------------\n√ 'linkerd-config' config map exists\n× control plane pods are ready\n    some pods are not ready: linkerd-destination-abc\n    see https://linkerd.io/2/checks/#l5d-api-control-ready for hints\n\nStatus check results are ×\n";
77
78    #[test]
79    fn keeps_failures_and_verdict_drops_passing() {
80        let r = compress("linkerd check", CHECK).unwrap();
81        assert!(r.contains("× control plane pods are ready"), "{r}");
82        assert!(r.contains("some pods are not ready"), "keeps hint: {r}");
83        assert!(r.contains("Status check results are ×"), "{r}");
84        assert!(r.contains("3 passed, 1 failed"), "tally: {r}");
85        assert!(
86            !r.contains("can initialize the client"),
87            "drops passing: {r}"
88        );
89    }
90
91    #[test]
92    fn non_check_subcommand_passes_through() {
93        assert!(compress("linkerd viz stat deploy", "some table").is_none());
94    }
95}