Skip to content

Commit be26060

Browse files
vsilentCopilot
andcommitted
fix: address PR #84 review comments, bump to v0.1.6
- Gate runtime_compose_tests with #[cfg(all(test, feature = "docker"))] - Replace blocking Path::exists() with tokio::fs::try_exists() in deploy - Cache detect_kata_runtime() with OnceLock + 5s timeout - Log errors in unlink_handler try_exists path - Bump version to 0.1.6 - Update CHANGELOG Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 367af3b commit be26060

5 files changed

Lines changed: 107 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## 0.1.6 — 2026-04-08
4+
### Added — Kata Containers Runtime Support
5+
6+
#### Container Runtime Selection (`commands/stacker.rs`)
7+
- `ContainerRuntime` enum (`runc`/`kata`) with serde support and Docker runtime name mapping
8+
- `detect_kata_runtime()` — cached detection via `docker info` with 5s timeout and `OnceLock`
9+
- `inject_runtime_into_compose()` — parses compose YAML and injects `runtime:` per-service
10+
- `DeployAppCommand` and `DeployWithConfigsCommand` accept optional `runtime` field
11+
- Graceful fallback: if Kata is requested but unavailable, deploys with runc and emits `kata_fallback` warning
12+
- Effective runtime reported in deploy result body
13+
14+
#### Capabilities Discovery (`comms/local_api.rs`)
15+
- `/capabilities` endpoint reports `"kata"` in features list when Kata runtime is detected on the host
16+
17+
#### Code Quality Fixes (PR #84 review)
18+
- `runtime_compose_tests` gated with `#[cfg(all(test, feature = "docker"))]` for minimal builds
19+
- Replaced blocking `std::path::Path::exists()` with `tokio::fs::try_exists()` in async deploy path
20+
- Added proper error logging in `unlink_handler` for `try_exists` failures
21+
22+
#### Tests
23+
- 14 new tests: enum behavior, serde deserialization, compose YAML injection (including edge cases), command parsing with runtime field
24+
25+
## 0.1.5 — 2026-03-26
26+
### Added — Long Polling, Vault Integration, Compose Agent Sidecar
27+
328
## 0.1.4 — 2026-03-13
429
### Added — CLI Improvements, Install Script & GitHub Releases
530

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "status-panel"
3-
version = "0.1.5"
3+
version = "0.1.6"
44
edition = "2021"
55

66
[features]

