Skip to content

Commit 5325e53

Browse files
authored
Merge pull request #1128 from Gijsreyn/gh-1115/main/support-copy-expression-validation
Add support to validate expressions on copy names
2 parents aa4db08 + 84d8e17 commit 5325e53

File tree

3 files changed

+84
-5
lines changed

3 files changed

+84
-5
lines changed

dsc/tests/dsc_copy.tests.ps1

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,56 @@ resources:
182182
$LASTEXITCODE | Should -Be 2 -Because ((Get-Content $testdrive/error.log) | Out-String)
183183
(Get-Content $testdrive/error.log -Raw) | Should -Match "Copy name result is not a string"
184184
}
185+
186+
It 'Copy works with parameters in resource name' {
187+
$configYaml = @'
188+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
189+
parameters:
190+
prefix:
191+
type: string
192+
defaultValue: srv
193+
resources:
194+
- name: "[concat(parameters('prefix'), '-', string(copyIndex()))]"
195+
copy:
196+
name: testLoop
197+
count: 3
198+
type: Microsoft.DSC.Debug/Echo
199+
properties:
200+
output: Hello
201+
'@
202+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
203+
$LASTEXITCODE | Should -Be 0 -Because ((Get-Content $testdrive/error.log) | Out-String)
204+
$out.results.Count | Should -Be 3
205+
$out.results[0].name | Should -Be 'srv-0'
206+
$out.results[0].result.actualState.output | Should -Be 'Hello'
207+
$out.results[1].name | Should -Be 'srv-1'
208+
$out.results[1].result.actualState.output | Should -Be 'Hello'
209+
$out.results[2].name | Should -Be 'srv-2'
210+
$out.results[2].result.actualState.output | Should -Be 'Hello'
211+
}
212+
213+
It 'Copy works with parameters in properties' {
214+
$configYaml = @'
215+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
216+
parameters:
217+
environment:
218+
type: string
219+
defaultValue: test
220+
resources:
221+
- name: "[format('Server-{0}', copyIndex())]"
222+
copy:
223+
name: testLoop
224+
count: 2
225+
type: Microsoft.DSC.Debug/Echo
226+
properties:
227+
output: "[concat('Environment: ', parameters('environment'))]"
228+
'@
229+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
230+
$LASTEXITCODE | Should -Be 0 -Because ((Get-Content $testdrive/error.log) | Out-String)
231+
$out.results.Count | Should -Be 2
232+
$out.results[0].name | Should -Be 'Server-0'
233+
$out.results[0].result.actualState.output | Should -Be 'Environment: test'
234+
$out.results[1].name | Should -Be 'Server-1'
235+
$out.results[1].result.actualState.output | Should -Be 'Environment: test'
236+
}
185237
}

dsc_lib/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ metadataMicrosoftDscIgnored = "Resource returned '_metadata' property 'Microsoft
7272
metadataNotObject = "Resource returned '_metadata' property which is not an object"
7373
metadataRestartRequiredInvalid = "Resource returned '_metadata' property '_restartRequired' which contains invalid value: %{value}"
7474
schemaExcludesMetadata = "Will not add '_metadata' to properties because resource schema does not support it"
75+
validateCopy = "Validating copy for resource '%{name}' with count %{count}"
7576
unrollingCopy = "Unrolling copy for resource '%{name}' with count %{count}"
7677
copyModeNotSupported = "Copy mode is not supported"
7778
copyBatchSizeNotSupported = "Copy batch size is not supported"

