Skip to content

Commit aa3ffc0

Browse files
authored
Merge pull request #1179 from SteveL-MSFT/int-expression
Fix allowing `copy` count to accept an expression
2 parents a771ed3 + 7e0fa18 commit aa3ffc0

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

dsc/tests/dsc_copy.tests.ps1

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,33 @@ resources:
234234
$out.results[1].name | Should -Be 'Server-1'
235235
$out.results[1].result.actualState.output | Should -Be 'Environment: test'
236236
}
237+
238+
It 'Copy count using expression' {
239+
$configYaml = @'
240+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
241+
parameters:
242+
serverCount:
243+
type: int
244+
defaultValue: 4
245+
resources:
246+
- name: "[format('Server-{0}', copyIndex())]"
247+
copy:
248+
name: testLoop
249+
count: "[parameters('serverCount')]"
250+
type: Microsoft.DSC.Debug/Echo
251+
properties:
252+
output: Hello
253+
'@
254+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
255+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $testdrive/error.log -Raw | Out-String)
256+
$out.results.Count | Should -Be 4
257+
$out.results[0].name | Should -Be 'Server-0'
258+
$out.results[0].result.actualState.output | Should -Be 'Hello'
259+
$out.results[1].name | Should -Be 'Server-1'
260+
$out.results[1].result.actualState.output | Should -Be 'Hello'
261+
$out.results[2].name | Should -Be 'Server-2'
262+
$out.results[2].result.actualState.output | Should -Be 'Hello'
263+
$out.results[3].name | Should -Be 'Server-3'
264+
$out.results[3].result.actualState.output | Should -Be 'Hello'
265+
}
237266
}

lib/dsc-lib/locales/en-us.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ nameResultNotString = "Resource name result is not a string"
7979
circularDependency = "Circular dependency or unresolvable parameter references detected in parameters: %{parameters}"
8080
userFunctionAlreadyDefined = "User function '%{name}' in namespace '%{namespace}' is already defined"
8181
addingUserFunction = "Adding user function '%{name}'"
82+
copyCountResultNotInteger = "Copy count result is not an integer: %{expression}"
8283

8384
[discovery.commandDiscovery]
8485
couldNotReadSetting = "Could not read 'resourcePath' setting"
@@ -444,6 +445,10 @@ description = "Retrieves parameters from the configuration"
444445
invoked = "parameters function"
445446
traceKey = "parameters key: %{key}"
446447
keyNotString = "Parameter '%{key}' is not a string"
448+
keyNotInt = "Parameter '%{key}' is not an integer"
449+
keyNotBool = "Parameter '%{key}' is not a boolean"
450+
keyNotObject = "Parameter '%{key}' is not an object"
451+
keyNotArray = "Parameter '%{key}' is not an array"
447452
keyNotFound = "Parameter '%{key}' not found in context"
448453

449454
[functions.path]

lib/dsc-lib/src/configure/config_doc.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,31 @@ pub enum CopyMode {
211211
Parallel,
212212
}
213213

214+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
215+
#[serde(untagged)]
216+
pub enum IntOrExpression {
217+
Int(i64),
218+
Expression(String),
219+
}
220+
221+
impl Display for IntOrExpression {
222+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223+
match self {
224+
IntOrExpression::Int(i) => write!(f, "{i}"),
225+
IntOrExpression::Expression(s) => write!(f, "{s}"),
226+
}
227+
}
228+
}
229+
214230
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
215231
#[serde(deny_unknown_fields)]
216232
pub struct Copy {
217233
pub name: String,
218-
pub count: i64,
234+
pub count: IntOrExpression,
219235
#[serde(skip_serializing_if = "Option::is_none")]
220236
pub mode: Option<CopyMode>,
221237
#[serde(skip_serializing_if = "Option::is_none", rename = "batchSize")]
222-
pub batch_size: Option<i64>,
238+
pub batch_size: Option<IntOrExpression>,
223239
}
224240

225241
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]