src/commands/stacker.rs

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,38 @@ impl ContainerRuntime {
5858

5959
/// Detect whether the Kata Containers runtime is available on this host.
6060
/// Checks `docker info` output for a registered kata runtime.
61+
/// Result is cached for the lifetime of the process via `OnceLock`.
6162
#[cfg(feature = "docker")]
6263
pub async fn detect_kata_runtime() -> bool {
64+
use std::sync::OnceLock;
6365
use tokio::process::Command;
6466

65-
let output = Command::new("docker")
66-
.args(["info", "--format", "{{json .Runtimes}}"])
67-
.output()
68-
.await;
67+
static KATA_AVAILABLE: OnceLock<bool> = OnceLock::new();
6968

70-
match output {
71-
Ok(out) if out.status.success() => {
72-
let stdout = String::from_utf8_lossy(&out.stdout);
73-
let lower = stdout.to_lowercase();
74-
lower.contains("kata") || lower.contains("io.containerd.kata")
75-
}
76-
_ => false,
69+
if let Some(&cached) = KATA_AVAILABLE.get() {
70+
return cached;
7771
}
72+
73+
let result = tokio::time::timeout(std::time::Duration::from_secs(5), async {
74+
let output = Command::new("docker")
75+
.args(["info", "--format", "{{json .Runtimes}}"])
76+
.output()
77+
.await;
78+
79+
match output {
80+
Ok(out) if out.status.success() => {
81+
let stdout = String::from_utf8_lossy(&out.stdout);
82+
let lower = stdout.to_lowercase();
83+
lower.contains("kata") || lower.contains("io.containerd.kata")
84+
}
85+
_ => false,
86+
}
87+
})
88+
.await
89+
.unwrap_or(false);
90+
91+
let _ = KATA_AVAILABLE.set(result);
92+
result
7893
}
7994

8095
/// Inject a `runtime` field into every service definition of a docker-compose YAML string.
@@ -2718,33 +2733,51 @@ async fn handle_deploy_app(
27182733
);
27192734
} else if let Some(runtime) = &data.runtime {
27202735
// Compose content not in payload — modify existing file on disk if runtime is requested
2721-
if *runtime == ContainerRuntime::Kata && std::path::Path::new(&compose_file).exists() {
2722-
let kata_available = detect_kata_runtime().await;
2723-
if kata_available {
2724-
if let Ok(existing) = tokio::fs::read_to_string(&compose_file).await {
2725-
let injected = inject_runtime_into_compose(&existing, runtime);
2726-
if let Err(e) = tokio::fs::write(&compose_file, &injected).await {
2736+
if *runtime == ContainerRuntime::Kata {
2737+
match tokio::fs::try_exists(&compose_file).await {
2738+
Ok(true) => {
2739+
let kata_available = detect_kata_runtime().await;
2740+
if kata_available {
2741+
if let Ok(existing) = tokio::fs::read_to_string(&compose_file).await {
2742+
let injected = inject_runtime_into_compose(&existing, runtime);
2743+
if let Err(e) = tokio::fs::write(&compose_file, &injected).await {
2744+
errors.push(make_error(
2745+
"runtime_inject_warning",
2746+
format!(
2747+
"Failed to inject Kata runtime into compose file: {}",
2748+
e
2749+
),
2750+
None,
2751+
));
2752+
} else {
2753+
tracing::info!(
2754+
compose_file = %compose_file,
2755+
"Injected Kata runtime into existing compose file"
2756+
);
2757+
}
2758+
}
2759+
} else {
2760+
tracing::warn!(
2761+
"Kata runtime requested but not available on this host — falling back to runc"
2762+
);
27272763
errors.push(make_error(
2728-
"runtime_inject_warning",
2729-
format!("Failed to inject Kata runtime into compose file: {}", e),
2764+
"kata_fallback",
2765+
"Kata runtime requested but not available on this host; deploying with runc",
27302766
None,
27312767
));
2732-
} else {
2733-
tracing::info!(
2734-
compose_file = %compose_file,
2735-
"Injected Kata runtime into existing compose file"
2736-
);
27372768
}
27382769
}
2739-
} else {
2740-
tracing::warn!(
2741-
"Kata runtime requested but not available on this host — falling back to runc"
2742-
);
2743-
errors.push(make_error(
2744-
"kata_fallback",
2745-
"Kata runtime requested but not available on this host; deploying with runc",
2746-
None,
2747-
));
2770+
Ok(false) => {}
2771+
Err(e) => {
2772+
errors.push(make_error(
2773+
"runtime_inject_warning",
2774+
format!(
2775+
"Failed to check compose file before runtime injection: {}",
2776+
e
2777+
),
2778+
None,
2779+
));
2780+
}
27482781
}
27492782
}
27502783
}
@@ -5028,7 +5061,7 @@ mod tests {
50285061
);
50295062
}
50305063

5031-
#[cfg(test)]
5064+
#[cfg(all(test, feature = "docker"))]
50325065
mod runtime_compose_tests {
50335066
use super::*;
50345067

src/comms/local_api.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,11 +1234,19 @@ async fn link_select_handler(
12341234

12351235
async fn unlink_handler(State(state): State<SharedState>) -> impl IntoResponse {
12361236
let reg_path = "/etc/status-panel/registration.json";
1237-
if tokio::fs::try_exists(reg_path).await.unwrap_or(false) {
1238-
if let Err(e) = tokio::fs::remove_file(reg_path).await {
1239-
error!("failed to remove registration: {}", e);
1240-
} else {
1241-
info!("dashboard unlinked, registration removed");
1237+
match tokio::fs::try_exists(reg_path).await {
1238+
Ok(true) => {
1239+
if let Err(e) = tokio::fs::remove_file(reg_path).await {
1240+
error!("failed to remove registration: {}", e);
1241+
} else {
1242+
info!("dashboard unlinked, registration removed");
1243+
}
1244+
}
1245+
Ok(false) => {
1246+
info!("unlink requested but no registration file found");
1247+
}
1248+
Err(e) => {
1249+
error!("failed to check registration file at {}: {}", reg_path, e);
12421250
}
12431251
}
12441252
if state.with_ui {

0 commit comments

Comments
 (0)