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