1#![allow(clippy::expect_fun_call)]
2mod docker;
3mod kubernetes;
4mod native;
5pub mod shared;
6
7use std::{
8 collections::HashMap,
9 net::IpAddr,
10 path::{Path, PathBuf},
11 sync::Arc,
12 time::Duration,
13};
14
15use async_trait::async_trait;
16use configuration::types::AssetLocation;
17use shared::{
18 constants::LOCALHOST,
19 types::{
20 ExecutionResult, GenerateFilesOptions, ProviderCapabilities, RunCommandOptions,
21 RunScriptOptions, SpawnNodeOptions,
22 },
23};
24use support::fs::FileSystemError;
25
26#[derive(Debug, thiserror::Error)]
27#[allow(missing_docs)]
28pub enum ProviderError {
29 #[error("Failed to create client '{0}': {1}")]
30 CreateClientFailed(String, anyhow::Error),
31
32 #[error("Failed to create namespace '{0}': {1}")]
33 CreateNamespaceFailed(String, anyhow::Error),
34
35 #[error("Failed to spawn node '{0}': {1}")]
36 NodeSpawningFailed(String, anyhow::Error),
37
38 #[error("Error running command '{0}' {1}: {2}")]
39 RunCommandError(String, String, anyhow::Error),
40
41 #[error("Error running script'{0}': {1}")]
42 RunScriptError(String, anyhow::Error),
43
44 #[error("Invalid network configuration field {0}")]
45 InvalidConfig(String),
46
47 #[error("Failed to retrieve node available args using image {0} and command {1}: {2}")]
48 NodeAvailableArgsError(String, String, String),
49
50 #[error("Can not recover node: {0}")]
51 MissingNode(String),
52
53 #[error("Can not recover node: {0} info, field: {1}")]
54 MissingNodeInfo(String, String),
55
56 #[error("File generation failed: {0}")]
57 FileGenerationFailed(anyhow::Error),
58
59 #[error(transparent)]
60 FileSystemError(#[from] FileSystemError),
61
62 #[error("Invalid script path for {0}")]
63 InvalidScriptPath(anyhow::Error),
64
65 #[error("Script with path {0} not found")]
66 ScriptNotFound(PathBuf),
67
68 #[error("Failed to retrieve process ID for node '{0}'")]
69 ProcessIdRetrievalFailed(String),
70
71 #[error("Failed to pause node '{0}': {1}")]
72 PauseNodeFailed(String, anyhow::Error),
73
74 #[error("Failed to resume node '{0}': {1}")]
75 ResumeNodeFailed(String, anyhow::Error),
76
77 #[error("Failed to kill node '{0}': {1}")]
78 KillNodeFailed(String, anyhow::Error),
79
80 #[error("Failed to restart node '{0}': {1}")]
81 RestartNodeFailed(String, anyhow::Error),
82
83 #[error("Failed to destroy node '{0}': {1}")]
84 DestroyNodeFailed(String, anyhow::Error),
85
86 #[error("Failed to get logs for node '{0}': {1}")]
87 GetLogsFailed(String, anyhow::Error),
88
89 #[error("Failed to dump logs for node '{0}': {1}")]
90 DumpLogsFailed(String, anyhow::Error),
91
92 #[error("Failed to copy file from node '{0}': {1}")]
93 CopyFileFromNodeError(String, anyhow::Error),
94
95 #[error("Failed to setup fileserver: {0}")]
96 FileServerSetupError(anyhow::Error),
97
98 #[error("Error uploading file: '{0}': {1}")]
99 UploadFile(String, anyhow::Error),
100
101 #[error("Error downloading file: '{0}': {1}")]
102 DownloadFile(String, anyhow::Error),
103
104 #[error("Error sending file '{0}' to {1}: {2}")]
105 SendFile(String, String, anyhow::Error),
106
107 #[error("Error creating port-forward '{0}:{1}': {2}")]
108 PortForwardError(u16, u16, anyhow::Error),
109
110 #[error("Failed to delete namespace '{0}': {1}")]
111 DeleteNamespaceFailed(String, anyhow::Error),
112
113 #[error("Serialization error: {0}")]
114 SerializationError(#[from] serde_json::Error),
115
116 #[error("Failed to acquire lock: {0}")]
117 FailedToAcquireLock(String),
118}
119
120#[async_trait]
121pub trait Provider {
122 fn name(&self) -> &str;
123
124 fn capabilities(&self) -> &ProviderCapabilities;
125
126 async fn namespaces(&self) -> HashMap<String, DynNamespace>;
127
128 async fn create_namespace(&self) -> Result<DynNamespace, ProviderError>;
129
130 async fn create_namespace_with_base_dir(
131 &self,
132 base_dir: &Path,
133 ) -> Result<DynNamespace, ProviderError>;
134
135 async fn create_namespace_from_json(
136 &self,
137 json_value: &serde_json::Value,
138 ) -> Result<DynNamespace, ProviderError>;
139}
140
141pub type DynProvider = Arc<dyn Provider + Send + Sync>;
142
143#[async_trait]
144pub trait ProviderNamespace {
145 fn name(&self) -> &str;
146
147 fn base_dir(&self) -> &PathBuf;
148
149 fn capabilities(&self) -> &ProviderCapabilities;
150
151 fn provider_name(&self) -> &str;
152
153 async fn detach(&self) {
154 warn!("Detach is not implemented for {}", self.name());
156 }
157
158 async fn is_detached(&self) -> bool {
159 false
161 }
162
163 async fn nodes(&self) -> HashMap<String, DynNode>;
164
165 async fn get_node_available_args(
166 &self,
167 options: (String, Option<String>),
168 ) -> Result<String, ProviderError>;
169
170 async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError>;
171
172 async fn spawn_node_from_json(
173 &self,
174 json_value: &serde_json::Value,
175 ) -> Result<DynNode, ProviderError>;
176
177 async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError>;
178
179 async fn destroy(&self) -> Result<(), ProviderError>;
180
181 async fn static_setup(&self) -> Result<(), ProviderError>;
182}
183
184pub type DynNamespace = Arc<dyn ProviderNamespace + Send + Sync>;
185
186#[async_trait]
187pub trait ProviderNode: erased_serde::Serialize {
188 fn name(&self) -> &str;
189
190 fn args(&self) -> Vec<&str>;
191
192 fn base_dir(&self) -> &PathBuf;
193
194 fn config_dir(&self) -> &PathBuf;
195
196 fn data_dir(&self) -> &PathBuf;
197
198 fn relay_data_dir(&self) -> &PathBuf;
199
200 fn scripts_dir(&self) -> &PathBuf;
201
202 fn log_path(&self) -> &PathBuf;
203
204 fn log_cmd(&self) -> String;
205
206 fn path_in_node(&self, file: &Path) -> PathBuf;
209
210 async fn logs(&self) -> Result<String, ProviderError>;
211
212 async fn dump_logs(&self, local_dest: PathBuf) -> Result<(), ProviderError>;
213
214 async fn ip(&self) -> Result<IpAddr, ProviderError> {
216 Ok(LOCALHOST)
217 }
218
219 async fn create_port_forward(
221 &self,
222 _local_port: u16,
223 _remote_port: u16,
224 ) -> Result<Option<u16>, ProviderError> {
225 Ok(None)
226 }
227
228 async fn run_command(
229 &self,
230 options: RunCommandOptions,
231 ) -> Result<ExecutionResult, ProviderError>;
232
233 async fn run_script(&self, options: RunScriptOptions)
234 -> Result<ExecutionResult, ProviderError>;
235
236 async fn send_file(
237 &self,
238 local_file_path: &Path,
239 remote_file_path: &Path,
240 mode: &str,
241 ) -> Result<(), ProviderError>;
242
243 async fn receive_file(
244 &self,
245 remote_file_path: &Path,
246 local_file_path: &Path,
247 ) -> Result<(), ProviderError>;
248
249 async fn pause(&self) -> Result<(), ProviderError>;
250
251 async fn resume(&self) -> Result<(), ProviderError>;
252
253 async fn restart(&self, after: Option<Duration>) -> Result<(), ProviderError>;
254
255 async fn restart_with(
256 &self,
257 assets: &[AssetLocation],
258 cmd: &str,
259 args: &[String],
260 after: Option<Duration>,
261 ) -> Result<(), ProviderError>;
262
263 async fn destroy(&self) -> Result<(), ProviderError>;
264}
265
266pub type DynNode = Arc<dyn ProviderNode + Send + Sync>;
267
268pub use docker::*;
270pub use kubernetes::*;
271pub use native::*;
272pub use shared::{constants, types};
273use tracing::warn;