vault_audit_tools/commands/
kv_summary.rs1use anyhow::{Context, Result};
45use std::fs::File;
46use std::io::BufReader;
47
48fn format_number(n: usize) -> String {
49 let s = n.to_string();
50 let mut result = String::new();
51 for (i, c) in s.chars().rev().enumerate() {
52 if i > 0 && i % 3 == 0 {
53 result.push(',');
54 }
55 result.push(c);
56 }
57 result.chars().rev().collect()
58}
59
60pub fn run(csv_file: &str) -> Result<()> {
61 let file = File::open(csv_file).context("Failed to open CSV file")?;
62 let reader = BufReader::new(file);
63 let mut csv_reader = csv::Reader::from_reader(reader);
64
65 let mut rows = Vec::new();
66 for result in csv_reader.records() {
67 let record = result?;
68 rows.push(record);
69 }
70
71 if rows.is_empty() {
72 println!("No data found in {}", csv_file);
73 return Ok(());
74 }
75
76 println!("\n{}", "=".repeat(70));
77 println!("{:^70}", "KV Usage Summary Report");
78 println!("{:^70}", format!("Source: {}", csv_file));
79 println!("{}\n", "=".repeat(70));
80
81 let total_paths = rows.len();
83 let mut total_clients = 0;
84 let mut total_operations = 0;
85
86 let headers = csv_reader.headers()?.clone();
88 let unique_clients_idx = headers.iter().position(|h| h == "unique_clients");
89 let operations_idx = headers.iter().position(|h| h == "operations_count");
90
91 for row in &rows {
92 if let Some(idx) = unique_clients_idx {
93 if let Ok(n) = row.get(idx).unwrap_or("0").parse::<usize>() {
94 total_clients += n;
95 }
96 }
97 if let Some(idx) = operations_idx {
98 if let Ok(n) = row.get(idx).unwrap_or("0").parse::<usize>() {
99 total_operations += n;
100 }
101 }
102 }
103
104 println!("Overview:");
105 println!(" • Total KV Paths: {}", total_paths);
106 println!(
107 " • Total Unique Clients: {}",
108 format_number(total_clients)
109 );
110 println!(" • Total Operations: {}", format_number(total_operations));
111 println!("\n{}\n", "-".repeat(70));
112
113 let kv_path_idx = headers.iter().position(|h| h == "kv_path");
115 let entity_ids_idx = headers.iter().position(|h| h == "entity_ids");
116 let alias_names_idx = headers.iter().position(|h| h == "alias_names");
117 let sample_paths_idx = headers.iter().position(|h| h == "sample_paths_accessed");
118
119 for (i, row) in rows.iter().enumerate() {
120 println!(
121 "{}. KV Path: {}",
122 i + 1,
123 kv_path_idx.and_then(|idx| row.get(idx)).unwrap_or("N/A")
124 );
125
126 if let Some(idx) = unique_clients_idx {
127 println!(" Unique Clients: {}", row.get(idx).unwrap_or("0"));
128 }
129
130 if let Some(idx) = operations_idx {
131 println!(" Total Operations: {}", row.get(idx).unwrap_or("0"));
132 }
133
134 if let Some(idx) = entity_ids_idx {
135 println!(" Entity IDs: {}", row.get(idx).unwrap_or("N/A"));
136 }
137
138 if let Some(idx) = alias_names_idx {
139 if let Some(names) = row.get(idx) {
140 if !names.is_empty() {
141 println!(" Alias Names: {}", names);
142 }
143 }
144 }
145
146 if let Some(idx) = sample_paths_idx {
147 if let Some(paths) = row.get(idx) {
148 let display_paths = if paths.len() > 80 {
149 format!("{}...", &paths[..77])
150 } else {
151 paths.to_string()
152 };
153 println!(" Sample Paths: {}", display_paths);
154 }
155 }
156
157 println!();
158 }
159
160 println!("{}", "-".repeat(70));
161 println!("Report complete. Analyzed {} KV paths.\n", total_paths);
162
163 Ok(())
164}