Skip to content

Commit 90c285c

Browse files
authored
Merge pull request #1895 from rtk-ai/fix/aggressive-filters-batch
fix(filters): aggresivity batch fix
2 parents d6c5647 + f21b864 commit 90c285c

16 files changed

Lines changed: 816 additions & 164 deletions

File tree

src/cmds/cloud/container.rs

Lines changed: 170 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::process::Command;
1212
#[derive(Debug, Clone, Copy)]
1313
pub enum ContainerCmd {
1414
DockerPs,
15+
DockerPsAll,
1516
DockerImages,
1617
DockerLogs,
1718
KubectlPods,
@@ -22,6 +23,7 @@ pub enum ContainerCmd {
2223
pub fn run(cmd: ContainerCmd, args: &[String], verbose: u8) -> Result<i32> {
2324
match cmd {
2425
ContainerCmd::DockerPs => docker_ps(verbose),
26+
ContainerCmd::DockerPsAll => docker_ps_all(verbose),
2527
ContainerCmd::DockerImages => docker_images(verbose),
2628
ContainerCmd::DockerLogs => docker_logs(args, verbose),
2729
ContainerCmd::KubectlPods => kubectl_pods(args, verbose),
@@ -81,40 +83,136 @@ fn docker_ps(_verbose: u8) -> Result<i32> {
8183
return Ok(0);
8284
}
8385

84-
let count = stdout.lines().count();
85-
rtk.push_str(&format!("[docker] {} containers:\n", count));
86+
const MAX_CONTAINERS: usize = 20;
87+
let lines: Vec<String> = stdout
88+
.lines()
89+
.filter(|l| !l.trim().is_empty())
90+
.filter_map(|line| format_container_line(line, true))
91+
.collect();
92+
93+
rtk.push_str(&format!("[docker] {} containers:\n", lines.len()));
94+
for entry in lines.iter().take(MAX_CONTAINERS) {
95+
rtk.push_str(entry);
96+
}
97+
if lines.len() > MAX_CONTAINERS {
98+
rtk.push_str(&format!(" … +{} more\n", lines.len() - MAX_CONTAINERS));
99+
let full: String = lines.concat();
100+
if let Some(hint) = crate::core::tee::force_tee_hint(&full, "docker-ps") {
101+
rtk.push_str(&format!("{}\n", hint));
102+
}
103+
}
104+
105+
print!("{}", rtk);
106+
timer.track("docker ps", "rtk docker ps", &raw, &rtk);
107+
Ok(0)
108+
}
109+
110+
fn docker_ps_all(_verbose: u8) -> Result<i32> {
111+
let timer = tracking::TimedExecution::start();
112+
113+
let raw = exec_capture(resolved_command("docker").args(["ps", "-a"]))
114+
.map(|r| r.stdout)
115+
.unwrap_or_default();
86116

87-
for line in stdout.lines().take(15) {
117+
let result = exec_capture(resolved_command("docker").args([
118+
"ps",
119+
"-a",
120+
"--format",
121+
"{{.State}}\t{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}",
122+
]))
123+
.context("Failed to run docker ps -a")?;
124+
125+
if !result.success() {
126+
eprint!("{}", result.stderr);
127+
timer.track("docker ps -a", "rtk docker ps -a", &raw, &raw);
128+
return Ok(result.exit_code);
129+
}
130+
131+
let mut running_lines: Vec<String> = Vec::new();
132+
let mut stopped_lines: Vec<String> = Vec::new();
133+
for line in result.stdout.lines().filter(|l| !l.trim().is_empty()) {
88134
let parts: Vec<&str> = line.split('\t').collect();
89-
if parts.len() >= 4 {
90-
let id = &parts[0][..12.min(parts[0].len())];
91-
let name = parts[1];
92-
let short_image = parts
93-
.get(3)
94-
.unwrap_or(&"")
95-
.split('/')
96-
.next_back()
97-
.unwrap_or("");
98-
let ports = compact_ports(parts.get(4).unwrap_or(&""));
99-
if ports == "-" {
100-
rtk.push_str(&format!(" {} {} ({})\n", id, name, short_image));
135+
let state = parts.first().copied().unwrap_or("");
136+
let is_running = matches!(state, "running" | "restarting");
137+
if let Some(entry) = format_container_line_from_parts(&parts[1..], is_running) {
138+
if is_running {
139+
running_lines.push(entry);
101140
} else {
102-
rtk.push_str(&format!(
103-
" {} {} ({}) [{}]\n",
104-
id, name, short_image, ports
105-
));
141+
stopped_lines.push(entry);
106142
}
107143
}
108144
}
109-
if count > 15 {
110-
rtk.push_str(&format!(" ... +{} more", count - 15));
145+
146+
const MAX_CONTAINERS: usize = 20;
147+
let truncated = running_lines.len() > MAX_CONTAINERS || stopped_lines.len() > MAX_CONTAINERS;
148+
149+
let mut rtk = String::new();
150+
rtk.push_str(&format!("[docker] {} running:\n", running_lines.len()));
151+
for l in running_lines.iter().take(MAX_CONTAINERS) {
152+
rtk.push_str(l);
153+
}
154+
if running_lines.len() > MAX_CONTAINERS {
155+
rtk.push_str(&format!(
156+
" … +{} more\n",
157+
running_lines.len() - MAX_CONTAINERS
158+
));
159+
}
160+
if !stopped_lines.is_empty() {
161+
rtk.push_str(&format!(
162+
"[docker] {} stopped/exited:\n",
163+
stopped_lines.len()
164+
));
165+
for l in stopped_lines.iter().take(MAX_CONTAINERS) {
166+
rtk.push_str(l);
167+
}
168+
if stopped_lines.len() > MAX_CONTAINERS {
169+
rtk.push_str(&format!(
170+
" … +{} more\n",
171+
stopped_lines.len() - MAX_CONTAINERS
172+
));
173+
}
174+
}
175+
if truncated {
176+
let full: String = running_lines.iter().chain(stopped_lines.iter()).cloned().collect();
177+
if let Some(hint) = crate::core::tee::force_tee_hint(&full, "docker-ps-a") {
178+
rtk.push_str(&format!("{}\n", hint));
179+
}
111180
}
112181

113182
print!("{}", rtk);
114-
timer.track("docker ps", "rtk docker ps", &raw, &rtk);
183+
timer.track("docker ps -a", "rtk docker ps -a", &raw, &rtk);
115184
Ok(0)
116185
}
117186

187+
fn format_container_line(line: &str, with_ports: bool) -> Option<String> {
188+
let parts: Vec<&str> = line.split('\t').collect();
189+
format_container_line_from_parts(&parts, with_ports)
190+
}
191+
192+
fn format_container_line_from_parts(parts: &[&str], with_ports: bool) -> Option<String> {
193+
if parts.len() < 4 {
194+
return None;
195+
}
196+
let id = &parts[0][..12.min(parts[0].len())];
197+
let name = parts[1];
198+
let status = parts[2].trim();
199+
let short_image = parts[3].split('/').next_back().unwrap_or("");
200+
let port_suffix = if with_ports {
201+
let ports = compact_ports(parts.get(4).unwrap_or(&""));
202+
if ports == "-" {
203+
String::new()
204+
} else {
205+
format!(" [{}]", ports)
206+
}
207+
} else {
208+
String::new()
209+
};
210+
Some(format!(
211+
" {} {} ({}) {}{}\n",
212+
id, name, short_image, status, port_suffix
213+
))
214+
}
215+
118216
fn docker_images(_verbose: u8) -> Result<i32> {
119217
let timer = tracking::TimedExecution::start();
120218

@@ -173,21 +271,35 @@ fn docker_images(_verbose: u8) -> Result<i32> {
173271
total_display
174272
));
175273

176-
for line in lines.iter().take(15) {
177-
let parts: Vec<&str> = line.split('\t').collect();
178-
if !parts.is_empty() {
179-
let image = parts[0];
180-
let size = parts.get(1).unwrap_or(&"");
181-
let short = if image.len() > 40 {
182-
format!("...{}", &image[image.len() - 37..])
183-
} else {
184-
image.to_string()
185-
};
186-
rtk.push_str(&format!(" {} [{}]\n", short, size));
187-
}
274+
// Show images with their full `repository:tag` name — truncating the
275+
// registry/user prefix to "..." breaks exact-match lookups against
276+
// deployment manifests and CI configs. The list is generously capped (a
277+
// higher bound than before, and only the count, never the names, is
278+
// abbreviated) so token savings still hold on machines with many images.
279+
const MAX_IMAGES: usize = 60;
280+
let image_lines: Vec<String> = lines
281+
.iter()
282+
.map(|line| {
283+
let parts: Vec<&str> = line.split('\t').collect();
284+
let image = parts.first().copied().unwrap_or("");
285+
let size = parts.get(1).copied().unwrap_or("");
286+
format!(" {} [{}]\n", image, size)
287+
})
288+
.collect();
289+
290+
let mut full_rtk = rtk.clone();
291+
for l in &image_lines {
292+
full_rtk.push_str(l);
293+
}
294+
295+
for l in image_lines.iter().take(MAX_IMAGES) {
296+
rtk.push_str(l);
188297
}
189-
if lines.len() > 15 {
190-
rtk.push_str(&format!(" ... +{} more", lines.len() - 15));
298+
if image_lines.len() > MAX_IMAGES {
299+
rtk.push_str(&format!(" … +{} more\n", image_lines.len() - MAX_IMAGES));
300+
if let Some(hint) = crate::core::tee::force_tee_tail_hint(&full_rtk, "docker-images", MAX_IMAGES + 2) {
301+
rtk.push_str(&format!("{}\n", hint));
302+
}
191303
}
192304

193305
print!("{}", rtk);
@@ -294,7 +406,7 @@ fn format_kubectl_pods(json: &Value) -> String {
294406
out.push_str(&format!(" {}\n", issue));
295407
}
296408
if issues.len() > 10 {
297-
out.push_str(&format!(" ... +{} more", issues.len() - 10));
409+
out.push_str(&format!(" +{} more", issues.len() - 10));
298410
}
299411
}
300412
out
@@ -347,7 +459,7 @@ fn format_kubectl_services(json: &Value) -> String {
347459
));
348460
}
349461
if services.len() > 15 {
350-
out.push_str(&format!(" ... +{} more", services.len() - 15));
462+
out.push_str(&format!(" +{} more", services.len() - 15));
351463
}
352464
out
353465
}
@@ -421,7 +533,7 @@ pub fn format_compose_ps(raw: &str) -> String {
421533
}
422534
}
423535
if lines.len() > 20 {
424-
result.push_str(&format!(" ... +{} more\n", lines.len() - 20));
536+
result.push_str(&format!(" +{} more\n", lines.len() - 20));
425537
}
426538

427539
result.trim_end().to_string()
@@ -511,7 +623,7 @@ fn compact_ports(ports: &str) -> String {
511623
port_nums.join(", ")
512624
} else {
513625
format!(
514-
"{}, ... +{}",
626+
"{}, +{}",
515627
port_nums[..2].join(", "),
516628
port_nums.len() - 2
517629
)
@@ -522,12 +634,15 @@ pub fn run_docker_passthrough(args: &[OsString], verbose: u8) -> Result<i32> {
522634
crate::core::runner::run_passthrough("docker", args, verbose)
523635
}
524636

525-
/// Run `docker compose ps` with compact output
526-
pub fn run_compose_ps(verbose: u8) -> Result<i32> {
637+
/// Run `docker compose ps` (or `docker compose ps -a`) with compact output
638+
pub fn run_compose_ps(all: bool, verbose: u8) -> Result<i32> {
527639
let timer = tracking::TimedExecution::start();
528640

529-
// Raw output for token tracking
530-
let raw_result = exec_capture(resolved_command("docker").args(["compose", "ps"]))
641+
let mut raw_args: Vec<&str> = vec!["compose", "ps"];
642+
if all {
643+
raw_args.push("-a");
644+
}
645+
let raw_result = exec_capture(resolved_command("docker").args(&raw_args))
531646
.context("Failed to run docker compose ps")?;
532647

533648
if !raw_result.success() {
@@ -536,14 +651,13 @@ pub fn run_compose_ps(verbose: u8) -> Result<i32> {
536651
}
537652
let raw = raw_result.stdout;
538653

539-
// Structured output for parsing (same pattern as docker_ps)
540-
let result = exec_capture(resolved_command("docker").args([
541-
"compose",
542-
"ps",
543-
"--format",
544-
"{{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}",
545-
]))
546-
.context("Failed to run docker compose ps --format")?;
654+
let mut format_args: Vec<&str> = vec!["compose", "ps"];
655+
if all {
656+
format_args.push("-a");
657+
}
658+
format_args.extend(["--format", "{{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"]);
659+
let result = exec_capture(resolved_command("docker").args(&format_args))
660+
.context("Failed to run docker compose ps --format")?;
547661

548662
if !result.success() {
549663
eprintln!("{}", result.stderr);
@@ -557,7 +671,9 @@ pub fn run_compose_ps(verbose: u8) -> Result<i32> {
557671

558672
let rtk = format_compose_ps(&structured);
559673
println!("{}", rtk);
560-
timer.track("docker compose ps", "rtk docker compose ps", &raw, &rtk);
674+
let label = if all { "docker compose ps -a" } else { "docker compose ps" };
675+
let rtk_label = if all { "rtk docker compose ps -a" } else { "rtk docker compose ps" };
676+
timer.track(label, rtk_label, &raw, &rtk);
561677
Ok(0)
562678
}
563679

@@ -789,7 +905,7 @@ api-1 | Connected to database";
789905
#[test]
790906
fn test_compact_ports_many() {
791907
let result = compact_ports("0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:9090->9090/tcp");
792-
assert!(result.contains("..."), "should truncate for >3 ports");
908+
assert!(result.contains(""), "should truncate for >3 ports");
793909
}
794910

795911
#[test]

0 commit comments

Comments
 (0)