system_harness/
container.rs1use crate::{Error, ErrorKind, Status, SystemHarness, SystemTerminal};
2use serde::{Deserialize, Serialize};
3use std::io::{Read, Write};
4use std::process::{Command, Output, Stdio, Child};
5
6fn strip_last_newline(input: &str) -> &str {
7 input
8 .strip_suffix("\r\n")
9 .or(input.strip_suffix("\n"))
10 .unwrap_or(input)
11}
12
13fn output_to_result(output: Output) -> Result<String, Error> {
15 match output.status.success() {
16 true => Ok(strip_last_newline(
17 std::str::from_utf8(&output.stdout)?
18 ).to_string()),
19 false => {
20 let error = std::str::from_utf8(&output.stderr)?;
21 Err(Error::new(ErrorKind::HarnessError, error))
22 }
23 }
24}
25
26#[derive(Clone, Serialize, Deserialize)]
28pub struct ContainerSystemConfig {
29
30 tool: String,
32
33 image: String,
35
36}
37
38impl ContainerSystemConfig {
39
40 pub fn build(&self) -> Result<ContainerSystem, Error> {
42 let id = Command::new(&self.tool)
43 .arg("create")
44 .arg("-t")
45 .arg(&self.image)
46 .output()
47 .map_err(|err| err.into())
48 .and_then(output_to_result)
49 .map_err(|err| { log::warn!("{err}"); err })?;
50 log::trace!("Created container: {id}");
51
52 Command::new(&self.tool)
53 .stdout(Stdio::null())
54 .arg("start")
55 .arg(&id)
56 .status()?;
57
58 Ok(ContainerSystem {
59 id,
60 tool: self.tool.clone()
61 })
62 }
63
64}
65
66pub struct ContainerSystem {
67 tool: String,
68 id: String,
69}
70
71pub struct ContainerSystemTerminal {
72 process: Child
73}
74
75#[derive(Deserialize)]
76#[serde(rename_all = "PascalCase")]
77struct State {
78 running: bool,
79 paused: bool
80}
81
82#[derive(Deserialize)]
83#[serde(rename_all = "PascalCase")]
84struct Inspect {
85 state: State
86}
87
88impl SystemTerminal for ContainerSystemTerminal {
89
90 fn send_key(&mut self, _key: crate::Key) -> Result<(), Error> {
91 Err(Error::new(ErrorKind::HarnessError, "Sending a keystroke not supported"))
92 }
93
94}
95
96impl Read for ContainerSystemTerminal {
97 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
98 self.process.stdout.as_mut()
99 .ok_or(std::io::Error::new(
100 std::io::ErrorKind::BrokenPipe,
101 "Can't read from container"
102 ))
103 .and_then(|stdout| stdout.read(buf))
104 }
105}
106
107impl Write for ContainerSystemTerminal {
108 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
109 self.process.stdin.as_mut()
110 .ok_or(std::io::Error::new(
111 std::io::ErrorKind::BrokenPipe,
112 "Can't write to container"
113 ))
114 .and_then(|stdin| stdin.write(buf))
115 }
116
117 fn flush(&mut self) -> std::io::Result<()> {
118 self.process.stdin.as_mut()
119 .ok_or(std::io::Error::new(
120 std::io::ErrorKind::BrokenPipe,
121 "Can't write to container"
122 ))
123 .and_then(|stdin| stdin.flush())
124 }
125}
126
127
128
129impl SystemHarness for ContainerSystem {
130
131 type Terminal = ContainerSystemTerminal;
132
133 fn terminal(&self) -> Result<Self::Terminal, Error> {
134 let process = Command::new(&self.tool)
135 .stdin(Stdio::piped())
136 .stdout(Stdio::piped())
137 .arg("exec")
138 .arg("-it")
139 .arg(&self.id)
140 .arg("sh")
141 .spawn()?;
142 Ok(Self::Terminal {
143 process
144 })
145 }
146
147 fn pause(&mut self) -> Result<(), Error> {
148 log::trace!("Pausing container: {}", &self.id);
149 Command::new(&self.tool)
150 .arg("pause")
151 .arg(&self.id)
152 .output()
153 .map_err(|err| err.into())
154 .and_then(output_to_result)
155 .map(|_| log::trace!("Paused container: {}", self.id))
156 }
157
158 fn resume(&mut self) -> Result<(), Error> {
159 log::trace!("Resuming container: {}", &self.id);
160 Command::new(&self.tool)
161 .arg("unpause")
162 .arg(&self.id)
163 .output()
164 .map_err(|err| err.into())
165 .and_then(output_to_result)
166 .map(|_| log::trace!("Resumed container: {}", self.id))
167 }
168
169 fn shutdown(&mut self) -> Result<(), Error> {
170 log::trace!("Shutting down container: {}", &self.id);
171 Command::new(&self.tool)
172 .arg("stop")
173 .arg(&self.id)
174 .output()
175 .map_err(|err| err.into())
176 .and_then(output_to_result)
177 .map(|_| log::trace!("Stopped container: {}", self.id))
178 }
179
180 fn status(&mut self) -> Result<Status, Error> {
181 Command::new(&self.tool)
182 .arg("inspect")
183 .arg(&self.id)
184 .output()
185 .map_err(|err| err.into())
186 .and_then(output_to_result)
187 .map_err(|err| { log::warn!("{err}"); err })
188 .and_then(|stdout| {
189 let inspect: Vec<Inspect> = serde_json::from_str(&stdout)?;
190 inspect.into_iter()
191 .next()
192 .ok_or(Error::new(ErrorKind::HarnessError, "Container doesn't exist"))
193 .and_then(|inspect| {
194 let state = &inspect.state;
195 if state.running {
196 Ok(Status::Running)
197 } else if state.paused {
198 Ok(Status::Paused)
199 } else if !state.running && !state.paused {
200 Ok(Status::Shutdown)
201 } else {
202 Err(Error::new(ErrorKind::HarnessError,
203 format!("Unhandled status")))
204 }
205 })
206 })
207 }
208
209 fn running(&mut self) -> Result<bool, Error> {
210 self.status().map(|status| status == Status::Running)
211 }
212
213}
214
215impl Drop for ContainerSystem {
216 fn drop(&mut self) {
217 if let Ok(running) = self.running() {
218 if running {
219 if let Ok(()) = self.shutdown() {
220 log::trace!("Deleting container: {}", &self.id);
221 let _ = Command::new(&self.tool)
222 .args(&["rm", "-f", &self.id])
223 .output();
224 } else {
225 log::warn!("Failed to shutdown: {}", &self.id);
226 }
227 }
228 }
229 }
230}