diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index c4fe82b1..d08a56a2 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -4,7 +4,7 @@ use crate::args::{ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand}; use crate::resource_command::{get_resource, self}; use crate::tablewriter::Table; -use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, EXIT_SUCCESS, EXIT_VALIDATION_FAILED, get_schema, write_output, get_input}; +use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, EXIT_SUCCESS, EXIT_VALIDATION_FAILED, get_schema, write_output, get_input, set_dscconfigroot}; use tracing::error; use atty::Stream; @@ -123,6 +123,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, stdin: ConfigSubCommand::Test { document, path, .. } | ConfigSubCommand::Validate { document, path, .. } | ConfigSubCommand::Export { document, path, .. } => { + let config_path = path.clone().unwrap_or_default(); + set_dscconfigroot(&config_path); get_input(document, stdin, path) } }; diff --git a/dsc/src/util.rs b/dsc/src/util.rs index fbaa15ea..2ca73de9 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -19,6 +19,8 @@ use dsc_lib::{ use schemars::{schema_for, schema::RootSchema}; use serde_yaml::Value; use std::collections::HashMap; +use std::env; +use std::path::Path; use std::process::exit; use syntect::{ easy::HighlightLines, @@ -357,3 +359,17 @@ pub fn get_input(input: &Option, stdin: &Option, path: &Option { dir_path.to_str().unwrap_or_default().to_string()}, + _ => String::new() + }; + + // Set env var so child processes (of resources) can use it + debug!("Setting 'DSCConfigRoot' env var as '{}'", config_root); + env::set_var("DSCConfigRoot", config_root.clone()); +} diff --git a/dsc_lib/src/functions/envvar.rs b/dsc_lib/src/functions/envvar.rs new file mode 100644 index 00000000..d2610092 --- /dev/null +++ b/dsc_lib/src/functions/envvar.rs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::DscError; +use crate::configure::context::Context; +use crate::parser::functions::{FunctionArg, FunctionResult}; +use super::{Function, AcceptedArgKind}; +use std::env; + +#[derive(Debug, Default)] +pub struct Envvar {} + +impl Function for Envvar { + fn accepted_arg_types(&self) -> Vec { + vec![AcceptedArgKind::String] + } + + fn min_args(&self) -> usize { + 1 + } + + fn max_args(&self) -> usize { + 1 + } + + fn invoke(&self, args: &[FunctionArg], _context: &Context) -> Result { + let FunctionArg::String(arg) = args.first().unwrap() else { + return Err(DscError::Parser("Invalid argument type".to_string())); + }; + + let val = env::var(arg).unwrap_or_default(); + Ok(FunctionResult::String(val)) + } +} diff --git a/dsc_lib/src/functions/mod.rs b/dsc_lib/src/functions/mod.rs index a2a7ced3..80f5a9d1 100644 --- a/dsc_lib/src/functions/mod.rs +++ b/dsc_lib/src/functions/mod.rs @@ -10,6 +10,7 @@ use crate::parser::functions::{FunctionArg, FunctionResult}; pub mod base64; pub mod concat; +pub mod envvar; pub mod parameters; pub mod resource_id; @@ -53,6 +54,7 @@ impl FunctionDispatcher { let mut functions: HashMap> = HashMap::new(); functions.insert("base64".to_string(), Box::new(base64::Base64{})); functions.insert("concat".to_string(), Box::new(concat::Concat{})); + functions.insert("envvar".to_string(), Box::new(envvar::Envvar{})); functions.insert("parameters".to_string(), Box::new(parameters::Parameters{})); functions.insert("resourceId".to_string(), Box::new(resource_id::ResourceId{})); Self { diff --git a/powershellgroup/Tests/PSTestModule/TestClassResource.psm1 b/powershellgroup/Tests/PSTestModule/TestClassResource.psm1 index 01092b52..a61c1a2c 100644 --- a/powershellgroup/Tests/PSTestModule/TestClassResource.psm1 +++ b/powershellgroup/Tests/PSTestModule/TestClassResource.psm1 @@ -39,6 +39,10 @@ class TestClassResource { $this.Prop1 = "ValueForProp1" } + else + { + $this.Prop1 = $env:DSCConfigRoot + } $this.EnumProp = [EnumPropEnumeration]::Expected return $this } diff --git a/powershellgroup/Tests/powershellgroup.config.tests.ps1 b/powershellgroup/Tests/powershellgroup.config.tests.ps1 index 6b14b395..cb67c147 100644 --- a/powershellgroup/Tests/powershellgroup.config.tests.ps1 +++ b/powershellgroup/Tests/powershellgroup.config.tests.ps1 @@ -94,4 +94,50 @@ Describe 'PowerShellGroup resource tests' { $env:PSModulePath = $OldPSModulePath } } + + It 'DSCConfigRoot macro is working when config is from a file' -Skip:(!$IsWindows){ + + $yaml = @" + `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json + resources: + - name: Working with class-based resources + type: DSC/PowerShellGroup + properties: + resources: + - name: Class-resource Info + type: PSTestModule/TestClassResource + properties: + Name: "[envvar('DSCConfigRoot')]" +"@ + + $config_path = "$TestDrive/test_config.dsc.yaml" + $yaml | Set-Content -Path $config_path + + $out = dsc config get --path $config_path + $LASTEXITCODE | Should -Be 0 + $res = $out | ConvertFrom-Json + $res.results[0].result.actualState.Name | Should -Be $TestDrive + $res.results[0].result.actualState.Prop1 | Should -Be $TestDrive + } + + It 'DSCConfigRoot macro is empty when config is piped from stdin' -Skip:(!$IsWindows){ + + $yaml = @" + `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json + resources: + - name: Working with class-based resources + type: DSC/PowerShellGroup + properties: + resources: + - name: Class-resource Info + type: PSTestModule/TestClassResource + properties: + Name: "[envvar('DSCConfigRoot')]" +"@ + $out = $yaml | dsc config get + $LASTEXITCODE | Should -Be 0 + $res = $out | ConvertFrom-Json + $res.results[0].result.actualState.Name | Should -Be "" + $res.results[0].result.actualState.Prop1 | Should -Be "" + } }