From b7ccb9b125920eeb4619a78b79647176903839e8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 17 Sep 2025 19:15:19 +0200 Subject: [PATCH 01/18] Add copy() and copyIndex() function reference documentation --- .../schemas/config/functions/copy.md | 220 ++++++++++++++++ .../schemas/config/functions/copyIndex.md | 240 ++++++++++++++++++ .../schemas/config/functions/overview.md | 2 + 3 files changed, 462 insertions(+) create mode 100644 docs/reference/schemas/config/functions/copy.md create mode 100644 docs/reference/schemas/config/functions/copyIndex.md diff --git a/docs/reference/schemas/config/functions/copy.md b/docs/reference/schemas/config/functions/copy.md new file mode 100644 index 000000000..206d8ab2d --- /dev/null +++ b/docs/reference/schemas/config/functions/copy.md @@ -0,0 +1,220 @@ +--- +description: Reference for the 'copy' DSC configuration document resource loop +ms.date: 02/28/2025 +ms.topic: reference +title: copy +--- + +# copy + +## Synopsis + +Defines a loop to create multiple instances of a resource. + +## Syntax + +```Syntax +copy: + name: + count: +``` + +## Description + +The `copy` property enables you to create multiple instances of a resource in a +DSC configuration. This is the equivalent implementation of the copy +functionality from Azure Resource Manager (ARM) templates, but without support +for variables and properties, which will be added in future releases. + +When you use `copy` on a resource, DSC creates multiple instances of that +resource based on the specified count. You can use the [`copyIndex()`][01] +function within the resource definition to access the current iteration index +and create unique names or property values for each instance. + +> [!NOTE] +> The `mode` and `batchSize` properties are not currently supported and will +> result in an error if used. + +## Examples + +### Example 1 - Create multiple Echo resources + +This example demonstrates the basic usage of `copy` to create three instances +of a Debug Echo resource. + +```yaml +# copy.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Echo-{0}', copyIndex())]" + copy: + name: echoLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Hello DSC" +``` + +```bash +dsc config get --file copy.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Echo-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +- name: Echo-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +- name: Echo-2 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +messages: [] +hadErrors: false +``` + +### Example 2 - Using copyIndex with offset + +This example demonstrates using [`copyIndex()`][01] with an offset to start +numbering from a different value. + +```yaml +# copy.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Service-{0}', copyIndex(100))]" + copy: + name: serviceLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Service instance" +``` + +```bash +dsc config get --file copy.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Service-100 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +- name: Service-101 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +- name: Service-102 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +messages: [] +hadErrors: false +``` + +### Example 3 - Using named loop references + +This example shows how to reference a specific loop by name when using +[`copyIndex()`][01]. + +```yaml +# copy.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Resource-{0}', copyIndex('mainLoop'))]" + copy: + name: mainLoop + count: 2 + type: Microsoft.DSC.Debug/Echo + properties: + output: "From main loop" +``` + +```bash +dsc config get --file copy.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Resource-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: From main loop +- name: Resource-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: From main loop +messages: [] +hadErrors: false +``` + +## Properties + +### name + +The name of the copy loop. This name can be used with the [`copyIndex()`][01] +function to reference the current iteration index of this specific loop. + +```yaml +Type: string +Required: true +``` + +### count + +The number of iterations to perform. Must be a non-negative integer. If set to +0, no instances of the resource are created. + +```yaml +Type: integer +Required: true +Minimum: 0 +``` + +### mode + +> [!WARNING] +> The `mode` property is not currently supported and will result in an error +> if used. + +This property is reserved for future implementation to specify whether resources +should be created serially or in parallel. + +### batchSize + +> [!WARNING] +> The `batchSize` property is not currently supported and will result in an +> error if used. + +This property is reserved for future implementation to specify how many +resources to create in each batch when using parallel mode. + +## Limitations + +The current implementation has the following limitations: + +- **Variables and properties**: Copy loops for variables and properties are not + yet supported. +- **Mode control**: The `mode` property (serial/parallel) is not implemented. +- **Batch processing**: The `batchSize` property is not implemented. +- **Name expressions**: The resource name expression must evaluate to a string. + +## Related Functions + +- [`copyIndex()`][01] - Returns the current iteration index of a copy loop. + + +[01]: ./copyIndex.md diff --git a/docs/reference/schemas/config/functions/copyIndex.md b/docs/reference/schemas/config/functions/copyIndex.md new file mode 100644 index 000000000..6fd0062c4 --- /dev/null +++ b/docs/reference/schemas/config/functions/copyIndex.md @@ -0,0 +1,240 @@ +--- +description: Reference for the 'copyIndex' DSC configuration document function +ms.date: 02/28/2025 +ms.topic: reference +title: copyIndex +--- + +# copyIndex + +## Synopsis + +Returns the current iteration index of a copy loop. + +## Syntax + +```Syntax +copyIndex() +copyIndex() +copyIndex('') +copyIndex('', ) +``` + +## Description + +The `copyIndex()` function returns the current iteration index of a copy loop. +This function can only be used within resources that have a `copy` property +defined. The function is necessary for creating unique names and property +values for each instance created by the copy loop. + +The index starts at 0 for the first iteration and increments by 1 for each +subsequent iteration. You can add an offset to shift the starting number, or +reference a specific loop by name when multiple copy loops are present. + +## Examples + +### Example 1 - Basic copyIndex usage + +This example shows the basic usage of `copyIndex()` to create unique resource +names. + +```yaml +# copyIndex.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Resource-{0}', copyIndex())]" + copy: + name: basicLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Hello DSC" +``` + +```bash +dsc config get --file copyIndex.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Resource-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +- name: Resource-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +- name: Resource-2 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +messages: [] +hadErrors: false +``` + +### Example 2 - Using copyIndex with offset + +This example demonstrates using an offset to start numbering from a different +value. + +```yaml +# copyIndex.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Server-{0}', copyIndex(10))]" + copy: + name: serverLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Server instance starting from 10 till 12" +``` + +```bash +dsc config get --file copyIndex.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Server-10 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +- name: Server-11 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +- name: Server-12 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +messages: [] +hadErrors: false +``` + +### Example 3 - Using copyIndex with loop name + +This example shows how to reference a specific loop by name. + +```yaml +# copyIndex.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Item-{0}', copyIndex('itemLoop'))]" + copy: + name: itemLoop + count: 1 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Item from loop" +``` + +```bash +dsc config get --file copyIndex.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Item-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Item from loop" +messages: [] +hadErrors: false +``` + +### Example 4 - Using copyIndex with loop name and offset + +This example combines both loop name and offset parameters. + +```yaml +# copyIndex.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Database-{0}', copyIndex('dbLoop', 100))]" + copy: + name: dbLoop + count: 2 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Database instance" +``` + +```bash +dsc config get --file copyIndex.example.4.dsc.config.yaml +``` + +```yaml +results: +- name: Database-100 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Database instance" +- name: Database-101 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Database instance" +messages: [] +hadErrors: false +``` + +## Parameters + +### offset + +An optional integer offset to add to the current index. The offset must be a +non-negative number. + +```yaml +Type: integer +Required: false +Minimum: 0 +``` + +### loopName + +An optional string specifying the name of the copy loop to reference. This is +useful when you have multiple copy loops and need to reference a specific one. + +```yaml +Type: string +Required: false +``` + +## Output + +The `copyIndex()` function returns an integer representing the current iteration +index, optionally adjusted by the offset. + +```yaml +Type: integer +``` + +## Error Conditions + +The `copyIndex()` function will return an error in the following situations: + +- **Used outside copy loop**: The function can only be used within resources + that have a `copy` property defined. +- **Negative offset**: The offset parameter must be non-negative. +- **Invalid loop name**: If a loop name is specified but no loop with that + name exists. +- **Invalid arguments**: If the arguments provided are not of the expected + types. + +## Related Properties + +- [`copy`][01] - Defines a loop to create multiple instances of a resource. + + +[01]: ./copy.md diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index dd8341359..236b8a6db 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -603,6 +603,7 @@ The following list of functions operate on integer values or arrays of integer v The following list of functions operate on resource instances: +- [copyIndex()][copyIndex] - Return the current iteration index of a copy loop. - [reference()][reference] - Return the result data for another resource instance. - [resourceId()][resourceId] - Return the ID of another resource instance to reference or depend on. @@ -631,6 +632,7 @@ The following list of functions create or convert values of a given type: [add]: ./add.md [base64]: ./base64.md [concat]: ./concat.md +[copyIndex]: ./copyIndex.md [createArray]: ./createArray.md [div]: ./div.md [envvar]: ./envvar.md From e3e5dd7be129dd1bacdeb423c431289201c904c9 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:47:13 +0200 Subject: [PATCH 02/18] Add support to validate expressions for names --- dsc/src/subcommand.rs | 6 + dsc/tests/dsc_expressions.tests.ps1 | 283 ++++++++++++++++++++++++++++ dsc_lib/locales/en-us.toml | 1 + dsc_lib/src/configure/mod.rs | 22 +++ 4 files changed, 312 insertions(+) diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index bf559cf65..2f8e3fd97 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -381,6 +381,12 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame exit(EXIT_INVALID_INPUT); } + // process resource names after parameters are available + if let Err(err) = configurator.process_resource_names() { + error!("Error processing resource names: {err}"); + exit(EXIT_DSC_ERROR); + } + match subcommand { ConfigSubCommand::Get { output_format, .. } => { config_get(&mut configurator, output_format.as_ref(), as_group); diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index bba61abd8..61d7ec165 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -242,4 +242,287 @@ resources: $log | Should -BeLike "*ERROR* Arguments must be of the same type*" } +g + Context 'Resource name expression evaluation' { + It 'Simple parameter expression in resource name: ' -TestCases @( + @{ expression = "[parameters('resourceName')]"; paramValue = 'TestResource'; expected = 'TestResource' } + @{ expression = "[parameters('serviceName')]"; paramValue = 'MyService'; expected = 'MyService' } + ) { + param($expression, $paramValue, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + resourceName: + type: string + defaultValue: $paramValue + serviceName: + type: string + defaultValue: $paramValue +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = .\dsc\target\debug\dsc.exe config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Concat function in resource name: ' -TestCases @( + @{ expression = "[concat('prefix-', parameters('name'))]"; paramValue = 'test'; expected = 'prefix-test' } + @{ expression = "[concat(parameters('prefix'), '-', parameters('suffix'))]"; expected = 'start-end' } + @{ expression = "[concat('Resource-', string(parameters('index')))]"; expected = 'Resource-42' } + ) { + param($expression, $paramValue, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + name: + type: string + defaultValue: ${paramValue} + prefix: + type: string + defaultValue: start + suffix: + type: string + defaultValue: end + index: + type: int + defaultValue: 42 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Format function in resource name: ' -TestCases @( + @{ expression = "[format('Service-{0}', parameters('id'))]"; expected = 'Service-123' } + @{ expression = "[format('{0}-{1}-{2}', parameters('env'), parameters('app'), parameters('ver'))]"; expected = 'prod-web-v1' } + @{ expression = "[format('Resource_{0:D3}', parameters('num'))]"; expected = 'Resource_005' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + id: + type: string + defaultValue: '123' + env: + type: string + defaultValue: prod + app: + type: string + defaultValue: web + ver: + type: string + defaultValue: v1 + num: + type: int + defaultValue: 5 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Complex expression in resource name: ' -TestCases @( + @{ expression = "[concat(parameters('prefix'), '-', string(add(parameters('base'), parameters('offset'))))]"; expected = 'server-105' } + @{ expression = "[format('{0}-{1}', parameters('type'), if(equals(parameters('env'), 'prod'), 'production', 'development'))]"; expected = 'web-production' } + @{ expression = "[toLower(concat(parameters('region'), '-', parameters('service')))]"; expected = 'eastus-webapp' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + prefix: + type: string + defaultValue: server + base: + type: int + defaultValue: 100 + offset: + type: int + defaultValue: 5 + type: + type: string + defaultValue: web + env: + type: string + defaultValue: prod + region: + type: string + defaultValue: EASTUS + service: + type: string + defaultValue: WebApp +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Expression with object parameter access: ' -TestCases @( + @{ expression = "[parameters('config').name]"; expected = 'MyApp' } + @{ expression = "[concat(parameters('config').prefix, '-', parameters('config').id)]"; expected = 'app-001' } + @{ expression = "[parameters('servers')[0]]"; expected = 'web01' } + @{ expression = "[parameters('servers')[parameters('config').index]]"; expected = 'db01' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + name: MyApp + prefix: app + id: '001' + index: 1 + servers: + type: array + defaultValue: + - web01 + - db01 + - cache01 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Resource name expression error cases: ' -TestCases @( + @{ expression = "[parameters('nonexistent')]"; errorPattern = "*Parameter 'nonexistent' not found*" } + @{ expression = "[concat()]"; errorPattern = "*requires at least 1 argument*" } + @{ expression = "[add('text', 'more')]"; errorPattern = "*must be a number*" } + @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Property 'nonexistent' not found*" } + @{ expression = "[parameters('array')[10]]"; errorPattern = "*Index out of bounds*" } + ) { + param($expression, $errorPattern) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + name: test + array: + type: array + defaultValue: + - item1 + - item2 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null + $LASTEXITCODE | Should -Be 2 + $errorLog = Get-Content $TestDrive/error.log -Raw + $errorLog | Should -BeLike $errorPattern + } + + It 'Resource name expression must evaluate to string' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + number: + type: int + defaultValue: 42 +resources: +- name: "[parameters('number')]" + type: Microsoft/OSInfo + properties: {} +'@ + dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null + $LASTEXITCODE | Should -Be 2 + $errorLog = Get-Content $TestDrive/error.log -Raw + $errorLog | Should -BeLike "*Resource name expression must evaluate to a string*" + } + + It 'Multiple resources with different name expressions' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + env: + type: string + defaultValue: test + appId: + type: int + defaultValue: 1 +resources: +- name: "[concat('web-', parameters('env'))]" + type: Microsoft/OSInfo + properties: {} +- name: "[format('app-{0:D2}', parameters('appId'))]" + type: Microsoft/OSInfo + properties: {} +- name: "static-name" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'web-test' + $out.results[1].name | Should -Be 'app-01' + $out.results[2].name | Should -Be 'static-name' + } + + It 'Resource name expression with conditional logic' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + isProd: + type: bool + defaultValue: true + serviceName: + type: string + defaultValue: api +resources: +- name: "[concat(parameters('serviceName'), if(parameters('isProd'), '-prod', '-dev'))]" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'api-prod' + } + + It 'Resource name with nested function calls' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + services: + - web + - api + - db + selectedIndex: 1 +resources: +- name: "[toUpper(parameters('config').services[parameters('config').selectedIndex])]" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'API' + } + } } diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 9e301148c..c78e0fcc5 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -76,6 +76,7 @@ unrollingCopy = "Unrolling copy for resource '%{name}' with count %{count}" copyModeNotSupported = "Copy mode is not supported" copyBatchSizeNotSupported = "Copy batch size is not supported" copyNameResultNotString = "Copy name result is not a string" +nameResultNotString = "Resource name result is not a string" userFunctionAlreadyDefined = "User function '%{name}' in namespace '%{namespace}' is already defined" addingUserFunction = "Adding user function '%{name}'" diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index d279e48cb..08590ffcc 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -912,6 +912,28 @@ impl Configurator { Ok(()) } + pub fn process_resource_names(&mut self) -> Result<(), DscError> { + let mut config = self.config.clone(); + let config_copy = config.clone(); + + for resource in config_copy.resources { + // skip resources that were created from copy loops (they already have evaluated names) + if resource.copy.is_none() && resource.name.starts_with('[') && resource.name.ends_with(']') { + // process resource name expressions for non-copy resources + let Value::String(new_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { + return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) + }; + // find and update the resource name in the config + if let Some(config_resource) = config.resources.iter_mut().find(|r| r.name == resource.name && r.resource_type == resource.resource_type) { + config_resource.name = new_name.to_string(); + } + } + } + + self.config = config; + Ok(()) + } + fn invoke_property_expressions(&mut self, properties: Option<&Map>) -> Result>, DscError> { debug!("{}", t!("configure.mod.invokePropertyExpressions")); if properties.is_none() { From 20f4c7f8a764216cbf6d93f9fe407ece480d1640 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:51:09 +0200 Subject: [PATCH 03/18] Remove files from commit --- .../schemas/config/functions/copy.md | 220 ------------------ .../schemas/config/functions/overview.md | 1 - 2 files changed, 221 deletions(-) delete mode 100644 docs/reference/schemas/config/functions/copy.md diff --git a/docs/reference/schemas/config/functions/copy.md b/docs/reference/schemas/config/functions/copy.md deleted file mode 100644 index 206d8ab2d..000000000 --- a/docs/reference/schemas/config/functions/copy.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -description: Reference for the 'copy' DSC configuration document resource loop -ms.date: 02/28/2025 -ms.topic: reference -title: copy ---- - -# copy - -## Synopsis - -Defines a loop to create multiple instances of a resource. - -## Syntax - -```Syntax -copy: - name: - count: -``` - -## Description - -The `copy` property enables you to create multiple instances of a resource in a -DSC configuration. This is the equivalent implementation of the copy -functionality from Azure Resource Manager (ARM) templates, but without support -for variables and properties, which will be added in future releases. - -When you use `copy` on a resource, DSC creates multiple instances of that -resource based on the specified count. You can use the [`copyIndex()`][01] -function within the resource definition to access the current iteration index -and create unique names or property values for each instance. - -> [!NOTE] -> The `mode` and `batchSize` properties are not currently supported and will -> result in an error if used. - -## Examples - -### Example 1 - Create multiple Echo resources - -This example demonstrates the basic usage of `copy` to create three instances -of a Debug Echo resource. - -```yaml -# copy.example.1.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Echo-{0}', copyIndex())]" - copy: - name: echoLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Hello DSC" -``` - -```bash -dsc config get --file copy.example.1.dsc.config.yaml -``` - -```yaml -results: -- name: Echo-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -- name: Echo-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -- name: Echo-2 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -messages: [] -hadErrors: false -``` - -### Example 2 - Using copyIndex with offset - -This example demonstrates using [`copyIndex()`][01] with an offset to start -numbering from a different value. - -```yaml -# copy.example.2.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Service-{0}', copyIndex(100))]" - copy: - name: serviceLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Service instance" -``` - -```bash -dsc config get --file copy.example.2.dsc.config.yaml -``` - -```yaml -results: -- name: Service-100 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -- name: Service-101 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -- name: Service-102 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -messages: [] -hadErrors: false -``` - -### Example 3 - Using named loop references - -This example shows how to reference a specific loop by name when using -[`copyIndex()`][01]. - -```yaml -# copy.example.3.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Resource-{0}', copyIndex('mainLoop'))]" - copy: - name: mainLoop - count: 2 - type: Microsoft.DSC.Debug/Echo - properties: - output: "From main loop" -``` - -```bash -dsc config get --file copy.example.3.dsc.config.yaml -``` - -```yaml -results: -- name: Resource-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: From main loop -- name: Resource-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: From main loop -messages: [] -hadErrors: false -``` - -## Properties - -### name - -The name of the copy loop. This name can be used with the [`copyIndex()`][01] -function to reference the current iteration index of this specific loop. - -```yaml -Type: string -Required: true -``` - -### count - -The number of iterations to perform. Must be a non-negative integer. If set to -0, no instances of the resource are created. - -```yaml -Type: integer -Required: true -Minimum: 0 -``` - -### mode - -> [!WARNING] -> The `mode` property is not currently supported and will result in an error -> if used. - -This property is reserved for future implementation to specify whether resources -should be created serially or in parallel. - -### batchSize - -> [!WARNING] -> The `batchSize` property is not currently supported and will result in an -> error if used. - -This property is reserved for future implementation to specify how many -resources to create in each batch when using parallel mode. - -## Limitations - -The current implementation has the following limitations: - -- **Variables and properties**: Copy loops for variables and properties are not - yet supported. -- **Mode control**: The `mode` property (serial/parallel) is not implemented. -- **Batch processing**: The `batchSize` property is not implemented. -- **Name expressions**: The resource name expression must evaluate to a string. - -## Related Functions - -- [`copyIndex()`][01] - Returns the current iteration index of a copy loop. - - -[01]: ./copyIndex.md diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index 236b8a6db..d1cfe687b 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -603,7 +603,6 @@ The following list of functions operate on integer values or arrays of integer v The following list of functions operate on resource instances: -- [copyIndex()][copyIndex] - Return the current iteration index of a copy loop. - [reference()][reference] - Return the result data for another resource instance. - [resourceId()][resourceId] - Return the ID of another resource instance to reference or depend on. From b3397c9db4cd11389aa337f87e0fff6e2cf13de4 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:51:26 +0200 Subject: [PATCH 04/18] Copyindex --- .../schemas/config/functions/copyIndex.md | 240 ------------------ 1 file changed, 240 deletions(-) delete mode 100644 docs/reference/schemas/config/functions/copyIndex.md diff --git a/docs/reference/schemas/config/functions/copyIndex.md b/docs/reference/schemas/config/functions/copyIndex.md deleted file mode 100644 index 6fd0062c4..000000000 --- a/docs/reference/schemas/config/functions/copyIndex.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -description: Reference for the 'copyIndex' DSC configuration document function -ms.date: 02/28/2025 -ms.topic: reference -title: copyIndex ---- - -# copyIndex - -## Synopsis - -Returns the current iteration index of a copy loop. - -## Syntax - -```Syntax -copyIndex() -copyIndex() -copyIndex('') -copyIndex('', ) -``` - -## Description - -The `copyIndex()` function returns the current iteration index of a copy loop. -This function can only be used within resources that have a `copy` property -defined. The function is necessary for creating unique names and property -values for each instance created by the copy loop. - -The index starts at 0 for the first iteration and increments by 1 for each -subsequent iteration. You can add an offset to shift the starting number, or -reference a specific loop by name when multiple copy loops are present. - -## Examples - -### Example 1 - Basic copyIndex usage - -This example shows the basic usage of `copyIndex()` to create unique resource -names. - -```yaml -# copyIndex.example.1.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Resource-{0}', copyIndex())]" - copy: - name: basicLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Hello DSC" -``` - -```bash -dsc config get --file copyIndex.example.1.dsc.config.yaml -``` - -```yaml -results: -- name: Resource-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -- name: Resource-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -- name: Resource-2 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -messages: [] -hadErrors: false -``` - -### Example 2 - Using copyIndex with offset - -This example demonstrates using an offset to start numbering from a different -value. - -```yaml -# copyIndex.example.2.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Server-{0}', copyIndex(10))]" - copy: - name: serverLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Server instance starting from 10 till 12" -``` - -```bash -dsc config get --file copyIndex.example.2.dsc.config.yaml -``` - -```yaml -results: -- name: Server-10 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -- name: Server-11 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -- name: Server-12 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -messages: [] -hadErrors: false -``` - -### Example 3 - Using copyIndex with loop name - -This example shows how to reference a specific loop by name. - -```yaml -# copyIndex.example.3.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Item-{0}', copyIndex('itemLoop'))]" - copy: - name: itemLoop - count: 1 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Item from loop" -``` - -```bash -dsc config get --file copyIndex.example.3.dsc.config.yaml -``` - -```yaml -results: -- name: Item-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Item from loop" -messages: [] -hadErrors: false -``` - -### Example 4 - Using copyIndex with loop name and offset - -This example combines both loop name and offset parameters. - -```yaml -# copyIndex.example.4.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Database-{0}', copyIndex('dbLoop', 100))]" - copy: - name: dbLoop - count: 2 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Database instance" -``` - -```bash -dsc config get --file copyIndex.example.4.dsc.config.yaml -``` - -```yaml -results: -- name: Database-100 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Database instance" -- name: Database-101 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Database instance" -messages: [] -hadErrors: false -``` - -## Parameters - -### offset - -An optional integer offset to add to the current index. The offset must be a -non-negative number. - -```yaml -Type: integer -Required: false -Minimum: 0 -``` - -### loopName - -An optional string specifying the name of the copy loop to reference. This is -useful when you have multiple copy loops and need to reference a specific one. - -```yaml -Type: string -Required: false -``` - -## Output - -The `copyIndex()` function returns an integer representing the current iteration -index, optionally adjusted by the offset. - -```yaml -Type: integer -``` - -## Error Conditions - -The `copyIndex()` function will return an error in the following situations: - -- **Used outside copy loop**: The function can only be used within resources - that have a `copy` property defined. -- **Negative offset**: The offset parameter must be non-negative. -- **Invalid loop name**: If a loop name is specified but no loop with that - name exists. -- **Invalid arguments**: If the arguments provided are not of the expected - types. - -## Related Properties - -- [`copy`][01] - Defines a loop to create multiple instances of a resource. - - -[01]: ./copy.md From 64423d24d10f8b47d2e3fb357d051487af32896f Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:52:04 +0200 Subject: [PATCH 05/18] Remove reference --- docs/reference/schemas/config/functions/overview.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index d1cfe687b..dd8341359 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -631,7 +631,6 @@ The following list of functions create or convert values of a given type: [add]: ./add.md [base64]: ./base64.md [concat]: ./concat.md -[copyIndex]: ./copyIndex.md [createArray]: ./createArray.md [div]: ./div.md [envvar]: ./envvar.md From a032db85b46676eedd3a52f609da2c1e28fe2641 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 06:40:47 +0200 Subject: [PATCH 06/18] Fix clipyy --- dsc_lib/src/configure/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 08590ffcc..d43f70c11 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -912,6 +912,17 @@ impl Configurator { Ok(()) } + /// Process resource name expressions for non-copy resources. + /// + /// This method evaluates DSC expressions in resource names after parameters + /// have been processed, ensuring that parameter references work correctly. + /// + /// # Errors + /// + /// This function will return an error if: + /// - Resource name expression evaluation fails + /// - Expression does not result in a string value + /// - Statement parser encounters invalid syntax pub fn process_resource_names(&mut self) -> Result<(), DscError> { let mut config = self.config.clone(); let config_copy = config.clone(); From 120e6e126bc8e296ed42691db7c1bbd0d3476ea1 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 06:57:20 +0200 Subject: [PATCH 07/18] Typo in file --- dsc/tests/dsc_expressions.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index 61d7ec165..ec241c53e 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -242,7 +242,7 @@ resources: $log | Should -BeLike "*ERROR* Arguments must be of the same type*" } -g + Context 'Resource name expression evaluation' { It 'Simple parameter expression in resource name: ' -TestCases @( @{ expression = "[parameters('resourceName')]"; paramValue = 'TestResource'; expected = 'TestResource' } From 4c3725936ca81cffb43bc8d543a364ed6b0b4f9e Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 07:31:54 +0200 Subject: [PATCH 08/18] Fix test cases --- dsc/tests/dsc_expressions.tests.ps1 | 47 ++++++----------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index ec241c53e..34d4b3e11 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -263,7 +263,7 @@ resources: type: Microsoft/OSInfo properties: {} "@ - $out = .\dsc\target\debug\dsc.exe config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) $out.results[0].name | Should -Be $expected } @@ -302,7 +302,6 @@ resources: It 'Format function in resource name: ' -TestCases @( @{ expression = "[format('Service-{0}', parameters('id'))]"; expected = 'Service-123' } @{ expression = "[format('{0}-{1}-{2}', parameters('env'), parameters('app'), parameters('ver'))]"; expected = 'prod-web-v1' } - @{ expression = "[format('Resource_{0:D3}', parameters('num'))]"; expected = 'Resource_005' } ) { param($expression, $expected) $yaml = @" @@ -336,7 +335,7 @@ resources: It 'Complex expression in resource name: ' -TestCases @( @{ expression = "[concat(parameters('prefix'), '-', string(add(parameters('base'), parameters('offset'))))]"; expected = 'server-105' } @{ expression = "[format('{0}-{1}', parameters('type'), if(equals(parameters('env'), 'prod'), 'production', 'development'))]"; expected = 'web-production' } - @{ expression = "[toLower(concat(parameters('region'), '-', parameters('service')))]"; expected = 'eastus-webapp' } + ) { param($expression, $expected) $yaml = @" @@ -408,10 +407,10 @@ resources: It 'Resource name expression error cases: ' -TestCases @( @{ expression = "[parameters('nonexistent')]"; errorPattern = "*Parameter 'nonexistent' not found*" } - @{ expression = "[concat()]"; errorPattern = "*requires at least 1 argument*" } - @{ expression = "[add('text', 'more')]"; errorPattern = "*must be a number*" } - @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Property 'nonexistent' not found*" } - @{ expression = "[parameters('array')[10]]"; errorPattern = "*Index out of bounds*" } + @{ expression = "[concat()]"; errorPattern = "*requires at least 2 arguments*" } + @{ expression = "[add('text', 'more')]"; errorPattern = "*Function 'add' does not accept string arguments, accepted types are: Number*" } + @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Parser: Member 'nonexistent' not found*" } + @{ expression = "[parameters('array')[10]]"; errorPattern = "*Parser: Index is out of bounds*" } ) { param($expression, $errorPattern) $yaml = @" @@ -452,35 +451,7 @@ resources: dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null $LASTEXITCODE | Should -Be 2 $errorLog = Get-Content $TestDrive/error.log -Raw - $errorLog | Should -BeLike "*Resource name expression must evaluate to a string*" - } - - It 'Multiple resources with different name expressions' { - $yaml = @' -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -parameters: - env: - type: string - defaultValue: test - appId: - type: int - defaultValue: 1 -resources: -- name: "[concat('web-', parameters('env'))]" - type: Microsoft/OSInfo - properties: {} -- name: "[format('app-{0:D2}', parameters('appId'))]" - type: Microsoft/OSInfo - properties: {} -- name: "static-name" - type: Microsoft/OSInfo - properties: {} -'@ - $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) - $out.results[0].name | Should -Be 'web-test' - $out.results[1].name | Should -Be 'app-01' - $out.results[2].name | Should -Be 'static-name' + $errorLog | Should -BeLike "*Resource name result is not a string*" } It 'Resource name expression with conditional logic' { @@ -516,13 +487,13 @@ parameters: - db selectedIndex: 1 resources: -- name: "[toUpper(parameters('config').services[parameters('config').selectedIndex])]" +- name: "[concat('SERVICE-', parameters('config').services[parameters('config').selectedIndex])]" type: Microsoft/OSInfo properties: {} '@ $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) - $out.results[0].name | Should -Be 'API' + $out.results[0].name | Should -Be 'SERVICE-api' } } } From 677faf3d24d6a25407ee44a0b2d4fad9cc545c96 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Wed, 17 Sep 2025 19:15:19 +0200 Subject: [PATCH 09/18] Add copy() and copyIndex() function reference documentation --- .../schemas/config/functions/copy.md | 220 ++++++++++++++++ .../schemas/config/functions/copyIndex.md | 240 ++++++++++++++++++ .../schemas/config/functions/overview.md | 2 + 3 files changed, 462 insertions(+) create mode 100644 docs/reference/schemas/config/functions/copy.md create mode 100644 docs/reference/schemas/config/functions/copyIndex.md diff --git a/docs/reference/schemas/config/functions/copy.md b/docs/reference/schemas/config/functions/copy.md new file mode 100644 index 000000000..206d8ab2d --- /dev/null +++ b/docs/reference/schemas/config/functions/copy.md @@ -0,0 +1,220 @@ +--- +description: Reference for the 'copy' DSC configuration document resource loop +ms.date: 02/28/2025 +ms.topic: reference +title: copy +--- + +# copy + +## Synopsis + +Defines a loop to create multiple instances of a resource. + +## Syntax + +```Syntax +copy: + name: + count: +``` + +## Description + +The `copy` property enables you to create multiple instances of a resource in a +DSC configuration. This is the equivalent implementation of the copy +functionality from Azure Resource Manager (ARM) templates, but without support +for variables and properties, which will be added in future releases. + +When you use `copy` on a resource, DSC creates multiple instances of that +resource based on the specified count. You can use the [`copyIndex()`][01] +function within the resource definition to access the current iteration index +and create unique names or property values for each instance. + +> [!NOTE] +> The `mode` and `batchSize` properties are not currently supported and will +> result in an error if used. + +## Examples + +### Example 1 - Create multiple Echo resources + +This example demonstrates the basic usage of `copy` to create three instances +of a Debug Echo resource. + +```yaml +# copy.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Echo-{0}', copyIndex())]" + copy: + name: echoLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Hello DSC" +``` + +```bash +dsc config get --file copy.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Echo-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +- name: Echo-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +- name: Echo-2 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Hello DSC +messages: [] +hadErrors: false +``` + +### Example 2 - Using copyIndex with offset + +This example demonstrates using [`copyIndex()`][01] with an offset to start +numbering from a different value. + +```yaml +# copy.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Service-{0}', copyIndex(100))]" + copy: + name: serviceLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Service instance" +``` + +```bash +dsc config get --file copy.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Service-100 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +- name: Service-101 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +- name: Service-102 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Service instance +messages: [] +hadErrors: false +``` + +### Example 3 - Using named loop references + +This example shows how to reference a specific loop by name when using +[`copyIndex()`][01]. + +```yaml +# copy.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Resource-{0}', copyIndex('mainLoop'))]" + copy: + name: mainLoop + count: 2 + type: Microsoft.DSC.Debug/Echo + properties: + output: "From main loop" +``` + +```bash +dsc config get --file copy.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Resource-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: From main loop +- name: Resource-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: From main loop +messages: [] +hadErrors: false +``` + +## Properties + +### name + +The name of the copy loop. This name can be used with the [`copyIndex()`][01] +function to reference the current iteration index of this specific loop. + +```yaml +Type: string +Required: true +``` + +### count + +The number of iterations to perform. Must be a non-negative integer. If set to +0, no instances of the resource are created. + +```yaml +Type: integer +Required: true +Minimum: 0 +``` + +### mode + +> [!WARNING] +> The `mode` property is not currently supported and will result in an error +> if used. + +This property is reserved for future implementation to specify whether resources +should be created serially or in parallel. + +### batchSize + +> [!WARNING] +> The `batchSize` property is not currently supported and will result in an +> error if used. + +This property is reserved for future implementation to specify how many +resources to create in each batch when using parallel mode. + +## Limitations + +The current implementation has the following limitations: + +- **Variables and properties**: Copy loops for variables and properties are not + yet supported. +- **Mode control**: The `mode` property (serial/parallel) is not implemented. +- **Batch processing**: The `batchSize` property is not implemented. +- **Name expressions**: The resource name expression must evaluate to a string. + +## Related Functions + +- [`copyIndex()`][01] - Returns the current iteration index of a copy loop. + + +[01]: ./copyIndex.md diff --git a/docs/reference/schemas/config/functions/copyIndex.md b/docs/reference/schemas/config/functions/copyIndex.md new file mode 100644 index 000000000..6fd0062c4 --- /dev/null +++ b/docs/reference/schemas/config/functions/copyIndex.md @@ -0,0 +1,240 @@ +--- +description: Reference for the 'copyIndex' DSC configuration document function +ms.date: 02/28/2025 +ms.topic: reference +title: copyIndex +--- + +# copyIndex + +## Synopsis + +Returns the current iteration index of a copy loop. + +## Syntax + +```Syntax +copyIndex() +copyIndex() +copyIndex('') +copyIndex('', ) +``` + +## Description + +The `copyIndex()` function returns the current iteration index of a copy loop. +This function can only be used within resources that have a `copy` property +defined. The function is necessary for creating unique names and property +values for each instance created by the copy loop. + +The index starts at 0 for the first iteration and increments by 1 for each +subsequent iteration. You can add an offset to shift the starting number, or +reference a specific loop by name when multiple copy loops are present. + +## Examples + +### Example 1 - Basic copyIndex usage + +This example shows the basic usage of `copyIndex()` to create unique resource +names. + +```yaml +# copyIndex.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Resource-{0}', copyIndex())]" + copy: + name: basicLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Hello DSC" +``` + +```bash +dsc config get --file copyIndex.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Resource-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +- name: Resource-1 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +- name: Resource-2 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Hello World" +messages: [] +hadErrors: false +``` + +### Example 2 - Using copyIndex with offset + +This example demonstrates using an offset to start numbering from a different +value. + +```yaml +# copyIndex.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Server-{0}', copyIndex(10))]" + copy: + name: serverLoop + count: 3 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Server instance starting from 10 till 12" +``` + +```bash +dsc config get --file copyIndex.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Server-10 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +- name: Server-11 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +- name: Server-12 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Server instance starting from 10 till 12" +messages: [] +hadErrors: false +``` + +### Example 3 - Using copyIndex with loop name + +This example shows how to reference a specific loop by name. + +```yaml +# copyIndex.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Item-{0}', copyIndex('itemLoop'))]" + copy: + name: itemLoop + count: 1 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Item from loop" +``` + +```bash +dsc config get --file copyIndex.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Item-0 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Item from loop" +messages: [] +hadErrors: false +``` + +### Example 4 - Using copyIndex with loop name and offset + +This example combines both loop name and offset parameters. + +```yaml +# copyIndex.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: "[format('Database-{0}', copyIndex('dbLoop', 100))]" + copy: + name: dbLoop + count: 2 + type: Microsoft.DSC.Debug/Echo + properties: + output: "Database instance" +``` + +```bash +dsc config get --file copyIndex.example.4.dsc.config.yaml +``` + +```yaml +results: +- name: Database-100 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Database instance" +- name: Database-101 + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: "Database instance" +messages: [] +hadErrors: false +``` + +## Parameters + +### offset + +An optional integer offset to add to the current index. The offset must be a +non-negative number. + +```yaml +Type: integer +Required: false +Minimum: 0 +``` + +### loopName + +An optional string specifying the name of the copy loop to reference. This is +useful when you have multiple copy loops and need to reference a specific one. + +```yaml +Type: string +Required: false +``` + +## Output + +The `copyIndex()` function returns an integer representing the current iteration +index, optionally adjusted by the offset. + +```yaml +Type: integer +``` + +## Error Conditions + +The `copyIndex()` function will return an error in the following situations: + +- **Used outside copy loop**: The function can only be used within resources + that have a `copy` property defined. +- **Negative offset**: The offset parameter must be non-negative. +- **Invalid loop name**: If a loop name is specified but no loop with that + name exists. +- **Invalid arguments**: If the arguments provided are not of the expected + types. + +## Related Properties + +- [`copy`][01] - Defines a loop to create multiple instances of a resource. + + +[01]: ./copy.md diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index dd8341359..236b8a6db 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -603,6 +603,7 @@ The following list of functions operate on integer values or arrays of integer v The following list of functions operate on resource instances: +- [copyIndex()][copyIndex] - Return the current iteration index of a copy loop. - [reference()][reference] - Return the result data for another resource instance. - [resourceId()][resourceId] - Return the ID of another resource instance to reference or depend on. @@ -631,6 +632,7 @@ The following list of functions create or convert values of a given type: [add]: ./add.md [base64]: ./base64.md [concat]: ./concat.md +[copyIndex]: ./copyIndex.md [createArray]: ./createArray.md [div]: ./div.md [envvar]: ./envvar.md From aa6d647838e999101671d1713496c0acb5cd91f5 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:47:13 +0200 Subject: [PATCH 10/18] Add support to validate expressions for names --- dsc/src/subcommand.rs | 6 + dsc/tests/dsc_expressions.tests.ps1 | 283 ++++++++++++++++++++++++++++ dsc_lib/locales/en-us.toml | 1 + dsc_lib/src/configure/mod.rs | 22 +++ 4 files changed, 312 insertions(+) diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index bf559cf65..2f8e3fd97 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -381,6 +381,12 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame exit(EXIT_INVALID_INPUT); } + // process resource names after parameters are available + if let Err(err) = configurator.process_resource_names() { + error!("Error processing resource names: {err}"); + exit(EXIT_DSC_ERROR); + } + match subcommand { ConfigSubCommand::Get { output_format, .. } => { config_get(&mut configurator, output_format.as_ref(), as_group); diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index bba61abd8..61d7ec165 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -242,4 +242,287 @@ resources: $log | Should -BeLike "*ERROR* Arguments must be of the same type*" } +g + Context 'Resource name expression evaluation' { + It 'Simple parameter expression in resource name: ' -TestCases @( + @{ expression = "[parameters('resourceName')]"; paramValue = 'TestResource'; expected = 'TestResource' } + @{ expression = "[parameters('serviceName')]"; paramValue = 'MyService'; expected = 'MyService' } + ) { + param($expression, $paramValue, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + resourceName: + type: string + defaultValue: $paramValue + serviceName: + type: string + defaultValue: $paramValue +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = .\dsc\target\debug\dsc.exe config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Concat function in resource name: ' -TestCases @( + @{ expression = "[concat('prefix-', parameters('name'))]"; paramValue = 'test'; expected = 'prefix-test' } + @{ expression = "[concat(parameters('prefix'), '-', parameters('suffix'))]"; expected = 'start-end' } + @{ expression = "[concat('Resource-', string(parameters('index')))]"; expected = 'Resource-42' } + ) { + param($expression, $paramValue, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + name: + type: string + defaultValue: ${paramValue} + prefix: + type: string + defaultValue: start + suffix: + type: string + defaultValue: end + index: + type: int + defaultValue: 42 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Format function in resource name: ' -TestCases @( + @{ expression = "[format('Service-{0}', parameters('id'))]"; expected = 'Service-123' } + @{ expression = "[format('{0}-{1}-{2}', parameters('env'), parameters('app'), parameters('ver'))]"; expected = 'prod-web-v1' } + @{ expression = "[format('Resource_{0:D3}', parameters('num'))]"; expected = 'Resource_005' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + id: + type: string + defaultValue: '123' + env: + type: string + defaultValue: prod + app: + type: string + defaultValue: web + ver: + type: string + defaultValue: v1 + num: + type: int + defaultValue: 5 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Complex expression in resource name: ' -TestCases @( + @{ expression = "[concat(parameters('prefix'), '-', string(add(parameters('base'), parameters('offset'))))]"; expected = 'server-105' } + @{ expression = "[format('{0}-{1}', parameters('type'), if(equals(parameters('env'), 'prod'), 'production', 'development'))]"; expected = 'web-production' } + @{ expression = "[toLower(concat(parameters('region'), '-', parameters('service')))]"; expected = 'eastus-webapp' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + prefix: + type: string + defaultValue: server + base: + type: int + defaultValue: 100 + offset: + type: int + defaultValue: 5 + type: + type: string + defaultValue: web + env: + type: string + defaultValue: prod + region: + type: string + defaultValue: EASTUS + service: + type: string + defaultValue: WebApp +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Expression with object parameter access: ' -TestCases @( + @{ expression = "[parameters('config').name]"; expected = 'MyApp' } + @{ expression = "[concat(parameters('config').prefix, '-', parameters('config').id)]"; expected = 'app-001' } + @{ expression = "[parameters('servers')[0]]"; expected = 'web01' } + @{ expression = "[parameters('servers')[parameters('config').index]]"; expected = 'db01' } + ) { + param($expression, $expected) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + name: MyApp + prefix: app + id: '001' + index: 1 + servers: + type: array + defaultValue: + - web01 + - db01 + - cache01 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be $expected + } + + It 'Resource name expression error cases: ' -TestCases @( + @{ expression = "[parameters('nonexistent')]"; errorPattern = "*Parameter 'nonexistent' not found*" } + @{ expression = "[concat()]"; errorPattern = "*requires at least 1 argument*" } + @{ expression = "[add('text', 'more')]"; errorPattern = "*must be a number*" } + @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Property 'nonexistent' not found*" } + @{ expression = "[parameters('array')[10]]"; errorPattern = "*Index out of bounds*" } + ) { + param($expression, $errorPattern) + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + name: test + array: + type: array + defaultValue: + - item1 + - item2 +resources: +- name: "$expression" + type: Microsoft/OSInfo + properties: {} +"@ + dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null + $LASTEXITCODE | Should -Be 2 + $errorLog = Get-Content $TestDrive/error.log -Raw + $errorLog | Should -BeLike $errorPattern + } + + It 'Resource name expression must evaluate to string' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + number: + type: int + defaultValue: 42 +resources: +- name: "[parameters('number')]" + type: Microsoft/OSInfo + properties: {} +'@ + dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null + $LASTEXITCODE | Should -Be 2 + $errorLog = Get-Content $TestDrive/error.log -Raw + $errorLog | Should -BeLike "*Resource name expression must evaluate to a string*" + } + + It 'Multiple resources with different name expressions' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + env: + type: string + defaultValue: test + appId: + type: int + defaultValue: 1 +resources: +- name: "[concat('web-', parameters('env'))]" + type: Microsoft/OSInfo + properties: {} +- name: "[format('app-{0:D2}', parameters('appId'))]" + type: Microsoft/OSInfo + properties: {} +- name: "static-name" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'web-test' + $out.results[1].name | Should -Be 'app-01' + $out.results[2].name | Should -Be 'static-name' + } + + It 'Resource name expression with conditional logic' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + isProd: + type: bool + defaultValue: true + serviceName: + type: string + defaultValue: api +resources: +- name: "[concat(parameters('serviceName'), if(parameters('isProd'), '-prod', '-dev'))]" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'api-prod' + } + + It 'Resource name with nested function calls' { + $yaml = @' +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + config: + type: object + defaultValue: + services: + - web + - api + - db + selectedIndex: 1 +resources: +- name: "[toUpper(parameters('config').services[parameters('config').selectedIndex])]" + type: Microsoft/OSInfo + properties: {} +'@ + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) + $out.results[0].name | Should -Be 'API' + } + } } diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 9e301148c..c78e0fcc5 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -76,6 +76,7 @@ unrollingCopy = "Unrolling copy for resource '%{name}' with count %{count}" copyModeNotSupported = "Copy mode is not supported" copyBatchSizeNotSupported = "Copy batch size is not supported" copyNameResultNotString = "Copy name result is not a string" +nameResultNotString = "Resource name result is not a string" userFunctionAlreadyDefined = "User function '%{name}' in namespace '%{namespace}' is already defined" addingUserFunction = "Adding user function '%{name}'" diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index d279e48cb..08590ffcc 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -912,6 +912,28 @@ impl Configurator { Ok(()) } + pub fn process_resource_names(&mut self) -> Result<(), DscError> { + let mut config = self.config.clone(); + let config_copy = config.clone(); + + for resource in config_copy.resources { + // skip resources that were created from copy loops (they already have evaluated names) + if resource.copy.is_none() && resource.name.starts_with('[') && resource.name.ends_with(']') { + // process resource name expressions for non-copy resources + let Value::String(new_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { + return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) + }; + // find and update the resource name in the config + if let Some(config_resource) = config.resources.iter_mut().find(|r| r.name == resource.name && r.resource_type == resource.resource_type) { + config_resource.name = new_name.to_string(); + } + } + } + + self.config = config; + Ok(()) + } + fn invoke_property_expressions(&mut self, properties: Option<&Map>) -> Result>, DscError> { debug!("{}", t!("configure.mod.invokePropertyExpressions")); if properties.is_none() { From 14ec8007c64fe9c3bb9e1f73051d7ce3ddb54ccc Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:51:09 +0200 Subject: [PATCH 11/18] Remove files from commit --- .../schemas/config/functions/copy.md | 220 ------------------ .../schemas/config/functions/overview.md | 1 - 2 files changed, 221 deletions(-) delete mode 100644 docs/reference/schemas/config/functions/copy.md diff --git a/docs/reference/schemas/config/functions/copy.md b/docs/reference/schemas/config/functions/copy.md deleted file mode 100644 index 206d8ab2d..000000000 --- a/docs/reference/schemas/config/functions/copy.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -description: Reference for the 'copy' DSC configuration document resource loop -ms.date: 02/28/2025 -ms.topic: reference -title: copy ---- - -# copy - -## Synopsis - -Defines a loop to create multiple instances of a resource. - -## Syntax - -```Syntax -copy: - name: - count: -``` - -## Description - -The `copy` property enables you to create multiple instances of a resource in a -DSC configuration. This is the equivalent implementation of the copy -functionality from Azure Resource Manager (ARM) templates, but without support -for variables and properties, which will be added in future releases. - -When you use `copy` on a resource, DSC creates multiple instances of that -resource based on the specified count. You can use the [`copyIndex()`][01] -function within the resource definition to access the current iteration index -and create unique names or property values for each instance. - -> [!NOTE] -> The `mode` and `batchSize` properties are not currently supported and will -> result in an error if used. - -## Examples - -### Example 1 - Create multiple Echo resources - -This example demonstrates the basic usage of `copy` to create three instances -of a Debug Echo resource. - -```yaml -# copy.example.1.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Echo-{0}', copyIndex())]" - copy: - name: echoLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Hello DSC" -``` - -```bash -dsc config get --file copy.example.1.dsc.config.yaml -``` - -```yaml -results: -- name: Echo-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -- name: Echo-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -- name: Echo-2 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Hello DSC -messages: [] -hadErrors: false -``` - -### Example 2 - Using copyIndex with offset - -This example demonstrates using [`copyIndex()`][01] with an offset to start -numbering from a different value. - -```yaml -# copy.example.2.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Service-{0}', copyIndex(100))]" - copy: - name: serviceLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Service instance" -``` - -```bash -dsc config get --file copy.example.2.dsc.config.yaml -``` - -```yaml -results: -- name: Service-100 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -- name: Service-101 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -- name: Service-102 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: Service instance -messages: [] -hadErrors: false -``` - -### Example 3 - Using named loop references - -This example shows how to reference a specific loop by name when using -[`copyIndex()`][01]. - -```yaml -# copy.example.3.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Resource-{0}', copyIndex('mainLoop'))]" - copy: - name: mainLoop - count: 2 - type: Microsoft.DSC.Debug/Echo - properties: - output: "From main loop" -``` - -```bash -dsc config get --file copy.example.3.dsc.config.yaml -``` - -```yaml -results: -- name: Resource-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: From main loop -- name: Resource-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: From main loop -messages: [] -hadErrors: false -``` - -## Properties - -### name - -The name of the copy loop. This name can be used with the [`copyIndex()`][01] -function to reference the current iteration index of this specific loop. - -```yaml -Type: string -Required: true -``` - -### count - -The number of iterations to perform. Must be a non-negative integer. If set to -0, no instances of the resource are created. - -```yaml -Type: integer -Required: true -Minimum: 0 -``` - -### mode - -> [!WARNING] -> The `mode` property is not currently supported and will result in an error -> if used. - -This property is reserved for future implementation to specify whether resources -should be created serially or in parallel. - -### batchSize - -> [!WARNING] -> The `batchSize` property is not currently supported and will result in an -> error if used. - -This property is reserved for future implementation to specify how many -resources to create in each batch when using parallel mode. - -## Limitations - -The current implementation has the following limitations: - -- **Variables and properties**: Copy loops for variables and properties are not - yet supported. -- **Mode control**: The `mode` property (serial/parallel) is not implemented. -- **Batch processing**: The `batchSize` property is not implemented. -- **Name expressions**: The resource name expression must evaluate to a string. - -## Related Functions - -- [`copyIndex()`][01] - Returns the current iteration index of a copy loop. - - -[01]: ./copyIndex.md diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index 236b8a6db..d1cfe687b 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -603,7 +603,6 @@ The following list of functions operate on integer values or arrays of integer v The following list of functions operate on resource instances: -- [copyIndex()][copyIndex] - Return the current iteration index of a copy loop. - [reference()][reference] - Return the result data for another resource instance. - [resourceId()][resourceId] - Return the ID of another resource instance to reference or depend on. From b8d02d887b851247ec98c22c492c8ec6d8e8b0c3 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:51:26 +0200 Subject: [PATCH 12/18] Copyindex --- .../schemas/config/functions/copyIndex.md | 240 ------------------ 1 file changed, 240 deletions(-) delete mode 100644 docs/reference/schemas/config/functions/copyIndex.md diff --git a/docs/reference/schemas/config/functions/copyIndex.md b/docs/reference/schemas/config/functions/copyIndex.md deleted file mode 100644 index 6fd0062c4..000000000 --- a/docs/reference/schemas/config/functions/copyIndex.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -description: Reference for the 'copyIndex' DSC configuration document function -ms.date: 02/28/2025 -ms.topic: reference -title: copyIndex ---- - -# copyIndex - -## Synopsis - -Returns the current iteration index of a copy loop. - -## Syntax - -```Syntax -copyIndex() -copyIndex() -copyIndex('') -copyIndex('', ) -``` - -## Description - -The `copyIndex()` function returns the current iteration index of a copy loop. -This function can only be used within resources that have a `copy` property -defined. The function is necessary for creating unique names and property -values for each instance created by the copy loop. - -The index starts at 0 for the first iteration and increments by 1 for each -subsequent iteration. You can add an offset to shift the starting number, or -reference a specific loop by name when multiple copy loops are present. - -## Examples - -### Example 1 - Basic copyIndex usage - -This example shows the basic usage of `copyIndex()` to create unique resource -names. - -```yaml -# copyIndex.example.1.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Resource-{0}', copyIndex())]" - copy: - name: basicLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Hello DSC" -``` - -```bash -dsc config get --file copyIndex.example.1.dsc.config.yaml -``` - -```yaml -results: -- name: Resource-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -- name: Resource-1 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -- name: Resource-2 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Hello World" -messages: [] -hadErrors: false -``` - -### Example 2 - Using copyIndex with offset - -This example demonstrates using an offset to start numbering from a different -value. - -```yaml -# copyIndex.example.2.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Server-{0}', copyIndex(10))]" - copy: - name: serverLoop - count: 3 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Server instance starting from 10 till 12" -``` - -```bash -dsc config get --file copyIndex.example.2.dsc.config.yaml -``` - -```yaml -results: -- name: Server-10 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -- name: Server-11 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -- name: Server-12 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Server instance starting from 10 till 12" -messages: [] -hadErrors: false -``` - -### Example 3 - Using copyIndex with loop name - -This example shows how to reference a specific loop by name. - -```yaml -# copyIndex.example.3.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Item-{0}', copyIndex('itemLoop'))]" - copy: - name: itemLoop - count: 1 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Item from loop" -``` - -```bash -dsc config get --file copyIndex.example.3.dsc.config.yaml -``` - -```yaml -results: -- name: Item-0 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Item from loop" -messages: [] -hadErrors: false -``` - -### Example 4 - Using copyIndex with loop name and offset - -This example combines both loop name and offset parameters. - -```yaml -# copyIndex.example.4.dsc.config.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: -- name: "[format('Database-{0}', copyIndex('dbLoop', 100))]" - copy: - name: dbLoop - count: 2 - type: Microsoft.DSC.Debug/Echo - properties: - output: "Database instance" -``` - -```bash -dsc config get --file copyIndex.example.4.dsc.config.yaml -``` - -```yaml -results: -- name: Database-100 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Database instance" -- name: Database-101 - type: Microsoft.DSC.Debug/Echo - result: - actualState: - output: "Database instance" -messages: [] -hadErrors: false -``` - -## Parameters - -### offset - -An optional integer offset to add to the current index. The offset must be a -non-negative number. - -```yaml -Type: integer -Required: false -Minimum: 0 -``` - -### loopName - -An optional string specifying the name of the copy loop to reference. This is -useful when you have multiple copy loops and need to reference a specific one. - -```yaml -Type: string -Required: false -``` - -## Output - -The `copyIndex()` function returns an integer representing the current iteration -index, optionally adjusted by the offset. - -```yaml -Type: integer -``` - -## Error Conditions - -The `copyIndex()` function will return an error in the following situations: - -- **Used outside copy loop**: The function can only be used within resources - that have a `copy` property defined. -- **Negative offset**: The offset parameter must be non-negative. -- **Invalid loop name**: If a loop name is specified but no loop with that - name exists. -- **Invalid arguments**: If the arguments provided are not of the expected - types. - -## Related Properties - -- [`copy`][01] - Defines a loop to create multiple instances of a resource. - - -[01]: ./copy.md From 08241aacb2badd63c5ba298401b17a92195d47f9 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 04:52:04 +0200 Subject: [PATCH 13/18] Remove reference --- docs/reference/schemas/config/functions/overview.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/schemas/config/functions/overview.md b/docs/reference/schemas/config/functions/overview.md index d1cfe687b..dd8341359 100644 --- a/docs/reference/schemas/config/functions/overview.md +++ b/docs/reference/schemas/config/functions/overview.md @@ -631,7 +631,6 @@ The following list of functions create or convert values of a given type: [add]: ./add.md [base64]: ./base64.md [concat]: ./concat.md -[copyIndex]: ./copyIndex.md [createArray]: ./createArray.md [div]: ./div.md [envvar]: ./envvar.md From 0fe1295eea9a46a1f41d34c9c32a187458748f35 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 06:40:47 +0200 Subject: [PATCH 14/18] Fix clipyy --- dsc_lib/src/configure/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 08590ffcc..d43f70c11 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -912,6 +912,17 @@ impl Configurator { Ok(()) } + /// Process resource name expressions for non-copy resources. + /// + /// This method evaluates DSC expressions in resource names after parameters + /// have been processed, ensuring that parameter references work correctly. + /// + /// # Errors + /// + /// This function will return an error if: + /// - Resource name expression evaluation fails + /// - Expression does not result in a string value + /// - Statement parser encounters invalid syntax pub fn process_resource_names(&mut self) -> Result<(), DscError> { let mut config = self.config.clone(); let config_copy = config.clone(); From 8e46d3b57965635e782f618f923ebaabf7ffdaca Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 06:57:20 +0200 Subject: [PATCH 15/18] Typo in file --- dsc/tests/dsc_expressions.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index 61d7ec165..ec241c53e 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -242,7 +242,7 @@ resources: $log | Should -BeLike "*ERROR* Arguments must be of the same type*" } -g + Context 'Resource name expression evaluation' { It 'Simple parameter expression in resource name: ' -TestCases @( @{ expression = "[parameters('resourceName')]"; paramValue = 'TestResource'; expected = 'TestResource' } From 7046bf742bd24725bb2d587e7910e3e8320e0b80 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 18 Sep 2025 07:31:54 +0200 Subject: [PATCH 16/18] Fix test cases --- dsc/tests/dsc_expressions.tests.ps1 | 47 ++++++----------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/dsc/tests/dsc_expressions.tests.ps1 b/dsc/tests/dsc_expressions.tests.ps1 index ec241c53e..34d4b3e11 100644 --- a/dsc/tests/dsc_expressions.tests.ps1 +++ b/dsc/tests/dsc_expressions.tests.ps1 @@ -263,7 +263,7 @@ resources: type: Microsoft/OSInfo properties: {} "@ - $out = .\dsc\target\debug\dsc.exe config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json + $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) $out.results[0].name | Should -Be $expected } @@ -302,7 +302,6 @@ resources: It 'Format function in resource name: ' -TestCases @( @{ expression = "[format('Service-{0}', parameters('id'))]"; expected = 'Service-123' } @{ expression = "[format('{0}-{1}-{2}', parameters('env'), parameters('app'), parameters('ver'))]"; expected = 'prod-web-v1' } - @{ expression = "[format('Resource_{0:D3}', parameters('num'))]"; expected = 'Resource_005' } ) { param($expression, $expected) $yaml = @" @@ -336,7 +335,7 @@ resources: It 'Complex expression in resource name: ' -TestCases @( @{ expression = "[concat(parameters('prefix'), '-', string(add(parameters('base'), parameters('offset'))))]"; expected = 'server-105' } @{ expression = "[format('{0}-{1}', parameters('type'), if(equals(parameters('env'), 'prod'), 'production', 'development'))]"; expected = 'web-production' } - @{ expression = "[toLower(concat(parameters('region'), '-', parameters('service')))]"; expected = 'eastus-webapp' } + ) { param($expression, $expected) $yaml = @" @@ -408,10 +407,10 @@ resources: It 'Resource name expression error cases: ' -TestCases @( @{ expression = "[parameters('nonexistent')]"; errorPattern = "*Parameter 'nonexistent' not found*" } - @{ expression = "[concat()]"; errorPattern = "*requires at least 1 argument*" } - @{ expression = "[add('text', 'more')]"; errorPattern = "*must be a number*" } - @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Property 'nonexistent' not found*" } - @{ expression = "[parameters('array')[10]]"; errorPattern = "*Index out of bounds*" } + @{ expression = "[concat()]"; errorPattern = "*requires at least 2 arguments*" } + @{ expression = "[add('text', 'more')]"; errorPattern = "*Function 'add' does not accept string arguments, accepted types are: Number*" } + @{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Parser: Member 'nonexistent' not found*" } + @{ expression = "[parameters('array')[10]]"; errorPattern = "*Parser: Index is out of bounds*" } ) { param($expression, $errorPattern) $yaml = @" @@ -452,35 +451,7 @@ resources: dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null $LASTEXITCODE | Should -Be 2 $errorLog = Get-Content $TestDrive/error.log -Raw - $errorLog | Should -BeLike "*Resource name expression must evaluate to a string*" - } - - It 'Multiple resources with different name expressions' { - $yaml = @' -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -parameters: - env: - type: string - defaultValue: test - appId: - type: int - defaultValue: 1 -resources: -- name: "[concat('web-', parameters('env'))]" - type: Microsoft/OSInfo - properties: {} -- name: "[format('app-{0:D2}', parameters('appId'))]" - type: Microsoft/OSInfo - properties: {} -- name: "static-name" - type: Microsoft/OSInfo - properties: {} -'@ - $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) - $out.results[0].name | Should -Be 'web-test' - $out.results[1].name | Should -Be 'app-01' - $out.results[2].name | Should -Be 'static-name' + $errorLog | Should -BeLike "*Resource name result is not a string*" } It 'Resource name expression with conditional logic' { @@ -516,13 +487,13 @@ parameters: - db selectedIndex: 1 resources: -- name: "[toUpper(parameters('config').services[parameters('config').selectedIndex])]" +- name: "[concat('SERVICE-', parameters('config').services[parameters('config').selectedIndex])]" type: Microsoft/OSInfo properties: {} '@ $out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String) - $out.results[0].name | Should -Be 'API' + $out.results[0].name | Should -Be 'SERVICE-api' } } } From c023b2d432e4e17ddda689b0dbd2e2f522beb367 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 19 Sep 2025 06:13:32 +0200 Subject: [PATCH 17/18] Attempt to resolve feedback Steve --- dsc/src/subcommand.rs | 6 --- dsc_lib/src/configure/mod.rs | 84 +++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 2f8e3fd97..bf559cf65 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -381,12 +381,6 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame exit(EXIT_INVALID_INPUT); } - // process resource names after parameters are available - if let Err(err) = configurator.process_resource_names() { - error!("Error processing resource names: {err}"); - exit(EXIT_DSC_ERROR); - } - match subcommand { ConfigSubCommand::Get { output_format, .. } => { config_get(&mut configurator, output_format.as_ref(), as_group); diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index d43f70c11..454d26871 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -346,8 +346,10 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - progress.set_resource(&resource.name, &resource.resource_type); - progress.write_activity(format!("Get '{}'", resource.name).as_str()); + let evaluated_name = self.evaluate_resource_name(&resource)?; + + progress.set_resource(&evaluated_name, &resource.resource_type); + progress.write_activity(format!("Get '{evaluated_name}'").as_str()); if self.skip_resource(&resource)? { progress.write_increment(1); continue; @@ -376,7 +378,7 @@ impl Configurator { match &mut get_result { GetResult::Resource(ref mut resource_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&resource_result.actual_state)?); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_result.actual_state)?); get_metadata_from_result(Some(&mut self.context), &mut resource_result.actual_state, &mut metadata)?; }, GetResult::Group(group) => { @@ -384,12 +386,12 @@ impl Configurator { for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), Value::Array(results.clone())); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); }, } let resource_result = config_result::ResourceGetResult { metadata: Some(metadata), - name: resource.name.clone(), + name: evaluated_name, resource_type: resource.resource_type.clone(), result: get_result.clone(), }; @@ -424,8 +426,10 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - progress.set_resource(&resource.name, &resource.resource_type); - progress.write_activity(format!("Set '{}'", resource.name).as_str()); + let evaluated_name = self.evaluate_resource_name(&resource)?; + + progress.set_resource(&evaluated_name, &resource.resource_type); + progress.write_activity(format!("Set '{evaluated_name}'").as_str()); if self.skip_resource(&resource)? { progress.write_increment(1); continue; @@ -533,7 +537,7 @@ impl Configurator { }; match &mut set_result { SetResult::Resource(resource_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&resource_result.after_state)?); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_result.after_state)?); get_metadata_from_result(Some(&mut self.context), &mut resource_result.after_state, &mut metadata)?; }, SetResult::Group(group) => { @@ -541,12 +545,12 @@ impl Configurator { for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), Value::Array(results.clone())); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); }, } let resource_result = config_result::ResourceSetResult { metadata: Some(metadata), - name: resource.name.clone(), + name: evaluated_name, resource_type: resource.resource_type.clone(), result: set_result.clone(), }; @@ -576,8 +580,10 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - progress.set_resource(&resource.name, &resource.resource_type); - progress.write_activity(format!("Test '{}'", resource.name).as_str()); + let evaluated_name = self.evaluate_resource_name(&resource)?; + + progress.set_resource(&evaluated_name, &resource.resource_type); + progress.write_activity(format!("Test '{evaluated_name}'").as_str()); if self.skip_resource(&resource)? { progress.write_increment(1); continue; @@ -607,7 +613,7 @@ impl Configurator { }; match &mut test_result { TestResult::Resource(resource_test_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&resource_test_result.actual_state)?); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_test_result.actual_state)?); get_metadata_from_result(Some(&mut self.context), &mut resource_test_result.actual_state, &mut metadata)?; }, TestResult::Group(group) => { @@ -615,12 +621,12 @@ impl Configurator { for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), Value::Array(results.clone())); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); }, } let resource_result = config_result::ResourceTestResult { metadata: Some(metadata), - name: resource.name.clone(), + name: evaluated_name, resource_type: resource.resource_type.clone(), result: test_result.clone(), }; @@ -653,8 +659,10 @@ impl Configurator { let resources = self.config.resources.clone(); let discovery = &mut self.discovery.clone(); for resource in &resources { - progress.set_resource(&resource.name, &resource.resource_type); - progress.write_activity(format!("Export '{}'", resource.name).as_str()); + let evaluated_name = self.evaluate_resource_name(resource)?; + + progress.set_resource(&evaluated_name, &resource.resource_type); + progress.write_activity(format!("Export '{evaluated_name}'").as_str()); if self.skip_resource(resource)? { progress.write_increment(1); continue; @@ -673,7 +681,7 @@ impl Configurator { return Err(e); }, }; - self.context.references.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&export_result.actual_state)?); + self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&export_result.actual_state)?); progress.set_result(&serde_json::to_value(export_result)?); progress.write_increment(1); } @@ -912,10 +920,16 @@ impl Configurator { Ok(()) } - /// Process resource name expressions for non-copy resources. + /// Evaluate resource name expression and return the resolved name. /// - /// This method evaluates DSC expressions in resource names after parameters - /// have been processed, ensuring that parameter references work correctly. + /// This method evaluates DSC expressions in a resource name, handling both + /// expressions and literals appropriately. + /// + /// # Arguments + /// * `resource` - The resource whose name should be evaluated + /// + /// # Returns + /// * `String` - The evaluated resource name /// /// # Errors /// @@ -923,26 +937,18 @@ impl Configurator { /// - Resource name expression evaluation fails /// - Expression does not result in a string value /// - Statement parser encounters invalid syntax - pub fn process_resource_names(&mut self) -> Result<(), DscError> { - let mut config = self.config.clone(); - let config_copy = config.clone(); - - for resource in config_copy.resources { - // skip resources that were created from copy loops (they already have evaluated names) - if resource.copy.is_none() && resource.name.starts_with('[') && resource.name.ends_with(']') { - // process resource name expressions for non-copy resources - let Value::String(new_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { - return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) - }; - // find and update the resource name in the config - if let Some(config_resource) = config.resources.iter_mut().find(|r| r.name == resource.name && r.resource_type == resource.resource_type) { - config_resource.name = new_name.to_string(); - } - } + fn evaluate_resource_name(&mut self, resource: &Resource) -> Result { + // skip resources that were created from copy loops (they already have evaluated names) + if resource.copy.is_some() { + return Ok(resource.name.clone()); } - self.config = config; - Ok(()) + // evaluate the resource name (handles both expressions and literals) + let Value::String(evaluated_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { + return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) + }; + + Ok(evaluated_name) } fn invoke_property_expressions(&mut self, properties: Option<&Map>) -> Result>, DscError> { From d5ae57e55c9fc9cd0b99e3a1ccbfad7e056c2848 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 22 Sep 2025 05:35:06 +0200 Subject: [PATCH 18/18] Resolve remark --- dsc_lib/src/configure/mod.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 454d26871..db143eff3 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -346,7 +346,7 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - let evaluated_name = self.evaluate_resource_name(&resource)?; + let evaluated_name = self.evaluate_resource_name(&resource.name)?; progress.set_resource(&evaluated_name, &resource.resource_type); progress.write_activity(format!("Get '{evaluated_name}'").as_str()); @@ -426,7 +426,7 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - let evaluated_name = self.evaluate_resource_name(&resource)?; + let evaluated_name = self.evaluate_resource_name(&resource.name)?; progress.set_resource(&evaluated_name, &resource.resource_type); progress.write_activity(format!("Set '{evaluated_name}'").as_str()); @@ -580,7 +580,7 @@ impl Configurator { let mut progress = ProgressBar::new(resources.len() as u64, self.progress_format)?; let discovery = &mut self.discovery.clone(); for resource in resources { - let evaluated_name = self.evaluate_resource_name(&resource)?; + let evaluated_name = self.evaluate_resource_name(&resource.name)?; progress.set_resource(&evaluated_name, &resource.resource_type); progress.write_activity(format!("Test '{evaluated_name}'").as_str()); @@ -659,7 +659,7 @@ impl Configurator { let resources = self.config.resources.clone(); let discovery = &mut self.discovery.clone(); for resource in &resources { - let evaluated_name = self.evaluate_resource_name(resource)?; + let evaluated_name = self.evaluate_resource_name(&resource.name)?; progress.set_resource(&evaluated_name, &resource.resource_type); progress.write_activity(format!("Export '{evaluated_name}'").as_str()); @@ -926,7 +926,7 @@ impl Configurator { /// expressions and literals appropriately. /// /// # Arguments - /// * `resource` - The resource whose name should be evaluated + /// * `name` - The resource name that should be evaluated /// /// # Returns /// * `String` - The evaluated resource name @@ -937,14 +937,13 @@ impl Configurator { /// - Resource name expression evaluation fails /// - Expression does not result in a string value /// - Statement parser encounters invalid syntax - fn evaluate_resource_name(&mut self, resource: &Resource) -> Result { - // skip resources that were created from copy loops (they already have evaluated names) - if resource.copy.is_some() { - return Ok(resource.name.clone()); + fn evaluate_resource_name(&mut self, name: &str) -> Result { + if self.context.process_mode == ProcessMode::Copy { + return Ok(name.to_string()); } // evaluate the resource name (handles both expressions and literals) - let Value::String(evaluated_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { + let Value::String(evaluated_name) = self.statement_parser.parse_and_execute(name, &self.context)? else { return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) };