dsc_lib/src/configure/mod.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ impl Configurator {
341341
///
342342
/// This function will return an error if the underlying resource fails.
343343
pub fn invoke_get(&mut self) -> Result<ConfigurationGetResult, DscError> {
344+
self.unroll_copy_loops()?;
345+
344346
let mut result = ConfigurationGetResult::new();
345347
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
346348
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
@@ -421,6 +423,8 @@ impl Configurator {
421423
/// This function will return an error if the underlying resource fails.
422424
#[allow(clippy::too_many_lines)]
423425
pub fn invoke_set(&mut self, skip_test: bool) -> Result<ConfigurationSetResult, DscError> {
426+
self.unroll_copy_loops()?;
427+
424428
let mut result = ConfigurationSetResult::new();
425429
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
426430
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
@@ -575,6 +579,8 @@ impl Configurator {
575579
///
576580
/// This function will return an error if the underlying resource fails.
577581
pub fn invoke_test(&mut self) -> Result<ConfigurationTestResult, DscError> {
582+
self.unroll_copy_loops()?;
583+
578584
let mut result = ConfigurationTestResult::new();
579585
let resources = get_resource_invocation_order(&self.config, &mut self.statement_parser, &self.context)?;
580586
let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?;
@@ -651,6 +657,8 @@ impl Configurator {
651657
///
652658
/// This function will return an error if the underlying resource fails.
653659
pub fn invoke_export(&mut self) -> Result<ConfigurationExportResult, DscError> {
660+
self.unroll_copy_loops()?;
661+
654662
let mut result = ConfigurationExportResult::new();
655663
let mut conf = config_doc::Configuration::new();
656664
conf.metadata.clone_from(&self.config.metadata);
@@ -874,7 +882,7 @@ impl Configurator {
874882
}
875883

876884
fn validate_config(&mut self) -> Result<(), DscError> {
877-
let mut config: Configuration = serde_json::from_str(self.json.as_str())?;
885+
let config: Configuration = serde_json::from_str(self.json.as_str())?;
878886
check_security_context(config.metadata.as_ref())?;
879887

880888
// Perform discovery of resources used in config
@@ -886,15 +894,33 @@ impl Configurator {
886894
if !discovery_filter.contains(&filter) {
887895
discovery_filter.push(filter);
888896
}
889-
// if the resource contains `Copy`, we need to unroll
897+
// defer actual unrolling until parameters are available
890898
if let Some(copy) = &resource.copy {
891-
debug!("{}", t!("configure.mod.unrollingCopy", name = &copy.name, count = copy.count));
899+
debug!("{}", t!("configure.mod.validateCopy", name = &copy.name, count = copy.count));
892900
if copy.mode.is_some() {
893901
return Err(DscError::Validation(t!("configure.mod.copyModeNotSupported").to_string()));
894902
}
895903
if copy.batch_size.is_some() {
896904
return Err(DscError::Validation(t!("configure.mod.copyBatchSizeNotSupported").to_string()));
897905
}
906+
}
907+
}
908+
909+
self.discovery.find_resources(&discovery_filter, self.progress_format);
910+
self.config = config;
911+
Ok(())
912+
}
913+
914+
/// Unroll copy loops in the configuration.
915+
/// This method should be called after parameters have been set in the context.
916+
fn unroll_copy_loops(&mut self) -> Result<(), DscError> {
917+
let mut config = self.config.clone();
918+
let config_copy = config.clone();
919+
920+
for resource in config_copy.resources {
921+
// if the resource contains `Copy`, unroll it
922+
if let Some(copy) = &resource.copy {
923+
debug!("{}", t!("configure.mod.unrollingCopy", name = &copy.name, count = copy.count));
898924
self.context.process_mode = ProcessMode::Copy;
899925
self.context.copy_current_loop_name.clone_from(&copy.name);
900926
let mut copy_resources = Vec::<Resource>::new();
@@ -905,6 +931,7 @@ impl Configurator {
905931
return Err(DscError::Parser(t!("configure.mod.copyNameResultNotString").to_string()))
906932
};
907933
new_resource.name = new_name.to_string();
934+
908935
new_resource.copy = None;
909936
copy_resources.push(new_resource);
910937
}
@@ -914,8 +941,7 @@ impl Configurator {
914941
config.resources.extend(copy_resources);
915942
}
916943
}
917-
918-
self.discovery.find_resources(&discovery_filter, self.progress_format);
944+
919945
self.config = config;
920946
Ok(())
921947
}

0 commit comments

Comments
 (0)