Skip to content

Commit 0a65344

Browse files
authored
Merge pull request #1056 from SteveL-MSFT/extension-refactor
Refactor extensions code
2 parents 73c95ca + d44420f commit 0a65344

File tree

4 files changed

+297
-255
lines changed

4 files changed

+297
-255
lines changed

dsc_lib/src/extensions/discover.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::dscresources::resource_manifest::ArgKind;
4+
use crate::{
5+
discovery::command_discovery::{
6+
load_manifest, ImportedManifest
7+
},
8+
dscerror::DscError,
9+
dscresources::{
10+
command_resource::{
11+
invoke_command, process_args
12+
},
13+
dscresource::DscResource,
14+
resource_manifest::ArgKind,
15+
},
16+
extensions::{
17+
dscextension::{
18+
Capability,
19+
DscExtension,
20+
},
21+
extension_manifest::ExtensionManifest,
22+
},
23+
};
24+
use rust_i18n::t;
525
use schemars::JsonSchema;
626
use serde::{Deserialize, Serialize};
27+
use std::path::Path;
28+
use tracing::{info, trace};
729

830
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
931
pub struct DiscoverMethod {
@@ -19,3 +41,67 @@ pub struct DiscoverResult {
1941
#[serde(rename = "manifestPath")]
2042
pub manifest_path: String,
2143
}
44+
45+
impl DscExtension {
46+
/// Perform discovery of resources using the extension.
47+
///
48+
/// # Returns
49+
///
50+
/// A result containing a vector of discovered resources or an error.
51+
///
52+
/// # Errors
53+
///
54+
/// This function will return an error if the discovery fails.
55+
pub fn discover(&self) -> Result<Vec<DscResource>, DscError> {
56+
let mut resources: Vec<DscResource> = Vec::new();
57+
58+
if self.capabilities.contains(&Capability::Discover) {
59+
let extension = match serde_json::from_value::<ExtensionManifest>(self.manifest.clone()) {
60+
Ok(manifest) => manifest,
61+
Err(err) => {
62+
return Err(DscError::Manifest(self.type_name.clone(), err));
63+
}
64+
};
65+
let Some(discover) = extension.discover else {
66+
return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Discover.to_string()));
67+
};
68+
let args = process_args(discover.args.as_ref(), "");
69+
let (_exit_code, stdout, _stderr) = invoke_command(
70+
&discover.executable,
71+
args,
72+
None,
73+
Some(self.directory.as_str()),
74+
None,
75+
extension.exit_codes.as_ref(),
76+
)?;
77+
if stdout.is_empty() {
78+
info!("{}", t!("extensions.dscextension.discoverNoResults", extension = self.type_name));
79+
} else {
80+
for line in stdout.lines() {
81+
trace!("{}", t!("extensions.dscextension.extensionReturned", extension = self.type_name, line = line));
82+
let discover_result: DiscoverResult = match serde_json::from_str(line) {
83+
Ok(result) => result,
84+
Err(err) => {
85+
return Err(DscError::Json(err));
86+
}
87+
};
88+
if !Path::new(&discover_result.manifest_path).is_absolute() {
89+
return Err(DscError::Extension(t!("extensions.dscextension.discoverNotAbsolutePath", extension = self.type_name.clone(), path = discover_result.manifest_path.clone()).to_string()));
90+
}
91+
let manifest_path = Path::new(&discover_result.manifest_path);
92+
// Currently we don't support extensions discovering other extensions
93+
if let ImportedManifest::Resource(resource) = load_manifest(manifest_path)? {
94+
resources.push(resource);
95+
}
96+
}
97+
}
98+
99+
Ok(resources)
100+
} else {
101+
Err(DscError::UnsupportedCapability(
102+
self.type_name.clone(),
103+
Capability::Discover.to_string()
104+
))
105+
}
106+
}
107+
}
Lines changed: 1 addition & 254 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use rust_i18n::t;
5-
use path_absolutize::Absolutize;
64
use serde::{Deserialize, Serialize};
75
use serde_json::Value;
86
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;
288

299
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
3010
#[serde(deny_unknown_fields)]
@@ -87,243 +67,10 @@ impl DscExtension {
8767
manifest: Value::Null,
8868
}
8969
}
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-
}
26570
}
26671

26772
impl Default for DscExtension {
26873
fn default() -> Self {
26974
DscExtension::new()
27075
}
27176
}
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

Comments
 (0)