Skip to content

Commit ffceb56

Browse files
committed
Add output property to import extensions
1 parent c73a1ff commit ffceb56

File tree

13 files changed

+102
-69
lines changed

13 files changed

+102
-69
lines changed

dsc/src/subcommand.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format
607607
let capability_types = [
608608
(ExtensionCapability::Discover, "d"),
609609
(ExtensionCapability::Secret, "s"),
610+
(ExtensionCapability::Import, "i"),
610611
];
611612
let mut capabilities = "-".repeat(capability_types.len());
612613

extensions/bicep/bicepparams.dsc.extension.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"fileArg": ""
1414
},
1515
"--stdout"
16-
]
16+
],
17+
"output": "[json(json(stdout()).parametersJson)]"
1718
}
1819
}

lib/dsc-lib/locales/en-us.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,8 @@ outputTypeNotMatch = "Output '%{name}' type does not match expected type '%{expe
8686
copyNotSupported = "Copy for output '%{name}' is currently not supported"
8787

8888
[configure.parameters]
89-
importingParametersFromJson = "Importing parameters from `parameters_input` JSON"
9089
importingParametersFromComplexInput = "Importing parameters from complex input"
9190
importingParametersFromInput = "Importing parameters from simple input"
92-
invalidParamsJsonFormat = "Invalid parameters JSON format: %{error}"
9391
invalidParamsFormat = "Invalid parameters format: %{error}"
9492

9593
[discovery.commandDiscovery]
@@ -231,6 +229,7 @@ importingFile = "Importing file '%{file}' with extension '%{extension}'"
231229
importNotSupported = "Import is not supported by extension '%{extension}' for file '%{file}'"
232230
importNoResults = "Extension '%{extension}' returned no results for import"
233231
secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines which is not supported for secrets"
232+
importProcessingOutput = "Processing output from extension '%{extension}'"
234233

235234
[extensions.extension_manifest]
236235
extensionManifestSchemaTitle = "Extension manifest schema URI"
@@ -545,6 +544,10 @@ invoked = "skip function"
545544
invalidNumberToSkip = "Second argument must be an integer"
546545
invalidOriginalValue = "First argument must be an array or string"
547546

547+
[functions.stdout]
548+
description = "Returns the standard output from the last executed resource. Can only be used in output definitions."
549+
noStdoutAvailable = "No standard output is available from the last executed resource"
550+
548551
[functions.string]
549552
description = "Converts a value to a string"
550553

@@ -664,6 +667,10 @@ indexOutOfBounds = "Index is out of bounds"
664667
indexOnNonArray = "Index access on non-array value"
665668
invalidIndexType = "Invalid index type"
666669
propertyNameNotString = "Property name is not a string"
670+
accessorResult = "Accessor result: %{result}"
671+
evaluatingMemberAccessor = "Evaluating member accessor: %{name}"
672+
evaluatingIndexAccessor = "Evaluating index accessor: %{index}"
673+
evaluatingIndexExpression = "Evaluating index expression: %{expression}"
667674

668675
[parser.functions]
669676
foundErrorNode = "Found error node parsing function"

