From 2e80cabae437011fe51062220313dc5c33e043e5 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 16:47:04 -0800 Subject: [PATCH 01/11] Add support for ARM params file and Bicep params file formats --- dsc/examples/hello_world.dsc.bicep | 4 +- dsc/examples/hello_world.dsc.bicepparam | 3 + dsc/examples/hello_world.dsc.json | 9 +++ dsc/src/main.rs | 41 ++--------- dsc/tests/dsc_parameters.tests.ps1 | 51 ++++++++++++- extensions/bicep/.project.data.json | 3 +- extensions/bicep/bicep.tests.ps1 | 8 +++ .../bicep/bicepparams.dsc.extension.json | 18 +++++ lib/dsc-lib/locales/en-us.toml | 10 +++ lib/dsc-lib/src/configure/mod.rs | 5 +- lib/dsc-lib/src/configure/parameters.rs | 71 ++++++++++++++++++- lib/dsc-lib/src/extensions/dscextension.rs | 3 + .../src/extensions/extension_manifest.rs | 3 + lib/dsc-lib/src/extensions/import.rs | 3 +- lib/dsc-lib/src/extensions/mod.rs | 2 +- 15 files changed, 190 insertions(+), 44 deletions(-) create mode 100644 dsc/examples/hello_world.dsc.bicepparam create mode 100644 dsc/examples/hello_world.dsc.json create mode 100644 extensions/bicep/bicepparams.dsc.extension.json diff --git a/dsc/examples/hello_world.dsc.bicep b/dsc/examples/hello_world.dsc.bicep index 8198de447..3dd799543 100644 --- a/dsc/examples/hello_world.dsc.bicep +++ b/dsc/examples/hello_world.dsc.bicep @@ -2,11 +2,13 @@ targetScope = 'desiredStateConfiguration' +param text string = 'Hello, world!' + // use workaround where Bicep currently requires version in date format resource echo 'Microsoft.DSC.Debug/Echo@2025-08-27' = { name: 'exampleEcho' properties: { - output: 'Hello, world!' + output: text } } diff --git a/dsc/examples/hello_world.dsc.bicepparam b/dsc/examples/hello_world.dsc.bicepparam new file mode 100644 index 000000000..f0e6583e0 --- /dev/null +++ b/dsc/examples/hello_world.dsc.bicepparam @@ -0,0 +1,3 @@ +using 'hello_world.dsc.bicep' + +param text = 'This is a parameterized hello world!' diff --git a/dsc/examples/hello_world.dsc.json b/dsc/examples/hello_world.dsc.json new file mode 100644 index 000000000..d6cbbda9e --- /dev/null +++ b/dsc/examples/hello_world.dsc.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "text": { + "value": "This is a parameterized hello world!" + } + } +} \ No newline at end of file diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 181694af9..83fbbac95 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -4,14 +4,14 @@ use args::{Args, SubCommand}; use clap::{CommandFactory, Parser}; use clap_complete::generate; +use dsc_lib::progress::ProgressFormat; use mcp::start_mcp_server; use rust_i18n::{i18n, t}; -use std::{io, io::Read, process::exit}; +use std::{io, process::exit}; use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; use tracing::{error, info, warn, debug}; -use dsc_lib::progress::ProgressFormat; -use crate::util::EXIT_INVALID_INPUT; +use crate::util::{EXIT_INVALID_INPUT, get_input}; #[cfg(debug_assertions)] use crossterm::event; @@ -54,38 +54,11 @@ fn main() { generate(shell, &mut cmd, "dsc", &mut io::stdout()); }, SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => { - // Read parameters from file if provided - let file_params = if let Some(file_name) = ¶meters_file { - if file_name == "-" { - info!("{}", t!("main.readingParametersFromStdin")); - let mut stdin = Vec::::new(); - match io::stdin().read_to_end(&mut stdin) { - Ok(_) => { - match String::from_utf8(stdin) { - Ok(input) => Some(input), - Err(err) => { - error!("{}: {err}", t!("util.invalidUtf8")); - exit(EXIT_INVALID_INPUT); - } - } - }, - Err(err) => { - error!("{}: {err}", t!("util.failedToReadStdin")); - exit(EXIT_INVALID_INPUT); - } - } - } else { - info!("{}: {file_name}", t!("main.readingParametersFile")); - match std::fs::read_to_string(file_name) { - Ok(content) => Some(content), - Err(err) => { - error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile")); - exit(util::EXIT_INVALID_INPUT); - } - } - } - } else { + let params = get_input(None, parameters_file.as_ref()); + let file_params = if params.is_empty() { None + } else { + Some(params) }; let merged_parameters = match (file_params, parameters) { diff --git a/dsc/tests/dsc_parameters.tests.ps1 b/dsc/tests/dsc_parameters.tests.ps1 index 839159377..9d18b364f 100644 --- a/dsc/tests/dsc_parameters.tests.ps1 +++ b/dsc/tests/dsc_parameters.tests.ps1 @@ -394,6 +394,53 @@ Describe 'Parameters tests' { $errorMessage | Should -BeLike "*ERROR*Empty input provided*" } + It 'Parameters in ARM syntax are supported' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + parameters: + myString: + type: string + myObject: + type: object + myArray: + type: array + myInt: + type: int + myBool: + type: bool + resources: + - name: echo + type: Microsoft.DSC.Debug/Echo + properties: + output: "[concat(parameters('myString'), '-', parameters('myObject').prop1, '-', parameters('myArray')[0], parameters('myArray')[1], '-', string(parameters('myInt')), '-', string(parameters('myBool'))]" +"@ + $params = @{ + parameters = @{ + myString = @{ + value = 'Hello' + } + myObject = @{ + value = @{ + prop1 = 'World' + } + } + myArray = @{ + value = @('Item1', 'Item2') + } + myInt = @{ + value = 123 + } + myBool = @{ + value = $true + } + } + } | ConvertTo-Json -Compress -Depth 5 + + $out = $config_yaml | dsc -l trace config -p $params get -f - 2> $TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.output | Should -BeExactly 'Hello-World-Item1Item2-123-true' + } + It 'Invalid parameters read from STDIN result in error' { $params = @{ osFamily = 'Windows' @@ -403,7 +450,7 @@ Describe 'Parameters tests' { $LASTEXITCODE | Should -Be 4 $out | Should -BeNullOrEmpty $errorMessage = Get-Content -Path $TestDrive/error.log -Raw - $errorMessage | Should -BeLike "*ERROR*Parameter input failure: JSON: missing field ````parameters````*" + $errorMessage | Should -BeLike "*ERROR*Invalid parameters format: missing field ````parameters````*" } It 'Parameters can reference other parameters in defaultValue: simple nested' { @@ -935,7 +982,7 @@ parameters: } else { $expectedOutput = "{0}-{1}" -f $fileValue, $inlineValue - } + } $out.results[0].result.actualState.output | Should -BeExactly $expectedOutput } diff --git a/extensions/bicep/.project.data.json b/extensions/bicep/.project.data.json index 8c9092129..21137eca0 100644 --- a/extensions/bicep/.project.data.json +++ b/extensions/bicep/.project.data.json @@ -3,7 +3,8 @@ "Kind": "Extension", "CopyFiles": { "All": [ - "bicep.dsc.extension.json" + "bicep.dsc.extension.json", + "bicepparams.dsc.extension.json" ] } } diff --git a/extensions/bicep/bicep.tests.ps1 b/extensions/bicep/bicep.tests.ps1 index 7d2af7785..12be8d1ba 100644 --- a/extensions/bicep/bicep.tests.ps1 +++ b/extensions/bicep/bicep.tests.ps1 @@ -94,4 +94,12 @@ resource invalid 'Microsoft.DSC.Extension/Bicep:1.0' = { $content | Should -Match "Importing file '$bicepFile' with extension 'Microsoft.DSC.Extension/Bicep'" $content | Should -Match "BCP033" } + + It 'Example bicep parameters file should work' { + $bicepFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\hello_world.dsc.bicep" + $bicepParamFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\hello_world.dsc.bicepparam" + $out = dsc -l trace config --parameters-file $bicepParamFile get --file $bicepFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.output | Should -BeExactly 'This is a parameterized hello world!' + } } diff --git a/extensions/bicep/bicepparams.dsc.extension.json b/extensions/bicep/bicepparams.dsc.extension.json new file mode 100644 index 000000000..63a18b396 --- /dev/null +++ b/extensions/bicep/bicepparams.dsc.extension.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/extension/manifest.json", + "type": "Microsoft.DSC.Extension/BicepParameters", + "version": "0.1.0", + "description": "Enable passing Bicep parameters file directly to DSC, but requires bicep executable to be available.", + "condition": "[not(equals(tryWhich('bicep'), null()))]", + "import": { + "fileExtensions": ["bicepparam"], + "executable": "bicep", + "args": [ + "build-params", + { + "fileArg": "" + }, + "--stdout" + ] + } +} diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 48e1450f3..f92196fed 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -85,6 +85,13 @@ secureOutputSkipped = "Secure output '%{name}' is skipped" outputTypeNotMatch = "Output '%{name}' type does not match expected type '%{expected_type}'" copyNotSupported = "Copy for output '%{name}' is currently not supported" +[configure.parameters] +importingParametersFromJson = "Importing parameters from `parameters_input` JSON" +importingParametersFromComplexInput = "Importing parameters from complex input" +importingParametersFromInput = "Importing parameters from simple input" +invalidParamsJsonFormat = "Invalid parameters JSON format: %{error}" +invalidParamsFormat = "Invalid parameters format: %{error}" + [discovery.commandDiscovery] couldNotReadSetting = "Could not read 'resourcePath' setting" appendingEnvPath = "Appending PATH to resourcePath" @@ -221,8 +228,11 @@ retrievingSecretFromExtension = "Retrieving secret '%{name}' from extension '%{e extensionReturnedSecret = "Extension '%{extension}' returned secret" extensionReturnedNoSecret = "Extension '%{extension}' did not return a secret" importingFile = "Importing file '%{file}' with extension '%{extension}'" +importingParametersFile = "Importing parameters file '%{file}' with extension '%{extension}'" importNotSupported = "Import is not supported by extension '%{extension}' for file '%{file}'" +importParametersNotSupported = "Import parameters is not supported by extension '%{extension}' for file '%{file}'" importNoResults = "Extension '%{extension}' returned no results for import" +importParametersNoResults = "Extension '%{extension}' returned no results for import parameters" secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines which is not supported for secrets" [extensions.extension_manifest] diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 1fd33a194..ee0f49b61 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. use crate::configure::context::{Context, ProcessMode}; -use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, RestartRequired, ValueOrCopy}, parameters::Input}; +use crate::configure::parameters::import_parameters; +use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, RestartRequired, ValueOrCopy}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscerror::DscError; use crate::dscresources::{ @@ -836,7 +837,7 @@ impl Configurator { // process input parameters first if let Some(parameters_input) = parameters_input { trace!("parameters_input: {parameters_input}"); - let input_parameters: HashMap = serde_json::from_value::(parameters_input.clone())?.parameters; + let input_parameters: HashMap = import_parameters(parameters_input)?; for (name, value) in input_parameters { if let Some(constraint) = parameters.get(&name) { diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index 549730b2f..da40ee7ad 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -1,16 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::dscerror::DscError; +use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{collections::HashMap, fmt::Display}; +use tracing::trace; -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Input { pub parameters: HashMap, } +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ComplexInput { + pub parameters: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct InputObject { + pub value: Value, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ParametersJson { + #[serde(rename = "parametersJson")] + pub parameters_json: String, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ParametersInput { + pub parameters_input: ParametersJson, +} + pub const SECURE_VALUE_REDACTED: &str = ""; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -64,3 +88,48 @@ pub enum SecureKind { #[serde(rename = "secureObject")] SecureObject(SecureObject), } + +pub fn import_parameters(parameters: &Value) -> Result, DscError> { + let input = match serde_json::from_value::(parameters.clone()) { + Ok(input) => { + trace!("{}", t!("configure.parameters.importingParametersFromJson")); + let param_map = match serde_json::from_str::(&input.parameters_json) { + Ok(param_map) => param_map, + Err(e) => { + return Err(DscError::Parser(t!("configure.parameters.invalidParamsJsonFormat", error = e).to_string())); + } + }; + let mut result: HashMap = HashMap::new(); + for (name, input_object) in param_map.parameters { + result.insert(name, input_object.value); + } + result + }, + Err(_) => { + let input = match serde_json::from_value::(parameters.clone()) { + Ok(input) => { + trace!("{}", t!("configure.parameters.importingParametersFromComplexInput")); + let mut result: HashMap = HashMap::new(); + for (name, input_object) in input.parameters { + result.insert(name, input_object.value); + } + result + }, + Err(_) => { + let input = match serde_json::from_value::(parameters.clone()) { + Ok(input) => { + trace!("{}", t!("configure.parameters.importingParametersFromInput")); + input.parameters + } + Err(e) => { + return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string())); + } + }; + input + } + }; + input + }, + }; + Ok(input) +} diff --git a/lib/dsc-lib/src/extensions/dscextension.rs b/lib/dsc-lib/src/extensions/dscextension.rs index 6bb1daedf..253263de7 100644 --- a/lib/dsc-lib/src/extensions/dscextension.rs +++ b/lib/dsc-lib/src/extensions/dscextension.rs @@ -43,6 +43,8 @@ pub enum Capability { Secret, /// The extension imports configuration from a different format. Import, + /// The extension imports parameters from a different format. + ImportParameters, } impl Display for Capability { @@ -51,6 +53,7 @@ impl Display for Capability { Capability::Discover => write!(f, "Discover"), Capability::Secret => write!(f, "Secret"), Capability::Import => write!(f, "Import"), + Capability::ImportParameters => write!(f, "ImportParams"), } } } diff --git a/lib/dsc-lib/src/extensions/extension_manifest.rs b/lib/dsc-lib/src/extensions/extension_manifest.rs index b745dc9e2..4fb867afc 100644 --- a/lib/dsc-lib/src/extensions/extension_manifest.rs +++ b/lib/dsc-lib/src/extensions/extension_manifest.rs @@ -34,6 +34,9 @@ pub struct ExtensionManifest { pub discover: Option, /// Details how to call the Import method of the extension. pub import: Option, + /// Details how to call the ImportParameters method of the extension. + #[serde(rename = "importParameters")] + pub import_parameters: Option, /// Details how to call the Secret method of the extension. pub secret: Option, /// Mapping of exit codes to descriptions. Zero is always success and non-zero is always failure. diff --git a/lib/dsc-lib/src/extensions/import.rs b/lib/dsc-lib/src/extensions/import.rs index e38ffa743..613b69544 100644 --- a/lib/dsc-lib/src/extensions/import.rs +++ b/lib/dsc-lib/src/extensions/import.rs @@ -67,7 +67,6 @@ impl DscExtension { if self.import_file_extensions.as_ref().is_some_and(|exts| exts.contains(&file_extension)) { debug!("{}", t!("extensions.dscextension.importingFile", file = file.display(), extension = self.type_name)); } else { - debug!("{}", t!("extensions.dscextension.importNotSupported", file = file.display(), extension = self.type_name)); return Err(DscError::NotSupported( t!("extensions.dscextension.importNotSupported", file = file.display(), extension = self.type_name).to_string(), )); @@ -101,7 +100,7 @@ impl DscExtension { self.type_name.clone(), Capability::Import.to_string() )) - } + } } fn process_import_args(args: Option<&Vec>, file: &Path) -> Result>, DscError> { diff --git a/lib/dsc-lib/src/extensions/mod.rs b/lib/dsc-lib/src/extensions/mod.rs index 3bf58ceca..23ad08db0 100644 --- a/lib/dsc-lib/src/extensions/mod.rs +++ b/lib/dsc-lib/src/extensions/mod.rs @@ -4,5 +4,5 @@ pub mod discover; pub mod dscextension; pub mod extension_manifest; -pub mod secret; pub mod import; +pub mod secret; From e70f9b1c7370a159c297f4bc0d2471899975396e Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 16:52:31 -0800 Subject: [PATCH 02/11] fix strings --- dsc/locales/en-us.toml | 3 --- lib/dsc-lib/locales/en-us.toml | 3 --- 2 files changed, 6 deletions(-) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index f6cb936cf..5ca2bf42a 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -40,10 +40,7 @@ mcpAbout = "Use DSC as a MCP server" [main] ctrlCReceived = "Ctrl-C received" failedCtrlCHandler = "Failed to set Ctrl-C handler" -failedReadingParametersFile = "Failed to read parameters file" -readingParametersFromStdin = "Reading parameters from STDIN" generatingCompleter = "Generating completion script for" -readingParametersFile = "Reading parameters from file" mergingParameters = "Merging inline parameters with parameters file (inline takes precedence)" failedMergingParameters = "Failed to merge parameters" usingDscVersion = "Running DSC version" diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index f92196fed..6eeb34508 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -228,11 +228,8 @@ retrievingSecretFromExtension = "Retrieving secret '%{name}' from extension '%{e extensionReturnedSecret = "Extension '%{extension}' returned secret" extensionReturnedNoSecret = "Extension '%{extension}' did not return a secret" importingFile = "Importing file '%{file}' with extension '%{extension}'" -importingParametersFile = "Importing parameters file '%{file}' with extension '%{extension}'" importNotSupported = "Import is not supported by extension '%{extension}' for file '%{file}'" -importParametersNotSupported = "Import parameters is not supported by extension '%{extension}' for file '%{file}'" importNoResults = "Extension '%{extension}' returned no results for import" -importParametersNoResults = "Extension '%{extension}' returned no results for import parameters" secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines which is not supported for secrets" [extensions.extension_manifest] From 4a982f59428272f6132950543ab1bd8a8f9c6aff Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 17:13:31 -0800 Subject: [PATCH 03/11] Update lib/dsc-lib/src/configure/parameters.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/dsc-lib/src/configure/parameters.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index da40ee7ad..085709702 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -106,29 +106,29 @@ pub fn import_parameters(parameters: &Value) -> Result, D result }, Err(_) => { - let input = match serde_json::from_value::(parameters.clone()) { - Ok(input) => { + let complex_input = match serde_json::from_value::(parameters.clone()) { + Ok(complex_input) => { trace!("{}", t!("configure.parameters.importingParametersFromComplexInput")); let mut result: HashMap = HashMap::new(); - for (name, input_object) in input.parameters { + for (name, input_object) in complex_input.parameters { result.insert(name, input_object.value); } result }, Err(_) => { - let input = match serde_json::from_value::(parameters.clone()) { - Ok(input) => { + let simple_input = match serde_json::from_value::(parameters.clone()) { + Ok(simple_input) => { trace!("{}", t!("configure.parameters.importingParametersFromInput")); - input.parameters + simple_input.parameters } Err(e) => { return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string())); } }; - input + simple_input } }; - input + complex_input }, }; Ok(input) From c54d7de634c95f60a8fa73a41b6c9dd09eca7f64 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 17:14:12 -0800 Subject: [PATCH 04/11] Update lib/dsc-lib/src/configure/parameters.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/dsc-lib/src/configure/parameters.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index 085709702..8949157f5 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -30,11 +30,6 @@ pub struct ParametersJson { pub parameters_json: String, } -#[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct ParametersInput { - pub parameters_input: ParametersJson, -} - pub const SECURE_VALUE_REDACTED: &str = ""; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] From 6b3c55db3b746bf9e3998b41b2f35568575cf732 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 17:22:10 -0800 Subject: [PATCH 05/11] add JsonSchema for input --- lib/dsc-lib/src/configure/parameters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index 8949157f5..6ed8a1c57 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -9,7 +9,7 @@ use serde_json::Value; use std::{collections::HashMap, fmt::Display}; use tracing::trace; -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)] pub struct Input { pub parameters: HashMap, } From 9fa4f9a3440eba0780456d49d4434c40f8b0a436 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 20 Nov 2025 17:35:44 -0800 Subject: [PATCH 06/11] fix test --- dsc/tests/dsc_extension_discover.tests.ps1 | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 8e015ed92..fcb5693c4 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -24,29 +24,37 @@ Describe 'Discover extension tests' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 if ($IsWindows) { - $out.Count | Should -Be 3 -Because ($out | Out-String) + $out.Count | Should -Be 4 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') + $out[1].type | Should -BeExactly 'Microsoft.DSC.Extension/BicepParameters' + $out[1].version | Should -BeExactly '0.1.0' + $out[1].capabilities | Should -BeExactly @('import') $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -BeExactly 'Test/Discover' - $out[2].version | Should -BeExactly '0.1.0' + $out[2].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[2].version | Should -Be '0.1.0' $out[2].capabilities | Should -BeExactly @('discover') $out[2].manifest | Should -Not -BeNullOrEmpty + $out[3].type | Should -BeExactly 'Test/Discover' + $out[3].version | Should -BeExactly '0.1.0' + $out[3].capabilities | Should -BeExactly @('discover') + $out[3].manifest | Should -Not -BeNullOrEmpty } else { - $out.Count | Should -Be 2 -Because ($out | Out-String) + $out.Count | Should -Be 3 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -BeExactly 'Test/Discover' + $out[1].type | Should -BeExactly 'Microsoft.DSC.Extension/BicepParameters' $out[1].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') + $out[1].capabilities | Should -BeExactly @('import') $out[1].manifest | Should -Not -BeNullOrEmpty + $out[2].type | Should -BeExactly 'Test/Discover' + $out[2].version | Should -BeExactly '0.1.0' + $out[2].capabilities | Should -BeExactly @('discover') + $out[2].manifest | Should -Not -BeNullOrEmpty } } From e7511991baf0b1d17ad16904560df2e3a5b30c30 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 21 Nov 2025 07:07:32 -0800 Subject: [PATCH 07/11] reformat test --- dsc/tests/dsc_parameters.tests.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dsc/tests/dsc_parameters.tests.ps1 b/dsc/tests/dsc_parameters.tests.ps1 index 9d18b364f..e5ec560c1 100644 --- a/dsc/tests/dsc_parameters.tests.ps1 +++ b/dsc/tests/dsc_parameters.tests.ps1 @@ -412,7 +412,19 @@ Describe 'Parameters tests' { - name: echo type: Microsoft.DSC.Debug/Echo properties: - output: "[concat(parameters('myString'), '-', parameters('myObject').prop1, '-', parameters('myArray')[0], parameters('myArray')[1], '-', string(parameters('myInt')), '-', string(parameters('myBool'))]" + output: >- + [concat( + parameters('myString'), + '-', + parameters('myObject').prop1, + '-', + parameters('myArray')[0], + parameters('myArray')[1], + '-', + string(parameters('myInt')), + '-', + string(parameters('myBool') + )] "@ $params = @{ parameters = @{ From d937f3aa2cf9d8b07a99584f6857f9add35e25da Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 21 Nov 2025 09:19:48 -0800 Subject: [PATCH 08/11] Update dsc/examples/hello_world.dsc.json Co-authored-by: Tess Gauthier --- dsc/examples/hello_world.dsc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/examples/hello_world.dsc.json b/dsc/examples/hello_world.dsc.json index d6cbbda9e..80b54e85e 100644 --- a/dsc/examples/hello_world.dsc.json +++ b/dsc/examples/hello_world.dsc.json @@ -6,4 +6,4 @@ "value": "This is a parameterized hello world!" } } -} \ No newline at end of file +} From c73a1ffab4f80b2e554c2eff38129d9242de50fe Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 21 Nov 2025 10:16:23 -0800 Subject: [PATCH 09/11] remove unused file --- dsc/examples/hello_world.dsc.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 dsc/examples/hello_world.dsc.json diff --git a/dsc/examples/hello_world.dsc.json b/dsc/examples/hello_world.dsc.json deleted file mode 100644 index 80b54e85e..000000000 --- a/dsc/examples/hello_world.dsc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "text": { - "value": "This is a parameterized hello world!" - } - } -} From ffceb56a94dd3fe99c6d9f8f5f79fa2510f44f52 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 21 Nov 2025 13:38:26 -0800 Subject: [PATCH 10/11] Add `output` property to import extensions --- dsc/src/subcommand.rs | 1 + .../bicep/bicepparams.dsc.extension.json | 3 +- lib/dsc-lib/locales/en-us.toml | 11 +++- lib/dsc-lib/src/configure/context.rs | 2 + lib/dsc-lib/src/configure/parameters.rs | 55 +++++-------------- .../src/discovery/command_discovery.rs | 6 +- lib/dsc-lib/src/extensions/dscextension.rs | 11 ++-- lib/dsc-lib/src/extensions/import.rs | 26 +++++---- lib/dsc-lib/src/functions/mod.rs | 2 + lib/dsc-lib/src/functions/stdout.rs | 36 ++++++++++++ lib/dsc-lib/src/parser/expressions.rs | 12 +++- lib/dsc-lib/src/parser/functions.rs | 4 +- .../tests/integration/schemas/schema_for.rs | 2 +- 13 files changed, 102 insertions(+), 69 deletions(-) create mode 100644 lib/dsc-lib/src/functions/stdout.rs diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 9e6857b29..3132dfedd 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -607,6 +607,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format let capability_types = [ (ExtensionCapability::Discover, "d"), (ExtensionCapability::Secret, "s"), + (ExtensionCapability::Import, "i"), ]; let mut capabilities = "-".repeat(capability_types.len()); diff --git a/extensions/bicep/bicepparams.dsc.extension.json b/extensions/bicep/bicepparams.dsc.extension.json index 63a18b396..8bd19868b 100644 --- a/extensions/bicep/bicepparams.dsc.extension.json +++ b/extensions/bicep/bicepparams.dsc.extension.json @@ -13,6 +13,7 @@ "fileArg": "" }, "--stdout" - ] + ], + "output": "[json(json(stdout()).parametersJson)]" } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 6eeb34508..0d3ad234f 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -86,10 +86,8 @@ outputTypeNotMatch = "Output '%{name}' type does not match expected type '%{expe copyNotSupported = "Copy for output '%{name}' is currently not supported" [configure.parameters] -importingParametersFromJson = "Importing parameters from `parameters_input` JSON" importingParametersFromComplexInput = "Importing parameters from complex input" importingParametersFromInput = "Importing parameters from simple input" -invalidParamsJsonFormat = "Invalid parameters JSON format: %{error}" invalidParamsFormat = "Invalid parameters format: %{error}" [discovery.commandDiscovery] @@ -231,6 +229,7 @@ importingFile = "Importing file '%{file}' with extension '%{extension}'" importNotSupported = "Import is not supported by extension '%{extension}' for file '%{file}'" importNoResults = "Extension '%{extension}' returned no results for import" secretMultipleLinesReturned = "Extension '%{extension}' returned multiple lines which is not supported for secrets" +importProcessingOutput = "Processing output from extension '%{extension}'" [extensions.extension_manifest] extensionManifestSchemaTitle = "Extension manifest schema URI" @@ -545,6 +544,10 @@ invoked = "skip function" invalidNumberToSkip = "Second argument must be an integer" invalidOriginalValue = "First argument must be an array or string" +[functions.stdout] +description = "Returns the standard output from the last executed resource. Can only be used in output definitions." +noStdoutAvailable = "No standard output is available from the last executed resource" + [functions.string] description = "Converts a value to a string" @@ -664,6 +667,10 @@ indexOutOfBounds = "Index is out of bounds" indexOnNonArray = "Index access on non-array value" invalidIndexType = "Invalid index type" propertyNameNotString = "Property name is not a string" +accessorResult = "Accessor result: %{result}" +evaluatingMemberAccessor = "Evaluating member accessor: %{name}" +evaluatingIndexAccessor = "Evaluating index accessor: %{index}" +evaluatingIndexExpression = "Evaluating index expression: %{expression}" [parser.functions] foundErrorNode = "Found error node parsing function" diff --git a/lib/dsc-lib/src/configure/context.rs b/lib/dsc-lib/src/configure/context.rs index abd52948d..50641ee96 100644 --- a/lib/dsc-lib/src/configure/context.rs +++ b/lib/dsc-lib/src/configure/context.rs @@ -34,6 +34,7 @@ pub struct Context { pub restart_required: Option>, pub security_context: SecurityContextKind, pub start_datetime: DateTime, + pub stdout: Option, pub system_root: PathBuf, pub user_functions: HashMap, pub variables: Map, @@ -60,6 +61,7 @@ impl Context { SecurityContext::User => SecurityContextKind::Restricted, }, start_datetime: chrono::Local::now(), + stdout: None, system_root: get_default_os_system_root(), user_functions: HashMap::new(), variables: Map::new(), diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index 6ed8a1c57..b9357da13 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -10,7 +10,7 @@ use std::{collections::HashMap, fmt::Display}; use tracing::trace; #[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)] -pub struct Input { +pub struct SimpleInput { pub parameters: HashMap, } @@ -24,12 +24,6 @@ pub struct InputObject { pub value: Value, } -#[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct ParametersJson { - #[serde(rename = "parametersJson")] - pub parameters_json: String, -} - pub const SECURE_VALUE_REDACTED: &str = ""; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -85,46 +79,27 @@ pub enum SecureKind { } pub fn import_parameters(parameters: &Value) -> Result, DscError> { - let input = match serde_json::from_value::(parameters.clone()) { - Ok(input) => { - trace!("{}", t!("configure.parameters.importingParametersFromJson")); - let param_map = match serde_json::from_str::(&input.parameters_json) { - Ok(param_map) => param_map, - Err(e) => { - return Err(DscError::Parser(t!("configure.parameters.invalidParamsJsonFormat", error = e).to_string())); - } - }; + let parameters = match serde_json::from_value::(parameters.clone()) { + Ok(complex_input) => { + trace!("{}", t!("configure.parameters.importingParametersFromComplexInput")); let mut result: HashMap = HashMap::new(); - for (name, input_object) in param_map.parameters { + for (name, input_object) in complex_input.parameters { result.insert(name, input_object.value); } result }, Err(_) => { - let complex_input = match serde_json::from_value::(parameters.clone()) { - Ok(complex_input) => { - trace!("{}", t!("configure.parameters.importingParametersFromComplexInput")); - let mut result: HashMap = HashMap::new(); - for (name, input_object) in complex_input.parameters { - result.insert(name, input_object.value); - } - result - }, - Err(_) => { - let simple_input = match serde_json::from_value::(parameters.clone()) { - Ok(simple_input) => { - trace!("{}", t!("configure.parameters.importingParametersFromInput")); - simple_input.parameters - } - Err(e) => { - return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string())); - } - }; - simple_input + let simple_input = match serde_json::from_value::(parameters.clone()) { + Ok(simple_input) => { + trace!("{}", t!("configure.parameters.importingParametersFromInput")); + simple_input.parameters + } + Err(e) => { + return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string())); } }; - complex_input - }, + simple_input + } }; - Ok(input) + Ok(parameters) } diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 0f12136af..e34ec66b2 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -803,14 +803,14 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result< verify_executable(&manifest.r#type, "secret", &secret.executable, path.parent().unwrap()); capabilities.push(dscextension::Capability::Secret); } - let import_extensions = if let Some(import) = &manifest.import { + let import = if let Some(import) = &manifest.import { verify_executable(&manifest.r#type, "import", &import.executable, path.parent().unwrap()); capabilities.push(dscextension::Capability::Import); if import.file_extensions.is_empty() { warn!("{}", t!("discovery.commandDiscovery.importExtensionsEmpty", extension = manifest.r#type)); None } else { - Some(import.file_extensions.clone()) + Some(import.clone()) } } else { None @@ -821,7 +821,7 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result< description: manifest.description.clone(), version: manifest.version.clone(), capabilities, - import_file_extensions: import_extensions, + import, path: path.to_path_buf(), directory: path.parent().unwrap().to_path_buf(), manifest: serde_json::to_value(manifest)?, diff --git a/lib/dsc-lib/src/extensions/dscextension.rs b/lib/dsc-lib/src/extensions/dscextension.rs index 253263de7..e668a3332 100644 --- a/lib/dsc-lib/src/extensions/dscextension.rs +++ b/lib/dsc-lib/src/extensions/dscextension.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::extensions::import::ImportMethod; use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -18,9 +19,8 @@ pub struct DscExtension { pub version: String, /// The capabilities of the resource. pub capabilities: Vec, - /// The extensions supported for importing. - #[serde(rename = "importFileExtensions")] - pub import_file_extensions: Option>, + /// The import specifics. + pub import: Option, /// The file path to the resource. pub path: PathBuf, /// The description of the resource. @@ -43,8 +43,6 @@ pub enum Capability { Secret, /// The extension imports configuration from a different format. Import, - /// The extension imports parameters from a different format. - ImportParameters, } impl Display for Capability { @@ -53,7 +51,6 @@ impl Display for Capability { Capability::Discover => write!(f, "Discover"), Capability::Secret => write!(f, "Secret"), Capability::Import => write!(f, "Import"), - Capability::ImportParameters => write!(f, "ImportParams"), } } } @@ -65,7 +62,7 @@ impl DscExtension { type_name: String::new(), version: String::new(), capabilities: Vec::new(), - import_file_extensions: None, + import: None, description: None, path: PathBuf::new(), directory: PathBuf::new(), diff --git a/lib/dsc-lib/src/extensions/import.rs b/lib/dsc-lib/src/extensions/import.rs index 613b69544..ef487dbde 100644 --- a/lib/dsc-lib/src/extensions/import.rs +++ b/lib/dsc-lib/src/extensions/import.rs @@ -2,19 +2,13 @@ // Licensed under the MIT License. use crate::{ - dscerror::DscError, - dscresources::{ - command_resource::{ - invoke_command, - }, - }, - extensions::{ + configure::context::Context, dscerror::DscError, dscresources::command_resource::invoke_command, extensions::{ dscextension::{ Capability, DscExtension, }, extension_manifest::ExtensionManifest, - }, + }, parser::Statement }; use path_absolutize::Absolutize; use rust_i18n::t; @@ -32,6 +26,8 @@ pub struct ImportMethod { pub executable: String, /// The arguments to pass to the command to perform an Import. pub args: Option>, + /// Enables modifying the resulting output from STDOUT after running the import command. + pub output: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -62,9 +58,9 @@ impl DscExtension { /// /// This function will return an error if the import fails or if the extension does not support the import capability. pub fn import(&self, file: &Path) -> Result { - if self.capabilities.contains(&Capability::Import) { + if let Some(import) = &self.import { let file_extension = file.extension().and_then(|s| s.to_str()).unwrap_or_default().to_string(); - if self.import_file_extensions.as_ref().is_some_and(|exts| exts.contains(&file_extension)) { + if import.file_extensions.contains(&file_extension) { debug!("{}", t!("extensions.dscextension.importingFile", file = file.display(), extension = self.type_name)); } else { return Err(DscError::NotSupported( @@ -93,6 +89,16 @@ impl DscExtension { if stdout.is_empty() { info!("{}", t!("extensions.dscextension.importNoResults", extension = self.type_name)); } else { + debug!("got stdout: {}", stdout); + if let Some(output) = &import.output { + debug!("processing output: {}", output); + debug!("{}", t!("extensions.dscextension.importProcessingOutput", extension = self.type_name)); + let mut parser = Statement::new()?; + let mut context = Context::new(); + context.stdout = Some(stdout); + let processed_output = parser.parse_and_execute(output, &context)?; + return Ok(processed_output.to_string()); + } return Ok(stdout); } } diff --git a/lib/dsc-lib/src/functions/mod.rs b/lib/dsc-lib/src/functions/mod.rs index bd4f3eaf3..0ab82e7f1 100644 --- a/lib/dsc-lib/src/functions/mod.rs +++ b/lib/dsc-lib/src/functions/mod.rs @@ -66,6 +66,7 @@ pub mod resource_id; pub mod secret; pub mod skip; pub mod starts_with; +pub mod stdout; pub mod string; pub mod take; pub mod sub; @@ -202,6 +203,7 @@ impl FunctionDispatcher { Box::new(secret::Secret{}), Box::new(skip::Skip{}), Box::new(starts_with::StartsWith{}), + Box::new(stdout::Stdout{}), Box::new(string::StringFn{}), Box::new(sub::Sub{}), Box::new(take::Take{}), diff --git a/lib/dsc-lib/src/functions/stdout.rs b/lib/dsc-lib/src/functions/stdout.rs new file mode 100644 index 000000000..9da1d1b9f --- /dev/null +++ b/lib/dsc-lib/src/functions/stdout.rs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::DscError; +use crate::configure::context::Context; +use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use rust_i18n::t; +use serde_json::Value; +use super::Function; + +#[derive(Debug, Default)] +pub struct Stdout {} + +impl Function for Stdout { + fn get_metadata(&self) -> FunctionMetadata { + FunctionMetadata { + name: "stdout".to_string(), + description: t!("functions.stdout.description").to_string(), + category: vec![FunctionCategory::System], + min_args: 0, + max_args: 0, + accepted_arg_ordered_types: vec![], + remaining_arg_accepted_types: None, + return_types: vec![FunctionArgKind::String], + } + } + + fn invoke(&self, _args: &[Value], context: &Context) -> Result { + if let Some(stdout) = &context.stdout { + let result = stdout.to_string(); + return Ok(Value::String(result)); + } + Err(DscError::Parser(t!("functions.stdout.noStdoutAvailable").to_string(), + )) + } +} diff --git a/lib/dsc-lib/src/parser/expressions.rs b/lib/dsc-lib/src/parser/expressions.rs index ea568b38e..0b5996411 100644 --- a/lib/dsc-lib/src/parser/expressions.rs +++ b/lib/dsc-lib/src/parser/expressions.rs @@ -3,7 +3,7 @@ use rust_i18n::t; use serde_json::Value; -use tracing::{debug, trace}; +use tracing::{debug, trace, warn}; use tree_sitter::Node; use crate::configure::context::Context; @@ -12,14 +12,14 @@ use crate::dscerror::DscError; use crate::functions::FunctionDispatcher; use crate::parser::functions::Function; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Accessor { Member(String), Index(Value), IndexExpression(Expression), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Expression { function: Function, accessors: Vec, @@ -156,8 +156,10 @@ impl Expression { let mut index = Value::Null; match accessor { Accessor::Member(member) => { + debug!("{}", t!("parser.expression.evaluatingMemberAccessor", name = member : {:?})); if let Some(object) = value.as_object() { if !object.contains_key(member) { + warn!("{}", t!("parser.expression.memberNameNotFound", member = member)); return Err(DscError::Parser(t!("parser.expression.memberNameNotFound", member = member).to_string())); } if is_secure { @@ -166,10 +168,12 @@ impl Expression { value = object[member].clone(); } } else { + warn!("{}", t!("parser.expression.accessOnNonObject")); return Err(DscError::Parser(t!("parser.expression.accessOnNonObject").to_string())); } }, Accessor::Index(index_value) => { + debug!("{}", t!("parser.expression.evaluatingIndexAccessor", index = index_value : {:?})); if is_secure { index = convert_to_secure(index_value); } else { @@ -177,6 +181,7 @@ impl Expression { } }, Accessor::IndexExpression(expression) => { + debug!("{}", t!("parser.expression.evaluatingIndexExpression", expression = expression : {:?})); index = expression.invoke(function_dispatcher, context)?; trace!("{}", t!("parser.expression.expressionResult", index = index : {:?})); }, @@ -220,6 +225,7 @@ impl Expression { } } + trace!("{}", t!("parser.expression.accessorResult", result = value : {:?})); Ok(value) } } diff --git a/lib/dsc-lib/src/parser/functions.rs b/lib/dsc-lib/src/parser/functions.rs index a01d3e8aa..70847ef14 100644 --- a/lib/dsc-lib/src/parser/functions.rs +++ b/lib/dsc-lib/src/parser/functions.rs @@ -13,13 +13,13 @@ use crate::parser::{ FunctionDispatcher, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Function { name: String, args: Option>, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum FunctionArg { Value(Value), Expression(Expression), diff --git a/lib/dsc-lib/tests/integration/schemas/schema_for.rs b/lib/dsc-lib/tests/integration/schemas/schema_for.rs index 4255b9605..ef35cc1cb 100644 --- a/lib/dsc-lib/tests/integration/schemas/schema_for.rs +++ b/lib/dsc-lib/tests/integration/schemas/schema_for.rs @@ -68,7 +68,7 @@ macro_rules! test_schema_for { } #[allow(unused_must_use)] #[cfg(test)] mod parameters { - test_schema_for!(dsc_lib::configure::parameters::Input); + test_schema_for!(dsc_lib::configure::parameters::SimpleInput); test_schema_for!(dsc_lib::configure::parameters::SecureString); test_schema_for!(dsc_lib::configure::parameters::SecureObject); test_schema_for!(dsc_lib::configure::parameters::SecureKind); From 0d0282a6bae0870e6905696719b790fb352a8adb Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 21 Nov 2025 14:04:10 -0800 Subject: [PATCH 11/11] fix test --- dsc/tests/dsc_expressions.tests.ps1 | 2 +- lib/dsc-lib/src/functions/stdout.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index 6c79fdfec..6086346cb 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -73,7 +73,7 @@ Describe 'Expressions tests' { "@ $out = dsc config get -i $yaml 2>&1 $LASTEXITCODE | Should -Be 2 - $out | Should -BeLike "*ERROR*" + ,$out | Should -BeLike "*ERROR*" -Because ($out | Out-String) } It 'Multi-line string literals work' { diff --git a/lib/dsc-lib/src/functions/stdout.rs b/lib/dsc-lib/src/functions/stdout.rs index 9da1d1b9f..8e0de8196 100644 --- a/lib/dsc-lib/src/functions/stdout.rs +++ b/lib/dsc-lib/src/functions/stdout.rs @@ -30,7 +30,6 @@ impl Function for Stdout { let result = stdout.to_string(); return Ok(Value::String(result)); } - Err(DscError::Parser(t!("functions.stdout.noStdoutAvailable").to_string(), - )) + Err(DscError::Parser(t!("functions.stdout.noStdoutAvailable").to_string())) } }