|
1 | 1 | // Copyright (c) Microsoft Corporation. |
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | | -use rust_i18n::t; |
5 | | -use path_absolutize::Absolutize; |
6 | 4 | use serde::{Deserialize, Serialize}; |
7 | 5 | use serde_json::Value; |
8 | 6 | use schemars::JsonSchema; |
9 | | -use std::{fmt::Display, path::Path}; |
10 | | -use tracing::{debug, info, trace}; |
11 | | - |
12 | | -use crate::{ |
13 | | - discovery::command_discovery::{ |
14 | | - load_manifest, ImportedManifest |
15 | | - }, |
16 | | - dscerror::DscError, |
17 | | - dscresources::{ |
18 | | - command_resource::{ |
19 | | - invoke_command, |
20 | | - process_args |
21 | | - }, |
22 | | - dscresource::DscResource |
23 | | - }, |
24 | | - extensions::import::ImportArgKind |
25 | | -}; |
26 | | - |
27 | | -use super::{discover::DiscoverResult, extension_manifest::ExtensionManifest, secret::SecretArgKind}; |
| 7 | +use std::fmt::Display; |
28 | 8 |
|
29 | 9 | #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] |
30 | 10 | #[serde(deny_unknown_fields)] |
@@ -87,243 +67,10 @@ impl DscExtension { |
87 | 67 | manifest: Value::Null, |
88 | 68 | } |
89 | 69 | } |
90 | | - |
91 | | - /// Perform discovery of resources using the extension. |
92 | | - /// |
93 | | - /// # Returns |
94 | | - /// |
95 | | - /// A result containing a vector of discovered resources or an error. |
96 | | - /// |
97 | | - /// # Errors |
98 | | - /// |
99 | | - /// This function will return an error if the discovery fails. |
100 | | - pub fn discover(&self) -> Result<Vec<DscResource>, DscError> { |
101 | | - let mut resources: Vec<DscResource> = Vec::new(); |
102 | | - |
103 | | - if self.capabilities.contains(&Capability::Discover) { |
104 | | - let extension = match serde_json::from_value::<ExtensionManifest>(self.manifest.clone()) { |
105 | | - Ok(manifest) => manifest, |
106 | | - Err(err) => { |
107 | | - return Err(DscError::Manifest(self.type_name.clone(), err)); |
108 | | - } |
109 | | - }; |
110 | | - let Some(discover) = extension.discover else { |
111 | | - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Discover.to_string())); |
112 | | - }; |
113 | | - let args = process_args(discover.args.as_ref(), ""); |
114 | | - let (_exit_code, stdout, _stderr) = invoke_command( |
115 | | - &discover.executable, |
116 | | - args, |
117 | | - None, |
118 | | - Some(self.directory.as_str()), |
119 | | - None, |
120 | | - extension.exit_codes.as_ref(), |
121 | | - )?; |
122 | | - if stdout.is_empty() { |
123 | | - info!("{}", t!("extensions.dscextension.discoverNoResults", extension = self.type_name)); |
124 | | - } else { |
125 | | - for line in stdout.lines() { |
126 | | - trace!("{}", t!("extensions.dscextension.extensionReturned", extension = self.type_name, line = line)); |
127 | | - let discover_result: DiscoverResult = match serde_json::from_str(line) { |
128 | | - Ok(result) => result, |
129 | | - Err(err) => { |
130 | | - return Err(DscError::Json(err)); |
131 | | - } |
132 | | - }; |
133 | | - if !Path::new(&discover_result.manifest_path).is_absolute() { |
134 | | - return Err(DscError::Extension(t!("extensions.dscextension.discoverNotAbsolutePath", extension = self.type_name.clone(), path = discover_result.manifest_path.clone()).to_string())); |
135 | | - } |
136 | | - let manifest_path = Path::new(&discover_result.manifest_path); |
137 | | - // Currently we don't support extensions discovering other extensions |
138 | | - if let ImportedManifest::Resource(resource) = load_manifest(manifest_path)? { |
139 | | - resources.push(resource); |
140 | | - } |
141 | | - } |
142 | | - } |
143 | | - |
144 | | - Ok(resources) |
145 | | - } else { |
146 | | - Err(DscError::UnsupportedCapability( |
147 | | - self.type_name.clone(), |
148 | | - Capability::Discover.to_string() |
149 | | - )) |
150 | | - } |
151 | | - } |
152 | | - |
153 | | - /// Import a file based on the extension. |
154 | | - /// |
155 | | - /// # Arguments |
156 | | - /// |
157 | | - /// * `file` - The file to import. |
158 | | - /// |
159 | | - /// # Returns |
160 | | - /// |
161 | | - /// A result containing the imported file content or an error. |
162 | | - /// |
163 | | - /// # Errors |
164 | | - /// |
165 | | - /// This function will return an error if the import fails or if the extension does not support the import capability. |
166 | | - pub fn import(&self, file: &str) -> Result<String, DscError> { |
167 | | - if self.capabilities.contains(&Capability::Import) { |
168 | | - let file_path = Path::new(file); |
169 | | - let file_extension = file_path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_string(); |
170 | | - if self.import_file_extensions.as_ref().is_some_and(|exts| exts.contains(&file_extension)) { |
171 | | - debug!("{}", t!("extensions.dscextension.importingFile", file = file, extension = self.type_name)); |
172 | | - } else { |
173 | | - debug!("{}", t!("extensions.dscextension.importNotSupported", file = file, extension = self.type_name)); |
174 | | - return Err(DscError::NotSupported( |
175 | | - t!("extensions.dscextension.importNotSupported", file = file, extension = self.type_name).to_string(), |
176 | | - )); |
177 | | - } |
178 | | - |
179 | | - let extension = match serde_json::from_value::<ExtensionManifest>(self.manifest.clone()) { |
180 | | - Ok(manifest) => manifest, |
181 | | - Err(err) => { |
182 | | - return Err(DscError::Manifest(self.type_name.clone(), err)); |
183 | | - } |
184 | | - }; |
185 | | - let Some(import) = extension.import else { |
186 | | - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Import.to_string())); |
187 | | - }; |
188 | | - let args = process_import_args(import.args.as_ref(), file)?; |
189 | | - let (_exit_code, stdout, _stderr) = invoke_command( |
190 | | - &import.executable, |
191 | | - args, |
192 | | - None, |
193 | | - Some(self.directory.as_str()), |
194 | | - None, |
195 | | - extension.exit_codes.as_ref(), |
196 | | - )?; |
197 | | - if stdout.is_empty() { |
198 | | - info!("{}", t!("extensions.dscextension.importNoResults", extension = self.type_name)); |
199 | | - } else { |
200 | | - return Ok(stdout); |
201 | | - } |
202 | | - } |
203 | | - Err(DscError::UnsupportedCapability( |
204 | | - self.type_name.clone(), |
205 | | - Capability::Import.to_string() |
206 | | - )) |
207 | | - } |
208 | | - |
209 | | - /// Retrieve a secret using the extension. |
210 | | - /// |
211 | | - /// # Arguments |
212 | | - /// |
213 | | - /// * `name` - The name of the secret to retrieve. |
214 | | - /// * `vault` - An optional vault name to use for the secret. |
215 | | - /// |
216 | | - /// # Returns |
217 | | - /// |
218 | | - /// A result containing the secret as a string or an error. |
219 | | - /// |
220 | | - /// # Errors |
221 | | - /// |
222 | | - /// This function will return an error if the secret retrieval fails or if the extension does not support the secret capability. |
223 | | - pub fn secret(&self, name: &str, vault: Option<&str>) -> Result<Option<String>, DscError> { |
224 | | - if self.capabilities.contains(&Capability::Secret) { |
225 | | - debug!("{}", t!("extensions.dscextension.retrievingSecretFromExtension", name = name, extension = self.type_name)); |
226 | | - let extension = match serde_json::from_value::<ExtensionManifest>(self.manifest.clone()) { |
227 | | - Ok(manifest) => manifest, |
228 | | - Err(err) => { |
229 | | - return Err(DscError::Manifest(self.type_name.clone(), err)); |
230 | | - } |
231 | | - }; |
232 | | - let Some(secret) = extension.secret else { |
233 | | - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Secret.to_string())); |
234 | | - }; |
235 | | - let args = process_secret_args(secret.args.as_ref(), name, vault); |
236 | | - let (_exit_code, stdout, _stderr) = invoke_command( |
237 | | - &secret.executable, |
238 | | - args, |
239 | | - vault, |
240 | | - Some(self.directory.as_str()), |
241 | | - None, |
242 | | - extension.exit_codes.as_ref(), |
243 | | - )?; |
244 | | - if stdout.is_empty() { |
245 | | - debug!("{}", t!("extensions.dscextension.extensionReturnedNoSecret", extension = self.type_name)); |
246 | | - Ok(None) |
247 | | - } else { |
248 | | - // see if multiple lines were returned |
249 | | - let secret = if stdout.lines().count() > 1 { |
250 | | - return Err(DscError::NotSupported(t!("extensions.dscextension.secretMultipleLinesReturned", extension = self.type_name).to_string())); |
251 | | - } else { |
252 | | - debug!("{}", t!("extensions.dscextension.extensionReturnedSecret", extension = self.type_name)); |
253 | | - // remove any trailing newline characters |
254 | | - stdout.trim_end_matches('\n').to_string() |
255 | | - }; |
256 | | - Ok(Some(secret)) |
257 | | - } |
258 | | - } else { |
259 | | - Err(DscError::UnsupportedCapability( |
260 | | - self.type_name.clone(), |
261 | | - Capability::Secret.to_string() |
262 | | - )) |
263 | | - } |
264 | | - } |
265 | 70 | } |
266 | 71 |
|
267 | 72 | impl Default for DscExtension { |
268 | 73 | fn default() -> Self { |
269 | 74 | DscExtension::new() |
270 | 75 | } |
271 | 76 | } |
272 | | - |
273 | | -fn process_import_args(args: Option<&Vec<ImportArgKind>>, file: &str) -> Result<Option<Vec<String>>, DscError> { |
274 | | - let Some(arg_values) = args else { |
275 | | - debug!("{}", t!("dscresources.commandResource.noArgs")); |
276 | | - return Ok(None); |
277 | | - }; |
278 | | - |
279 | | - // make path absolute |
280 | | - let path = Path::new(file); |
281 | | - let Ok(full_path) = path.absolutize() else { |
282 | | - return Err(DscError::Extension(t!("util.failedToAbsolutizePath", path = path : {:?}).to_string())); |
283 | | - }; |
284 | | - |
285 | | - let mut processed_args = Vec::<String>::new(); |
286 | | - for arg in arg_values { |
287 | | - match arg { |
288 | | - ImportArgKind::String(s) => { |
289 | | - processed_args.push(s.clone()); |
290 | | - }, |
291 | | - ImportArgKind::File { file_arg } => { |
292 | | - if !file_arg.is_empty() { |
293 | | - processed_args.push(file_arg.to_string()); |
294 | | - } |
295 | | - processed_args.push(full_path.to_string_lossy().to_string()); |
296 | | - }, |
297 | | - } |
298 | | - } |
299 | | - |
300 | | - Ok(Some(processed_args)) |
301 | | -} |
302 | | - |
303 | | -fn process_secret_args(args: Option<&Vec<SecretArgKind>>, name: &str, vault: Option<&str>) -> Option<Vec<String>> { |
304 | | - let Some(arg_values) = args else { |
305 | | - debug!("{}", t!("dscresources.commandResource.noArgs")); |
306 | | - return None; |
307 | | - }; |
308 | | - |
309 | | - let mut processed_args = Vec::<String>::new(); |
310 | | - for arg in arg_values { |
311 | | - match arg { |
312 | | - SecretArgKind::String(s) => { |
313 | | - processed_args.push(s.clone()); |
314 | | - }, |
315 | | - SecretArgKind::Name { name_arg } => { |
316 | | - processed_args.push(name_arg.to_string()); |
317 | | - processed_args.push(name.to_string()); |
318 | | - }, |
319 | | - SecretArgKind::Vault { vault_arg } => { |
320 | | - if let Some(value) = vault { |
321 | | - processed_args.push(vault_arg.to_string()); |
322 | | - processed_args.push(value.to_string()); |
323 | | - } |
324 | | - }, |
325 | | - } |
326 | | - } |
327 | | - |
328 | | - Some(processed_args) |
329 | | -} |
0 commit comments