opendev_docker/
local_runtime.rs1use std::collections::HashMap;
7use std::path::Path;
8
9use tracing::info;
10
11use crate::errors::{DockerError, Result};
12use crate::models::{
13 BashAction, BashObservation, CheckMode, CloseSessionRequest, CloseSessionResponse,
14 CreateSessionRequest, CreateSessionResponse, IsAliveResponse, ReadFileRequest,
15 ReadFileResponse, WriteFileRequest, WriteFileResponse,
16};
17use crate::session::DockerSession;
18
19pub struct LocalRuntime {
23 container_id: String,
24 sessions: HashMap<String, DockerSession>,
25 closed: bool,
26}
27
28impl LocalRuntime {
29 pub fn new(container_id: impl Into<String>) -> Self {
31 Self {
32 container_id: container_id.into(),
33 sessions: HashMap::new(),
34 closed: false,
35 }
36 }
37
38 pub fn is_alive(&self) -> IsAliveResponse {
40 if self.closed {
41 return IsAliveResponse {
42 status: "error".into(),
43 message: "Runtime is closed".into(),
44 };
45 }
46 IsAliveResponse::default()
47 }
48
49 pub async fn create_session(
51 &mut self,
52 request: &CreateSessionRequest,
53 ) -> Result<CreateSessionResponse> {
54 if self.sessions.contains_key(&request.session) {
55 return Err(DockerError::SessionExists(request.session.clone()));
56 }
57
58 let session = DockerSession::new(&self.container_id, &request.session);
59
60 self.sessions.insert(request.session.clone(), session);
61 info!("Created session '{}'", request.session);
62
63 Ok(CreateSessionResponse {
64 success: true,
65 session: request.session.clone(),
66 message: String::new(),
67 })
68 }
69
70 pub async fn run_in_session(&mut self, action: &BashAction) -> Result<BashObservation> {
72 if !self.sessions.contains_key(&action.session) {
73 if action.session == "default" {
74 self.create_session(&CreateSessionRequest::default())
75 .await?;
76 } else {
77 return Err(DockerError::SessionNotFound(action.session.clone()));
78 }
79 }
80
81 let session = self.sessions.get(&action.session).unwrap();
82 session
83 .exec_command(&action.command, action.timeout, action.check)
84 .await
85 }
86
87 pub async fn close_session(&mut self, request: &CloseSessionRequest) -> CloseSessionResponse {
89 if let Some(_session) = self.sessions.remove(&request.session) {
90 info!("Closed session '{}'", request.session);
91 CloseSessionResponse {
92 success: true,
93 message: String::new(),
94 }
95 } else {
96 CloseSessionResponse {
97 success: false,
98 message: format!("Session '{}' not found", request.session),
99 }
100 }
101 }
102
103 pub async fn read_file(&self, request: &ReadFileRequest) -> Result<ReadFileResponse> {
105 let session = self.get_or_default_session()?;
106 let obs = session
107 .exec_command(&format!("cat '{}'", request.path), 30.0, CheckMode::Silent)
108 .await?;
109
110 if obs.exit_code == Some(0) || obs.exit_code.is_none() {
111 Ok(ReadFileResponse {
112 success: true,
113 content: obs.output,
114 error: None,
115 })
116 } else {
117 Ok(ReadFileResponse {
118 success: false,
119 content: String::new(),
120 error: Some(obs.output),
121 })
122 }
123 }
124
125 pub async fn write_file(&self, request: &WriteFileRequest) -> Result<WriteFileResponse> {
127 let session = self.get_or_default_session()?;
128 let parent = Path::new(&request.path)
130 .parent()
131 .map(|p| p.to_string_lossy().to_string())
132 .unwrap_or_else(|| ".".into());
133
134 let escaped_content = request.content.replace('\'', "'\\''");
135 let cmd = format!(
136 "mkdir -p '{}' && printf '%s' '{}' > '{}'",
137 parent, escaped_content, request.path
138 );
139
140 let obs = session.exec_command(&cmd, 30.0, CheckMode::Silent).await?;
141
142 if obs.exit_code == Some(0) || obs.exit_code.is_none() {
143 Ok(WriteFileResponse {
144 success: true,
145 error: None,
146 })
147 } else {
148 Ok(WriteFileResponse {
149 success: false,
150 error: Some(obs.output),
151 })
152 }
153 }
154
155 pub async fn close(&mut self) {
157 let names: Vec<String> = self.sessions.keys().cloned().collect();
158 for name in names {
159 self.close_session(&CloseSessionRequest { session: name })
160 .await;
161 }
162 self.closed = true;
163 info!("Runtime closed");
164 }
165
166 pub fn container_id(&self) -> &str {
168 &self.container_id
169 }
170
171 fn get_or_default_session(&self) -> Result<&DockerSession> {
173 self.sessions
174 .get("default")
175 .or_else(|| self.sessions.values().next())
176 .ok_or_else(|| DockerError::SessionNotFound("default".into()))
177 }
178}
179
180#[cfg(test)]
181#[path = "local_runtime_tests.rs"]
182mod tests;