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/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/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/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/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/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 } } diff --git a/dsc/tests/dsc_parameters.tests.ps1 b/dsc/tests/dsc_parameters.tests.ps1 index 839159377..e5ec560c1 100644 --- a/dsc/tests/dsc_parameters.tests.ps1 +++ b/dsc/tests/dsc_parameters.tests.ps1 @@ -394,6 +394,65 @@ 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 +462,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 +994,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..8bd19868b --- /dev/null +++ b/extensions/bicep/bicepparams.dsc.extension.json @@ -0,0 +1,19 @@ +{ + "$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" + ], + "output": "[json(json(stdout()).parametersJson)]" + } +} diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 48e1450f3..0d3ad234f 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -85,6 +85,11 @@ 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] +importingParametersFromComplexInput = "Importing parameters from complex input" +importingParametersFromInput = "Importing parameters from simple input" +invalidParamsFormat = "Invalid parameters format: %{error}" + [discovery.commandDiscovery] couldNotReadSetting = "Could not read 'resourcePath' setting" appendingEnvPath = "Appending PATH to resourcePath" @@ -224,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" @@ -538,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" @@ -657,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/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..b9357da13 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -1,16 +1,29 @@ // 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)] -pub struct Input { +#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)] +pub struct SimpleInput { 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, +} + pub const SECURE_VALUE_REDACTED: &str = ""; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -64,3 +77,29 @@ pub enum SecureKind { #[serde(rename = "secureObject")] SecureObject(SecureObject), } + +pub fn import_parameters(parameters: &Value) -> Result, DscError> { + 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 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 + } + }; + 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 6bb1daedf..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. @@ -62,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/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..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,12 +58,11 @@ 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 { - 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(), )); @@ -94,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); } } @@ -101,7 +106,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; 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..8e0de8196 --- /dev/null +++ b/lib/dsc-lib/src/functions/stdout.rs @@ -0,0 +1,35 @@ +// 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);