Skip to content

Commit 73e30a2

Browse files
committed
fix expression invocation for default parameter values
1 parent e710d2a commit 73e30a2

File tree

9 files changed

+138
-120
lines changed

9 files changed

+138
-120
lines changed

dsc/examples/osinfo_parameters.dsc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/c
22
parameters:
33
osFamily:
44
type: string
5-
defaultValue: Windows
5+
defaultValue: "[concat('Win','dows')]"
66
allowedValues:
77
- Windows
88
- Linux

dsc/tests/dsc_parameters.tests.ps1

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,39 @@ Describe 'Parameters tests' {
268268
$out.results[0].result.actualState.family | Should -BeExactly $os
269269
$out.results[0].result.inDesiredState | Should -BeTrue
270270
}
271+
272+
It 'secure types can be passed as objects to resources' {
273+
$out = dsc config -f $PSScriptRoot/../examples/secure_parameters.parameters.yaml get -p $PSScriptRoot/../examples/secure_parameters.dsc.yaml | ConvertFrom-Json
274+
$LASTEXITCODE | Should -Be 0
275+
$out.results[0].result.actualState.output | Should -BeExactly 'mySecret'
276+
$out.results[1].result.actualState.output | Should -BeExactly 'mySecretProperty'
277+
}
278+
279+
It 'paramter types are validated for <type>' -TestCases @(
280+
@{ type = 'array'; value = 'hello'}
281+
@{ type = 'bool'; value = 'hello'}
282+
@{ type = 'int'; value = @(1,2)}
283+
@{ type = 'object'; value = 1}
284+
@{ type = 'secureString'; value = 1}
285+
@{ type = 'secureObject'; value = 'hello'}
286+
@{ type = 'string'; value = 42 }
287+
){
288+
param($type, $value)
289+
290+
$config_yaml = @"
291+
`$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/10/config/document.json
292+
parameters:
293+
param:
294+
type: $type
295+
resources:
296+
- name: Echo
297+
type: Test/Echo
298+
properties:
299+
output: '[parameters(''param'')]'
300+
"@
301+
302+
$params_json = @{ parameters = @{ param = $value }} | ConvertTo-Json
303+
$null = $config_yaml | dsc config -p $params_json get
304+
$LASTEXITCODE | Should -Be 4
305+
}
271306
}

dsc_lib/src/configure/context.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
use serde_json::Value;
55
use std::collections::HashMap;
66

7+
use super::config_doc::DataType;
8+
79
pub struct Context {
8-
pub parameters: HashMap<String, Value>,
10+
pub parameters: HashMap<String, (Value, DataType)>,
911
pub _variables: HashMap<String, Value>,
1012
pub outputs: HashMap<String, Value>, // this is used by the `reference()` function to retrieve output
1113
}

dsc_lib/src/configure/mod.rs

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,18 @@ impl Configurator {
398398

399399
for (name, parameter) in parameters {
400400
if let Some(default_value) = &parameter.default_value {
401-
// TODO: default values can be expressions
402-
// TODO: validate default value matches the type
403-
self.context.parameters.insert(name.clone(), default_value.clone());
401+
// default values can be expressions
402+
let value = if default_value.is_string() {
403+
if let Some(value) = default_value.as_str() {
404+
self.statement_parser.parse_and_execute(&value, &self.context)?
405+
} else {
406+
return Err(DscError::Parser("Default value as string is not defined".to_string()));
407+
}
408+
} else {
409+
default_value.clone()
410+
};
411+
Configurator::validate_parameter_type(&name, &value, &parameter.parameter_type)?;
412+
self.context.parameters.insert(name.clone(), (value, parameter.parameter_type.clone()));
404413
}
405414
}
406415

@@ -420,36 +429,13 @@ impl Configurator {
420429
// TODO: additional array constraints
421430
// TODO: object constraints
422431

423-
match constraint.parameter_type {
424-
DataType::String | DataType::SecureString => {
425-
if !value.is_string() {
426-
return Err(DscError::Validation(format!("Parameter '{name}' is not a string")));
427-
}
428-
},
429-
DataType::Int => {
430-
if !value.is_i64() {
431-
return Err(DscError::Validation(format!("Parameter '{name}' is not an integer")));
432-
}
433-
},
434-
DataType::Bool => {
435-
if !value.is_boolean() {
436-
return Err(DscError::Validation(format!("Parameter '{name}' is not a boolean")));
437-
}
438-
},
439-
DataType::Array => {
440-
if !value.is_array() {
441-
return Err(DscError::Validation(format!("Parameter '{name}' is not an array")));
442-
}
443-
},
444-
DataType::Object | DataType::SecureObject => {
445-
if !value.is_object() {
446-
return Err(DscError::Validation(format!("Parameter '{name}' is not an object")));
447-
}
448-
},
432+
Configurator::validate_parameter_type(&name, &value, &constraint.parameter_type)?;
433+
if constraint.parameter_type == DataType::SecureString || constraint.parameter_type == DataType::SecureObject {
434+
info!("Set secure parameter '{name}'");
435+
} else {
436+
info!("Set parameter '{name}' to '{value}'");
449437
}
450-
451-
info!("Set parameter '{name}' to '{value}'");
452-
self.context.parameters.insert(name.clone(), value.clone());
438+
self.context.parameters.insert(name.clone(), (value.clone(), constraint.parameter_type.clone()));
453439
}
454440
else {
455441
return Err(DscError::Validation(format!("Parameter '{name}' not defined in configuration")));
@@ -458,6 +444,38 @@ impl Configurator {
458444
Ok(())
459445
}
460446

447+
fn validate_parameter_type(name: &str, value: &Value, parameter_type: &DataType) -> Result<(), DscError> {
448+
match parameter_type {
449+
DataType::String | DataType::SecureString => {
450+
if !value.is_string() {
451+
return Err(DscError::Validation(format!("Parameter '{name}' is not a string")));
452+
}
453+
},
454+
DataType::Int => {
455+
if !value.is_i64() {
456+
return Err(DscError::Validation(format!("Parameter '{name}' is not an integer")));
457+
}
458+
},
459+
DataType::Bool => {
460+
if !value.is_boolean() {
461+
return Err(DscError::Validation(format!("Parameter '{name}' is not a boolean")));
462+
}
463+
},
464+
DataType::Array => {
465+
if !value.is_array() {
466+
return Err(DscError::Validation(format!("Parameter '{name}' is not an array")));
467+
}
468+
},
469+
DataType::Object | DataType::SecureObject => {
470+
if !value.is_object() {
471+
return Err(DscError::Validation(format!("Parameter '{name}' is not an object")));
472+
}
473+
},
474+
}
475+
476+
Ok(())
477+
}
478+
461479
fn validate_config(&mut self) -> Result<Configuration, DscError> {
462480
let config: Configuration = serde_json::from_str(self.config.as_str())?;
463481
check_security_context(&config.metadata)?;

dsc_lib/src/configure/parameters.rs

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,18 @@
44
use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66
use serde_json::Value;
7-
use std::{collections::HashMap, fmt::{self, Display, Formatter}};
7+
use std::collections::HashMap;
88

99
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
1010
pub struct Input {
1111
pub parameters: HashMap<String, Value>,
1212
}
1313

14-
pub struct SecureString {
15-
pub value: String,
16-
}
17-
18-
impl Display for SecureString {
19-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
20-
write!(f, "<SecureString>")
21-
}
22-
}
23-
24-
impl Drop for SecureString {
25-
fn drop(&mut self) {
26-
self.value.clear();
27-
}
28-
}
29-
30-
pub struct SecureObject {
31-
pub value: Value,
32-
}
33-
34-
impl Display for SecureObject {
35-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
36-
write!(f, "<SecureObject>")
37-
}
38-
}
39-
40-
impl Drop for SecureObject {
41-
fn drop(&mut self) {
42-
self.value = Value::Null;
43-
}
14+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
15+
#[serde(deny_unknown_fields, untagged)]
16+
pub enum SecureKind {
17+
#[serde(rename = "secureString")]
18+
SecureString(String),
19+
#[serde(rename = "secureObject")]
20+
SecureObject(Value),
4421
}

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use schemars::JsonSchema;
77
use serde::{Deserialize, Serialize};
88
use serde_json::Value;
99
use std::collections::HashMap;
10-
use tracing::{debug, trace};
1110

1211
use super::{command_resource, dscerror, invoke_result::{ExportResult, GetResult, ResourceTestResponse, SetResult, TestResult, ValidateResult}, resource_manifest::import_manifest};
1312

@@ -77,54 +76,6 @@ impl DscResource {
7776
manifest: None,
7877
}
7978
}
80-
81-
fn validate_input(&self, input: &str) -> Result<(), DscError> {
82-
debug!("Validating input for resource: {}", &self.type_name);
83-
if input.is_empty() {
84-
return Ok(());
85-
}
86-
let Some(manifest) = &self.manifest else {
87-
return Err(DscError::MissingManifest(self.type_name.clone()));
88-
};
89-
let resource_manifest = import_manifest(manifest.clone())?;
90-
91-
if resource_manifest.validate.is_some() {
92-
trace!("Using custom validation");
93-
let validation_result = match self.validate(input) {
94-
Ok(validation_result) => validation_result,
95-
Err(err) => {
96-
return Err(DscError::Validation(format!("Validation failed: {err}")));
97-
},
98-
};
99-
trace!("Validation result is valid: {}", validation_result.valid);
100-
if !validation_result.valid {
101-
return Err(DscError::Validation("Validation failed".to_string()));
102-
}
103-
}
104-
else {
105-
trace!("Using JSON schema validation");
106-
let Ok(schema) = self.schema() else {
107-
return Err(DscError::Validation("Schema not available".to_string()));
108-
};
109-
110-
let schema = serde_json::from_str::<Value>(&schema)?;
111-
112-
let Ok(compiled_schema) = jsonschema::JSONSchema::compile(&schema) else {
113-
return Err(DscError::Validation("Schema compilation failed".to_string()));
114-
};
115-
116-
let input = serde_json::from_str::<Value>(input)?;
117-
if let Err(err) = compiled_schema.validate(&input) {
118-
let mut error = format!("Resource '{}' failed validation: ", self.type_name);
119-
for e in err {
120-
error.push_str(&format!("\n{e} "));
121-
}
122-
return Err(DscError::Validation(error));
123-
};
124-
}
125-
126-
Ok(())
127-
}
12879
}
12980

13081
impl Default for DscResource {
@@ -201,7 +152,6 @@ pub trait Invoke {
201152

202153
impl Invoke for DscResource {
203154
fn get(&self, filter: &str) -> Result<GetResult, DscError> {
204-
self.validate_input(filter)?;
205155
match &self.implemented_as {
206156
ImplementedAs::Custom(_custom) => {
207157
Err(DscError::NotImplemented("get custom resources".to_string()))
@@ -217,7 +167,6 @@ impl Invoke for DscResource {
217167
}
218168

219169
fn set(&self, desired: &str, skip_test: bool) -> Result<SetResult, DscError> {
220-
self.validate_input(desired)?;
221170
match &self.implemented_as {
222171
ImplementedAs::Custom(_custom) => {
223172
Err(DscError::NotImplemented("set custom resources".to_string()))
@@ -233,7 +182,6 @@ impl Invoke for DscResource {
233182
}
234183

235184
fn test(&self, expected: &str) -> Result<TestResult, DscError> {
236-
self.validate_input(expected)?;
237185
match &self.implemented_as {
238186
ImplementedAs::Custom(_custom) => {
239187
Err(DscError::NotImplemented("test custom resources".to_string()))

dsc_lib/src/functions/parameters.rs

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

4+
use crate::configure::config_doc::DataType;
5+
use crate::configure::parameters::SecureKind;
46
use crate::DscError;
57
use crate::configure::context::Context;
68
use crate::functions::{AcceptedArgKind, Function};
@@ -28,7 +30,25 @@ impl Function for Parameters {
2830
if let Some(key) = args[0].as_str() {
2931
trace!("parameters key: {key}");
3032
if context.parameters.contains_key(key) {
31-
Ok(context.parameters[key].clone())
33+
let (value, data_type) = &context.parameters[key];
34+
35+
// if secureString or secureObject types, we keep it as JSON object
36+
match data_type {
37+
DataType::SecureString => {
38+
let Some(value) = value.as_str() else {
39+
return Err(DscError::Parser(format!("Parameter '{key}' is not a string")));
40+
};
41+
let secure_string = SecureKind::SecureString(value.to_string());
42+
Ok(serde_json::to_value(secure_string)?)
43+
},
44+
DataType::SecureObject => {
45+
let secure_object = SecureKind::SecureObject(value.clone());
46+
Ok(serde_json::to_value(secure_object)?)
47+
},
48+
_ => {
49+
Ok(value.clone())
50+
}
51+
}
3252
}
3353
else {
3454
Err(DscError::Parser(format!("Parameter '{key}' not found in context")))

tools/dsctest/src/args.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Args {
2121
pub enum SubCommand {
2222
#[clap(name = "echo", about = "Return the input")]
2323
Echo {
24-
#[clap(name = "input", short, long, help = "The input to the echo command")]
24+
#[clap(name = "input", short, long, help = "The input to the echo command as JSON")]
2525
input: String,
2626
},
2727

@@ -33,7 +33,7 @@ pub enum SubCommand {
3333

3434
#[clap(name = "sleep", about = "Sleep for a specified number of seconds")]
3535
Sleep {
36-
#[clap(name = "input", short, long, help = "The input to the sleep command")]
36+
#[clap(name = "input", short, long, help = "The input to the sleep command as JSON")]
3737
input: String,
3838
},
3939
}

tools/dsctest/src/echo.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66
use serde_json::Value;
77

8+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
9+
pub struct SecureString {
10+
#[serde(rename = "secureString")]
11+
pub value: String,
12+
}
13+
14+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
15+
pub struct SecureObject {
16+
#[serde(rename = "secureObject")]
17+
pub value: Value,
18+
}
19+
820
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
921
#[serde(untagged)]
1022
pub enum Output {
@@ -14,6 +26,12 @@ pub enum Output {
1426
Bool(bool),
1527
#[serde(rename = "number")]
1628
Number(i64),
29+
#[serde(rename = "object")]
30+
Object(Value),
31+
#[serde(rename = "secureObject")]
32+
SecureObject(Value),
33+
#[serde(rename = "secureString")]
34+
SecureString(String),
1735
#[serde(rename = "string")]
1836
String(String),
1937
}

0 commit comments

Comments
 (0)