Skip to content

Commit 4410b23

Browse files
committed
Add variables() function and support in configuration
1 parent 3049f4a commit 4410b23

File tree

8 files changed

+128
-9
lines changed

8 files changed

+128
-9
lines changed

dsc/examples/variables.dsc.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
2+
parameters:
3+
myParameter:
4+
type: string
5+
# the use of `concat()` here is just an example of using an expression for a defaultValue
6+
defaultValue: "[concat('world','!')]"
7+
variables:
8+
myOutput: "[concat('Hello ', parameters('myParameter'))]"
9+
myObject:
10+
test: baz
11+
resources:
12+
- name: test
13+
type: Test/Echo
14+
properties:
15+
output: "[concat('myOutput is: ', variables('myOutput'), ', myObject is: ', variables('myObject').test)]"

dsc/src/subcommand.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option<String>, stdin:
270270
}
271271
};
272272

273-
if let Err(err) = configurator.set_parameters(&parameters) {
273+
if let Err(err) = configurator.set_context(&parameters) {
274274
error!("Error: Parameter input failure: {err}");
275275
exit(EXIT_INVALID_INPUT);
276276
}

dsc_lib/src/configure/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct Context {
1414
pub outputs: HashMap<String, Value>, // this is used by the `reference()` function to retrieve output
1515
pub parameters: HashMap<String, (Value, DataType)>,
1616
pub security_context: SecurityContextKind,
17-
_variables: HashMap<String, Value>,
17+
pub variables: HashMap<String, Value>,
1818
pub start_datetime: DateTime<Local>,
1919
}
2020

@@ -29,7 +29,7 @@ impl Context {
2929
SecurityContext::Admin => SecurityContextKind::Elevated,
3030
SecurityContext::User => SecurityContextKind::Restricted,
3131
},
32-
_variables: HashMap::new(),
32+
variables: HashMap::new(),
3333
start_datetime: chrono::Local::now(),
3434
}
3535
}

dsc_lib/src/configure/mod.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ impl Configurator {
479479
Ok(result)
480480
}
481481

