Skip to content

Commit a92821f

Browse files
authored
Fix Apple Container status parsing across versions (#1214)
1 parent d739b6f commit a92821f

3 files changed

Lines changed: 101 additions & 40 deletions

File tree

crates/tools/src/sandbox/apple.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ use tokio::sync::{Mutex, RwLock};
1818

1919
#[cfg(target_os = "macos")]
2020
use super::containers::{
21-
apple_container_exec_args, apple_container_run_args, apple_container_status_from_inspect,
22-
is_apple_container_daemon_stale_error, is_apple_container_exists_error,
23-
is_apple_container_service_error, rebuildable_sandbox_image_tag, sandbox_image_exists,
24-
unmark_zombie,
21+
ContainerRunState, apple_container_exec_args, apple_container_run_args,
22+
apple_container_run_state_from_inspect, is_apple_container_daemon_stale_error,
23+
is_apple_container_exists_error, is_apple_container_service_error,
24+
rebuildable_sandbox_image_tag, sandbox_image_exists, unmark_zombie,
2525
};
2626
#[cfg(target_os = "macos")]
2727
use super::host::provision_packages;
@@ -316,9 +316,9 @@ impl AppleContainerSandbox {
316316
match output {
317317
Ok(output) if output.status.success() => {
318318
let stdout = String::from_utf8_lossy(&output.stdout);
319-
match apple_container_status_from_inspect(&stdout) {
320-
Some("running") => return Ok(()),
321-
Some("stopped") => {
319+
match apple_container_run_state_from_inspect(&stdout) {
320+
Some(ContainerRunState::Running) => return Ok(()),
321+
Some(ContainerRunState::Stopped | ContainerRunState::Exited) => {
322322
return Err(Error::message(format!(
323323
"container {name} failed to stay running after startup"
324324
)));
@@ -452,9 +452,9 @@ impl AppleContainerSandbox {
452452
return ContainerState::NotFound;
453453
}
454454

455-
match apple_container_status_from_inspect(&stdout) {
456-
Some("running") => ContainerState::Running,
457-
Some("stopped") => ContainerState::Stopped,
455+
match apple_container_run_state_from_inspect(&stdout) {
456+
Some(ContainerRunState::Running) => ContainerState::Running,
457+
Some(ContainerRunState::Stopped | ContainerRunState::Exited) => ContainerState::Stopped,
458458
_ => ContainerState::Unknown,
459459
}
460460
}

crates/tools/src/sandbox/containers.rs

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::collections::HashSet;
44

55
use {
6-
serde::Serialize,
6+
serde::{Deserialize, Serialize},
77
sha2::{Digest, Sha256},
88
tracing::warn,
99
};
@@ -433,6 +433,42 @@ pub enum ContainerRunState {
433433
Unknown,
434434
}
435435

436+
#[derive(Debug, Clone, Copy, Deserialize)]
437+
#[serde(rename_all = "lowercase")]
438+
enum AppleContainerState {
439+
Running,
440+
Stopped,
441+
Exited,
442+
#[serde(other)]
443+
Unknown,
444+
}
445+
446+
#[derive(Debug, Deserialize)]
447+
#[serde(untagged)]
448+
enum AppleContainerStatus {
449+
Legacy(AppleContainerState),
450+
Current { state: AppleContainerState },
451+
}
452+
453+
impl AppleContainerStatus {
454+
fn run_state(&self) -> ContainerRunState {
455+
match self {
456+
Self::Legacy(state) | Self::Current { state } => match state {
457+
AppleContainerState::Running => ContainerRunState::Running,
458+
AppleContainerState::Stopped => ContainerRunState::Stopped,
459+
AppleContainerState::Exited => ContainerRunState::Exited,
460+
AppleContainerState::Unknown => ContainerRunState::Unknown,
461+
},
462+
}
463+
}
464+
}
465+
466+
fn apple_container_run_state(entry: &serde_json::Value) -> Option<ContainerRunState> {
467+
let status =
468+
serde_json::from_value::<AppleContainerStatus>(entry.get("status")?.clone()).ok()?;
469+
Some(status.run_state())
470+
}
471+
436472
/// Which container backend manages this container.
437473
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
438474
#[serde(rename_all = "kebab-case")]
@@ -524,16 +560,7 @@ pub async fn list_running_containers_for_prefixes(
524560
{
525561
continue;
526562
}
527-
let state_str = entry
528-
.pointer("/status/state")
529-
.and_then(|v| v.as_str())
530-
.unwrap_or_default();
531-
let state = match state_str {
532-
"running" => ContainerRunState::Running,
533-
"stopped" => ContainerRunState::Stopped,
534-
"exited" => ContainerRunState::Exited,
535-
_ => ContainerRunState::Unknown,
536-
};
563+
let state = apple_container_run_state(&entry).unwrap_or(ContainerRunState::Unknown);
537564
let image = entry
538565
.pointer("/configuration/image/reference")
539566
.and_then(|v| v.as_str())
@@ -845,8 +872,8 @@ pub async fn remove_container(name: &str) -> Result<()> {
845872
match inspect {
846873
Ok(ref ins) if ins.status.success() => {
847874
let stdout = String::from_utf8_lossy(&ins.stdout);
848-
let status = apple_container_status_from_inspect(&stdout);
849-
if status == Some("running") {
875+
let status = apple_container_run_state_from_inspect(&stdout);
876+
if status == Some(ContainerRunState::Running) {
850877
// Container is genuinely running — return the rm error.
851878
let stderr = output
852879
.as_ref()
@@ -946,21 +973,14 @@ pub async fn restart_container_daemon() -> Result<()> {
946973
))
947974
}
948975

949-
pub(crate) fn apple_container_status_from_inspect(stdout: &str) -> Option<&'static str> {
976+
pub(crate) fn apple_container_run_state_from_inspect(stdout: &str) -> Option<ContainerRunState> {
950977
let inspect = stdout.trim();
951978
if inspect.is_empty() || inspect == "[]" {
952979
return None;
953980
}
954981

955-
if inspect.contains(r#""status":"running""#) {
956-
return Some("running");
957-
}
958-
959-
if inspect.contains(r#""status":"stopped""#) || inspect.contains(r#""status":"exited""#) {
960-
return Some("stopped");
961-
}
962-
963-
None
982+
let entries = serde_json::from_str::<Vec<serde_json::Value>>(inspect).ok()?;
983+
entries.first().and_then(apple_container_run_state)
964984
}
965985

966986
pub(crate) fn is_apple_container_service_error(stderr: &str) -> bool {

crates/tools/src/sandbox/tests/apple.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,60 @@ fn test_container_exec_shell_args_docker_keeps_standard_exec_shape() {
327327
}
328328

329329
#[test]
330-
fn test_apple_container_status_from_inspect() {
330+
fn test_apple_container_run_state_from_legacy_inspect() {
331331
assert_eq!(
332-
apple_container_status_from_inspect(
332+
apple_container_run_state_from_inspect(
333333
r#"[{"id":"abc","status":"running","configuration":{}}]"#
334334
),
335-
Some("running")
335+
Some(ContainerRunState::Running)
336336
);
337337
assert_eq!(
338-
apple_container_status_from_inspect(r#"[{"id":"abc","status":"stopped"}]"#),
339-
Some("stopped")
338+
apple_container_run_state_from_inspect(r#"[{"id":"abc","status":"stopped"}]"#),
339+
Some(ContainerRunState::Stopped)
340+
);
341+
assert_eq!(
342+
apple_container_run_state_from_inspect(r#"[{"id":"abc","status":"exited"}]"#),
343+
Some(ContainerRunState::Exited)
344+
);
345+
}
346+
347+
#[test]
348+
fn test_apple_container_run_state_from_current_inspect() {
349+
assert_eq!(
350+
apple_container_run_state_from_inspect(
351+
r#"[
352+
{
353+
"id": "abc",
354+
"status": { "state": "running" },
355+
"configuration": {}
356+
}
357+
]"#
358+
),
359+
Some(ContainerRunState::Running)
360+
);
361+
assert_eq!(
362+
apple_container_run_state_from_inspect(r#"[{"id":"abc","status":{"state":"stopped"}}]"#),
363+
Some(ContainerRunState::Stopped)
364+
);
365+
assert_eq!(
366+
apple_container_run_state_from_inspect(r#"[{"id":"abc","status":{"state":"exited"}}]"#),
367+
Some(ContainerRunState::Exited)
368+
);
369+
}
370+
371+
#[test]
372+
fn test_apple_container_run_state_rejects_missing_or_malformed_inspect() {
373+
assert_eq!(
374+
apple_container_run_state_from_inspect(r#"[{"id":"abc","status":{"state":"starting"}}]"#),
375+
Some(ContainerRunState::Unknown)
376+
);
377+
assert_eq!(apple_container_run_state_from_inspect("[]"), None);
378+
assert_eq!(apple_container_run_state_from_inspect(""), None);
379+
assert_eq!(apple_container_run_state_from_inspect("not json"), None);
380+
assert_eq!(
381+
apple_container_run_state_from_inspect(r#"[{"id":"abc"}]"#),
382+
None
340383
);
341-
assert_eq!(apple_container_status_from_inspect("[]"), None);
342-
assert_eq!(apple_container_status_from_inspect(""), None);
343384
}
344385

345386
#[test]

0 commit comments

Comments
 (0)