lib/dsc-lib/src/configure/mod.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::configure::config_doc::{ExecutionKind, Metadata, Resource, Parameter};
55
use crate::configure::context::{Context, ProcessMode};
6-
use crate::configure::{config_doc::RestartRequired, parameters::Input};
6+
use crate::configure::{config_doc::{IntOrExpression, RestartRequired}, parameters::Input};
77
use crate::discovery::discovery_trait::DiscoveryFilter;
88
use crate::dscerror::DscError;
99
use crate::dscresources::{
@@ -779,7 +779,7 @@ impl Configurator {
779779
if let Some(parameters_input) = parameters_input {
780780
trace!("parameters_input: {parameters_input}");
781781
let input_parameters: HashMap<String, Value> = serde_json::from_value::<Input>(parameters_input.clone())?.parameters;
782-
782+
783783
for (name, value) in input_parameters {
784784
if let Some(constraint) = parameters.get(&name) {
785785
debug!("Validating parameter '{name}'");
@@ -818,7 +818,7 @@ impl Configurator {
818818

819819
while !unresolved_parameters.is_empty() {
820820
let mut resolved_in_this_pass = Vec::new();
821-
821+
822822
for (name, parameter) in &unresolved_parameters {
823823
debug!("{}", t!("configure.mod.processingParameter", name = name));
824824
if let Some(default_value) = &parameter.default_value {
@@ -962,7 +962,16 @@ impl Configurator {
962962
self.context.process_mode = ProcessMode::Copy;
963963
self.context.copy_current_loop_name.clone_from(&copy.name);
964964
let mut copy_resources = Vec::<Resource>::new();
965-
for i in 0..copy.count {
965+
let count: i64 = match &copy.count {
966+
IntOrExpression::Int(i) => *i,
967+
IntOrExpression::Expression(e) => {
968+
let Value::Number(n) = self.statement_parser.parse_and_execute(e, &self.context)? else {
969+
return Err(DscError::Parser(t!("configure.mod.copyCountResultNotInteger", expression = e).to_string()))
970+
};
971+
n.as_i64().ok_or_else(|| DscError::Parser(t!("configure.mod.copyCountResultNotInteger", expression = e).to_string()))?
972+
},
973+
};
974+
for i in 0..count {
966975
self.context.copy.insert(copy.name.clone(), i);
967976
let mut new_resource = resource.clone();
968977
let Value::String(new_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else {

lib/dsc-lib/src/functions/parameters.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,41 @@ impl Function for Parameters {
4343
let secure_string = SecureString {
4444
secure_string: value.to_string(),
4545
};
46-
Ok(serde_json::to_value(secure_string)?)
46+
return Ok(serde_json::to_value(secure_string)?);
4747
},
4848
DataType::SecureObject => {
4949
let secure_object = SecureObject {
5050
secure_object: value.clone(),
5151
};
52-
Ok(serde_json::to_value(secure_object)?)
52+
return Ok(serde_json::to_value(secure_object)?);
53+
},
54+
DataType::String => {
55+
let Some(_value) = value.as_str() else {
56+
return Err(DscError::Parser(t!("functions.parameters.keyNotString", key = key).to_string()));
57+
};
58+
},
59+
DataType::Int => {
60+
let Some(_value) = value.as_i64() else {
61+
return Err(DscError::Parser(t!("functions.parameters.keyNotInt", key = key).to_string()));
62+
};
63+
},
64+
DataType::Bool => {
65+
let Some(_value) = value.as_bool() else {
66+
return Err(DscError::Parser(t!("functions.parameters.keyNotBool", key = key).to_string()));
67+
};
68+
},
69+
DataType::Object => {
70+
let Some(_value) = value.as_object() else {
71+
return Err(DscError::Parser(t!("functions.parameters.keyNotObject", key = key).to_string()));
72+
};
73+
},
74+
DataType::Array => {
75+
let Some(_value) = value.as_array() else {
76+
return Err(DscError::Parser(t!("functions.parameters.keyNotArray", key = key).to_string()));
77+
};
5378
},
54-
_ => {
55-
Ok(value.clone())
56-
}
5779
}
80+
Ok(value.clone())
5881
}
5982
else {
6083
Err(DscError::Parser(t!("functions.parameters.keyNotFound", key = key).to_string()))

0 commit comments

Comments
 (0)