482-
/// Set the parameters context for the configuration.
482+
/// Set the parameters and variables context for the configuration.
483483
///
484484
/// # Arguments
485485
///
@@ -488,12 +488,18 @@ impl Configurator {
488488
/// # Errors
489489
///
490490
/// This function will return an error if the parameters are invalid.
491-
pub fn set_parameters(&mut self, parameters_input: &Option<Value>) -> Result<(), DscError> {
492-
// set default parameters first
491+
pub fn set_context(&mut self, parameters_input: &Option<Value>) -> Result<(), DscError> {
493492
let config = serde_json::from_str::<Configuration>(self.json.as_str())?;
493+
self.set_parameters(parameters_input, &config)?;
494+
self.set_variables(&config)?;
495+
Ok(())
496+
}
497+
498+
fn set_parameters(&mut self, parameters_input: &Option<Value>, config: &Configuration) -> Result<(), DscError> {
499+
// set default parameters first
494500
let Some(parameters) = &config.parameters else {
495501
if parameters_input.is_none() {
496-
debug!("No parameters defined in configuration and no parameters input");
502+
info!("No parameters defined in configuration and no parameters input");
497503
return Ok(());
498504
}
499505
return Err(DscError::Validation("No parameters defined in configuration".to_string()));
@@ -513,7 +519,7 @@ impl Configurator {
513519
} else {
514520
default_value.clone()
515521
};
516-
Configurator::validate_parameter_type(name, &value, &parameter.parameter_type)?;
522+
Configurator::validate_parameter_type(&name, &value, &parameter.parameter_type)?;
517523
self.context.parameters.insert(name.clone(), (value, parameter.parameter_type.clone()));
518524
}
519525
}
@@ -543,6 +549,11 @@ impl Configurator {
543549
} else {
544550
info!("Set parameter '{name}' to '{value}'");
545551
}
552+
553+
if self.context.parameters.contains_key(&name) {
554+
return Err(DscError::Validation(format!("Parameter '{name}' defined more than once")));
555+
}
556+
546557
self.context.parameters.insert(name.clone(), (value.clone(), constraint.parameter_type.clone()));
547558
// also update the configuration with the parameter value
548559
if let Some(parameters) = &mut self.config.parameters {
@@ -558,6 +569,29 @@ impl Configurator {
558569
Ok(())
559570
}
560571

572+
pub fn set_variables(&mut self, config: &Configuration) -> Result<(), DscError> {
573+
let Some(variables) = &config.variables else {
574+
debug!("No variables defined in configuration");
575+
return Ok(());
576+
};
577+
578+
for (name, value) in variables {
579+
let new_value = if let Some(string) = value.as_str() {
580+
self.statement_parser.parse_and_execute(string, &self.context)?
581+
}
582+
else {
583+
value.clone()
584+
};
585+
info!("Set variable '{name}' to '{new_value}'");
586+
if self.context.variables.contains_key(name) {
587+
return Err(DscError::Validation(format!("Variable '{name}' defined mnore than once")));
588+
}
589+
590+
self.context.variables.insert(name.to_string(), new_value);
591+
}
592+
Ok(())
593+
}
594+
561595
fn get_result_metadata(&self, operation: Operation) -> Metadata {
562596
let end_datetime = chrono::Local::now();
563597
Metadata {

dsc_lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod parameters;
2222
pub mod reference;
2323
pub mod resource_id;
2424
pub mod sub;
25+
pub mod variables;
2526

2627
/// The kind of argument that a function accepts.
2728
#[derive(Debug, PartialEq)]
@@ -78,6 +79,7 @@ impl FunctionDispatcher {
7879
functions.insert("reference".to_string(), Box::new(reference::Reference{}));
7980
functions.insert("resourceId".to_string(), Box::new(resource_id::ResourceId{}));
8081
functions.insert("sub".to_string(), Box::new(sub::Sub{}));
82+
functions.insert("variables".to_string(), Box::new(variables::Variables{}));
8183
Self {
8284
functions,
8385
}

dsc_lib/src/functions/variables.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function};
7+
use serde_json::Value;
8+
use tracing::debug;
9+
10+
#[derive(Debug, Default)]
11+
pub struct Variables {}
12+
13+
impl Function for Variables {
14+
fn min_args(&self) -> usize {
15+
1
16+
}
17+
18+
fn max_args(&self) -> usize {
19+
1
20+
}
21+
22+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
23+
vec![AcceptedArgKind::String]
24+
}
25+
26+
fn invoke(&self, args: &[Value], context: &Context) -> Result<Value, DscError> {
27+
debug!("variables function");
28+
if let Some(key) = args[0].as_str() {
29+
if context.variables.contains_key(key) {
30+
Ok(context.variables[key].clone())
31+
} else {
32+
Err(DscError::Parser(format!("Variable '{key}' does not exist or has not been initialized yet")))
33+
}
34+
} else {
35+
Err(DscError::Parser("Invalid argument".to_string()))
36+
}
37+
}
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use crate::configure::context::Context;
43+
use crate::parser::Statement;
44+
45+
#[test]
46+
fn valid_variable() {
47+
let mut parser = Statement::new().unwrap();
48+
let mut context = Context::new();
49+
context.variables.insert("hello".to_string(), "world".into());
50+
let result = parser.parse_and_execute("[variables('hello')]", &context).unwrap();
51+
assert_eq!(result, "world");
52+
}
53+
54+
#[test]
55+
fn invalid_resourceid() {
56+
let mut parser = Statement::new().unwrap();
57+
let result = parser.parse_and_execute("[variables('foo')]", &Context::new());
58+
assert!(result.is_err());
59+
}
60+
}

dsc_lib/src/parser/functions.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use serde_json::{Number, Value};
5+
use tracing::debug;
56
use tree_sitter::Node;
67

78
use crate::DscError;
@@ -51,8 +52,10 @@ impl Function {
5152
return Err(DscError::Parser("Function name node not found".to_string()));
5253
};
5354
let args = convert_args_node(statement_bytes, &function_args)?;
55+
let name = name.utf8_text(statement_bytes)?;
56+
debug!("Function name: {0}", name);
5457
Ok(Function{
55-
name: name.utf8_text(statement_bytes)?.to_string(),
58+
name: name.to_string(),
5659
args})
5760
}
5861

@@ -68,10 +71,12 @@ impl Function {
6871
for arg in args {
6972
match arg {
7073
FunctionArg::Expression(expression) => {
74+
debug!("Arg is expression");
7175
let value = expression.invoke(function_dispatcher, context)?;
7276
resolved_args.push(value.clone());
7377
},
7478
FunctionArg::Value(value) => {
79+
debug!("Arg is value: '{:?}'", value);
7580
resolved_args.push(value.clone());
7681
}
7782
}

dsc_lib/src/parser/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,19 @@ impl Statement {
6969
let Ok(value) = child_node.utf8_text(statement_bytes) else {
7070
return Err(DscError::Parser("Error parsing string literal".to_string()));
7171
};
72+
debug!("Parsing string literal: {0}", value.to_string());
7273
Ok(Value::String(value.to_string()))
7374
},
7475
"escapedStringLiteral" => {
7576
// need to remove the first character: [[ => [
7677
let Ok(value) = child_node.utf8_text(statement_bytes) else {
7778
return Err(DscError::Parser("Error parsing escaped string literal".to_string()));
7879
};
80+
debug!("Parsing escaped string literal: {0}", value[1..].to_string());
7981
Ok(Value::String(value[1..].to_string()))
8082
},
8183
"expression" => {
84+
debug!("Parsing expression");
8285
let expression = Expression::new(statement_bytes, &child_node)?;
8386
Ok(expression.invoke(&self.function_dispatcher, context)?)
8487
},

0 commit comments

Comments
 (0)