lib/dsc-lib/src/configure/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct Context {
3434
pub restart_required: Option<Vec<RestartRequired>>,
3535
pub security_context: SecurityContextKind,
3636
pub start_datetime: DateTime<Local>,
37+
pub stdout: Option<String>,
3738
pub system_root: PathBuf,
3839
pub user_functions: HashMap<String, UserFunctionDefinition>,
3940
pub variables: Map<String, Value>,
@@ -60,6 +61,7 @@ impl Context {
6061
SecurityContext::User => SecurityContextKind::Restricted,
6162
},
6263
start_datetime: chrono::Local::now(),
64+
stdout: None,
6365
system_root: get_default_os_system_root(),
6466
user_functions: HashMap::new(),
6567
variables: Map::new(),

lib/dsc-lib/src/configure/parameters.rs

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{collections::HashMap, fmt::Display};
1010
use tracing::trace;
1111

1212
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
13-
pub struct Input {
13+
pub struct SimpleInput {
1414
pub parameters: HashMap<String, Value>,
1515
}
1616

@@ -24,12 +24,6 @@ pub struct InputObject {
2424
pub value: Value,
2525
}
2626

27-
#[derive(Debug, Clone, PartialEq, Deserialize)]
28-
pub struct ParametersJson {
29-
#[serde(rename = "parametersJson")]
30-
pub parameters_json: String,
31-
}
32-
3327
pub const SECURE_VALUE_REDACTED: &str = "<secureValue>";
3428

3529
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
@@ -85,46 +79,27 @@ pub enum SecureKind {
8579
}
8680

8781
pub fn import_parameters(parameters: &Value) -> Result<HashMap<String, Value>, DscError> {
88-
let input = match serde_json::from_value::<ParametersJson>(parameters.clone()) {
89-
Ok(input) => {
90-
trace!("{}", t!("configure.parameters.importingParametersFromJson"));
91-
let param_map = match serde_json::from_str::<ComplexInput>(&input.parameters_json) {
92-
Ok(param_map) => param_map,
93-
Err(e) => {
94-
return Err(DscError::Parser(t!("configure.parameters.invalidParamsJsonFormat", error = e).to_string()));
95-
}
96-
};
82+
let parameters = match serde_json::from_value::<ComplexInput>(parameters.clone()) {
83+
Ok(complex_input) => {
84+
trace!("{}", t!("configure.parameters.importingParametersFromComplexInput"));
9785
let mut result: HashMap<String, Value> = HashMap::new();
98-
for (name, input_object) in param_map.parameters {
86+
for (name, input_object) in complex_input.parameters {
9987
result.insert(name, input_object.value);
10088
}
10189
result
10290
},
10391
Err(_) => {
104-
let complex_input = match serde_json::from_value::<ComplexInput>(parameters.clone()) {
105-
Ok(complex_input) => {
106-
trace!("{}", t!("configure.parameters.importingParametersFromComplexInput"));
107-
let mut result: HashMap<String, Value> = HashMap::new();
108-
for (name, input_object) in complex_input.parameters {
109-
result.insert(name, input_object.value);
110-
}
111-
result
112-
},
113-
Err(_) => {
114-
let simple_input = match serde_json::from_value::<Input>(parameters.clone()) {
115-
Ok(simple_input) => {
116-
trace!("{}", t!("configure.parameters.importingParametersFromInput"));
117-
simple_input.parameters
118-
}
119-
Err(e) => {
120-
return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string()));
121-
}
122-
};
123-
simple_input
92+
let simple_input = match serde_json::from_value::<SimpleInput>(parameters.clone()) {
93+
Ok(simple_input) => {
94+
trace!("{}", t!("configure.parameters.importingParametersFromInput"));
95+
simple_input.parameters
96+
}
97+
Err(e) => {
98+
return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string()));
12499
}
125100
};
126-
complex_input
127-
},
101+
simple_input
102+
}
128103
};
129-
Ok(input)
104+
Ok(parameters)
130105
}

lib/dsc-lib/src/discovery/command_discovery.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,14 +803,14 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
803803
verify_executable(&manifest.r#type, "secret", &secret.executable, path.parent().unwrap());
804804
capabilities.push(dscextension::Capability::Secret);
805805
}
806-
let import_extensions = if let Some(import) = &manifest.import {
806+
let import = if let Some(import) = &manifest.import {
807807
verify_executable(&manifest.r#type, "import", &import.executable, path.parent().unwrap());
808808
capabilities.push(dscextension::Capability::Import);
809809
if import.file_extensions.is_empty() {
810810
warn!("{}", t!("discovery.commandDiscovery.importExtensionsEmpty", extension = manifest.r#type));
811811
None
812812
} else {
813-
Some(import.file_extensions.clone())
813+
Some(import.clone())
814814
}
815815
} else {
816816
None
@@ -821,7 +821,7 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result<
821821
description: manifest.description.clone(),
822822
version: manifest.version.clone(),
823823
capabilities,
824-
import_file_extensions: import_extensions,
824+
import,
825825
path: path.to_path_buf(),
826826
directory: path.parent().unwrap().to_path_buf(),
827827
manifest: serde_json::to_value(manifest)?,

lib/dsc-lib/src/extensions/dscextension.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use crate::extensions::import::ImportMethod;
45
use dsc_lib_jsonschema::transforms::idiomaticize_string_enum;
56
use serde::{Deserialize, Serialize};
67
use serde_json::Value;
@@ -18,9 +19,8 @@ pub struct DscExtension {
1819
pub version: String,
1920
/// The capabilities of the resource.
2021
pub capabilities: Vec<Capability>,
21-
/// The extensions supported for importing.
22-
#[serde(rename = "importFileExtensions")]
23-
pub import_file_extensions: Option<Vec<String>>,
22+
/// The import specifics.
23+
pub import: Option<ImportMethod>,
2424
/// The file path to the resource.
2525
pub path: PathBuf,
2626
/// The description of the resource.
@@ -43,8 +43,6 @@ pub enum Capability {
4343
Secret,
4444
/// The extension imports configuration from a different format.
4545
Import,
46-
/// The extension imports parameters from a different format.
47-
ImportParameters,
4846
}
4947

5048
impl Display for Capability {
@@ -53,7 +51,6 @@ impl Display for Capability {
5351
Capability::Discover => write!(f, "Discover"),
5452
Capability::Secret => write!(f, "Secret"),
5553
Capability::Import => write!(f, "Import"),
56-
Capability::ImportParameters => write!(f, "ImportParams"),
5754
}
5855
}
5956
}
@@ -65,7 +62,7 @@ impl DscExtension {
6562
type_name: String::new(),
6663
version: String::new(),
6764
capabilities: Vec::new(),
68-
import_file_extensions: None,
65+
import: None,
6966
description: None,
7067
path: PathBuf::new(),
7168
directory: PathBuf::new(),

lib/dsc-lib/src/extensions/import.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,13 @@
22
// Licensed under the MIT License.
33

44
use crate::{
5-
dscerror::DscError,
6-
dscresources::{
7-
command_resource::{
8-
invoke_command,
9-
},
10-
},
11-
extensions::{
5+
configure::context::Context, dscerror::DscError, dscresources::command_resource::invoke_command, extensions::{
126
dscextension::{
137
Capability,
148
DscExtension,
159
},
1610
extension_manifest::ExtensionManifest,
17-
},
11+
}, parser::Statement
1812
};
1913
use path_absolutize::Absolutize;
2014
use rust_i18n::t;
@@ -32,6 +26,8 @@ pub struct ImportMethod {
3226
pub executable: String,
3327
/// The arguments to pass to the command to perform an Import.
3428
pub args: Option<Vec<ImportArgKind>>,
29+
/// Enables modifying the resulting output from STDOUT after running the import command.
30+
pub output: Option<String>,
3531
}
3632

3733
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
@@ -62,9 +58,9 @@ impl DscExtension {
6258
///
6359
/// This function will return an error if the import fails or if the extension does not support the import capability.
6460
pub fn import(&self, file: &Path) -> Result<String, DscError> {
65-
if self.capabilities.contains(&Capability::Import) {
61+
if let Some(import) = &self.import {
6662
let file_extension = file.extension().and_then(|s| s.to_str()).unwrap_or_default().to_string();
67-
if self.import_file_extensions.as_ref().is_some_and(|exts| exts.contains(&file_extension)) {
63+
if import.file_extensions.contains(&file_extension) {
6864
debug!("{}", t!("extensions.dscextension.importingFile", file = file.display(), extension = self.type_name));
6965
} else {
7066
return Err(DscError::NotSupported(
@@ -93,6 +89,16 @@ impl DscExtension {
9389
if stdout.is_empty() {
9490
info!("{}", t!("extensions.dscextension.importNoResults", extension = self.type_name));
9591
} else {
92+
debug!("got stdout: {}", stdout);
93+
if let Some(output) = &import.output {
94+
debug!("processing output: {}", output);
95+
debug!("{}", t!("extensions.dscextension.importProcessingOutput", extension = self.type_name));
96+
let mut parser = Statement::new()?;
97+
let mut context = Context::new();
98+
context.stdout = Some(stdout);
99+
let processed_output = parser.parse_and_execute(output, &context)?;
100+
return Ok(processed_output.to_string());
101+
}
96102
return Ok(stdout);
97103
}
98104
}

lib/dsc-lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub mod resource_id;
6666
pub mod secret;
6767
pub mod skip;
6868
pub mod starts_with;
69+
pub mod stdout;
6970
pub mod string;
7071
pub mod take;
7172
pub mod sub;
@@ -202,6 +203,7 @@ impl FunctionDispatcher {
202203
Box::new(secret::Secret{}),
203204
Box::new(skip::Skip{}),
204205
Box::new(starts_with::StartsWith{}),
206+
Box::new(stdout::Stdout{}),
205207
Box::new(string::StringFn{}),
206208
Box::new(sub::Sub{}),
207209
Box::new(take::Take{}),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use super::Function;
10+
11+
#[derive(Debug, Default)]
12+
pub struct Stdout {}
13+
14+
impl Function for Stdout {
15+
fn get_metadata(&self) -> FunctionMetadata {
16+
FunctionMetadata {
17+
name: "stdout".to_string(),
18+
description: t!("functions.stdout.description").to_string(),
19+
category: vec![FunctionCategory::System],
20+
min_args: 0,
21+
max_args: 0,
22+
accepted_arg_ordered_types: vec![],
23+
remaining_arg_accepted_types: None,
24+
return_types: vec![FunctionArgKind::String],
25+
}
26+
}
27+
28+
fn invoke(&self, _args: &[Value], context: &Context) -> Result<Value, DscError> {
29+
if let Some(stdout) = &context.stdout {
30+
let result = stdout.to_string();
31+
return Ok(Value::String(result));
32+
}
33+
Err(DscError::Parser(t!("functions.stdout.noStdoutAvailable").to_string(),
34+
))
35+
}
36+
}

0 commit comments

Comments
 (0)