Skip to main content

osdk_core/container/
runtime.rs

1//! Native-runtime discovery and foreground delegation contracts.
2
3use std::io;
4use std::process::ExitStatus;
5
6use super::redact::{CommandPurpose, NativeProgram, RedactedCommand};
7use super::report::{DiagnosticReport, RuntimeKind};
8use crate::process::{CaptureLimits, CommandOutcome, CommandRunner, CommandSpec};
9
10/// An injectable native-runtime adapter. Implementations parse captured raw
11/// output into typed report fields and must discard the raw bytes afterward.
12pub trait RuntimeAdapter: Send + Sync {
13    fn kind(&self) -> RuntimeKind;
14
15    fn diagnose(&self, runner: &dyn CommandRunner, limits: CaptureLimits) -> DiagnosticReport;
16}
17
18/// A bounded read-only command used during native-runtime discovery.
19///
20/// This value is intentionally not serializable because its raw command can
21/// contain endpoint selectors or other sensitive arguments. Use `evidence()`
22/// when populating a diagnostic report.
23#[derive(Debug)]
24pub struct ProbeCommand {
25    command: CommandSpec,
26    evidence: RedactedCommand,
27}
28
29impl ProbeCommand {
30    pub fn new(program: NativeProgram, purpose: CommandPurpose, command: CommandSpec) -> Self {
31        let evidence = RedactedCommand::from_spec(program, purpose, &command);
32        Self { command, evidence }
33    }
34
35    pub fn evidence(&self) -> &RedactedCommand {
36        &self.evidence
37    }
38
39    pub fn execute(&self, runner: &dyn CommandRunner, limits: CaptureLimits) -> CommandOutcome {
40        runner.run_captured(&self.command, limits)
41    }
42}
43
44/// A single foreground native operation such as pull or prune.
45///
46/// `execute` consumes the value and delegates exactly once. It does not retry,
47/// wrap the command in a shell, or capture any inherited stream.
48#[derive(Debug)]
49pub struct ForegroundCommand {
50    command: CommandSpec,
51    evidence: RedactedCommand,
52}
53
54impl ForegroundCommand {
55    pub fn new(program: NativeProgram, purpose: CommandPurpose, command: CommandSpec) -> Self {
56        let evidence = RedactedCommand::from_spec(program, purpose, &command);
57        Self { command, evidence }
58    }
59
60    pub fn evidence(&self) -> &RedactedCommand {
61        &self.evidence
62    }
63
64    pub fn execute(self, runner: &dyn CommandRunner) -> io::Result<ExitStatus> {
65        runner.run_foreground(&self.command)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use std::sync::atomic::{AtomicUsize, Ordering};
72
73    use super::*;
74    use crate::process::CapturedOutput;
75
76    #[derive(Default)]
77    struct FakeRunner {
78        captured_calls: AtomicUsize,
79        foreground_calls: AtomicUsize,
80    }
81
82    impl CommandRunner for FakeRunner {
83        fn run_captured(&self, _command: &CommandSpec, _limits: CaptureLimits) -> CommandOutcome {
84            self.captured_calls.fetch_add(1, Ordering::Relaxed);
85            CommandOutcome::TimedOut {
86                output: CapturedOutput::default(),
87                termination: crate::process::TerminationStatus::Requested,
88            }
89        }
90
91        fn run_foreground(&self, _command: &CommandSpec) -> io::Result<ExitStatus> {
92            self.foreground_calls.fetch_add(1, Ordering::Relaxed);
93            Err(io::Error::other("synthetic foreground failure"))
94        }
95    }
96
97    #[test]
98    fn probe_uses_injected_runner_and_exposes_only_redacted_evidence() {
99        let runner = FakeRunner::default();
100        let probe = ProbeCommand::new(
101            NativeProgram::Docker,
102            CommandPurpose::ContextInspect,
103            CommandSpec::new("docker").args(["context", "inspect", "secret-context"]),
104        );
105
106        assert!(matches!(
107            probe.execute(&runner, CaptureLimits::default()),
108            CommandOutcome::TimedOut { .. }
109        ));
110        assert_eq!(runner.captured_calls.load(Ordering::Relaxed), 1);
111        let json = serde_json::to_string(probe.evidence()).unwrap();
112        assert!(!json.contains("secret-context"));
113        assert!(json.contains("context-inspect"));
114    }
115
116    #[test]
117    fn foreground_delegation_invokes_the_runner_exactly_once() {
118        let runner = FakeRunner::default();
119        let operation = ForegroundCommand::new(
120            NativeProgram::Docker,
121            CommandPurpose::Pull,
122            CommandSpec::new("docker").args(["pull", "registry.example/secret"]),
123        );
124
125        assert!(operation.execute(&runner).is_err());
126        assert_eq!(runner.foreground_calls.load(Ordering::Relaxed), 1);
127    }
128}