Skip to content

Commit 23b4e37

Browse files
authored
Merge branch 'main' into gh-1093/main/invoke-mcp-config
2 parents 4b20ca9 + bc5639b commit 23b4e37

File tree

7 files changed

+453
-0
lines changed

7 files changed

+453
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
description: Reference for the 'last' DSC configuration document function
3+
ms.date: 01/25/2025
4+
ms.topic: reference
5+
title: last
6+
---
7+
8+
## Synopsis
9+
10+
Returns the last element of an array, or the last character of a string.
11+
12+
## Syntax
13+
14+
```Syntax
15+
last(arg)
16+
```
17+
18+
## Description
19+
20+
The `last()` function returns the final element from an array or the final
21+
character from a string. This is useful when you need to access the most recent
22+
item in a sequence, the final stage in a deployment pipeline, or the last
23+
character in a configuration value.
24+
25+
For arrays, it returns the element at index `length - 1`. For strings, it
26+
returns the last character as a string.
27+
28+
## Examples
29+
30+
### Example 1 - Extract the final deployment stage (array of strings)
31+
32+
Use `last()` to retrieve the final stage in a multi-stage deployment pipeline.
33+
This helps you identify which environment or phase should receive special
34+
handling, such as extended health checks or manual approval gates. This example
35+
uses [`createArray()`][01] to build the deployment stages.
36+
37+
```yaml
38+
# last.example.1.dsc.config.yaml
39+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
40+
resources:
41+
- name: Deployment Pipeline
42+
type: Microsoft.DSC.Debug/Echo
43+
properties:
44+
output:
45+
finalStage: "[last(createArray('dev', 'test', 'staging', 'production'))]"
46+
requiresApproval: true
47+
```
48+
49+
```bash
50+
dsc config get --file last.example.1.dsc.config.yaml
51+
```
52+
53+
```yaml
54+
results:
55+
- name: Deployment Pipeline
56+
type: Microsoft.DSC.Debug/Echo
57+
result:
58+
actualState:
59+
output:
60+
finalStage: production
61+
requiresApproval: true
62+
messages: []
63+
hadErrors: false
64+
```
65+
66+
This identifies `production` as the final stage, allowing you to apply
67+
production-specific policies or validations.
68+
69+
### Example 2 - Get the last character of a configuration string
70+
71+
Use `last()` to extract the final character from a string value. This is useful
72+
for parsing identifiers, checking suffixes, or validating format conventions
73+
like version numbers or region codes.
74+
75+
```yaml
76+
# last.example.2.dsc.config.yaml
77+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
78+
resources:
79+
- name: Region Identifier
80+
type: Microsoft.DSC.Debug/Echo
81+
properties:
82+
output:
83+
regionCode: us-west-2
84+
zoneSuffix: "[last('us-west-2')]"
85+
description: "Zone suffix extracted from region code"
86+
```
87+
88+
```bash
89+
dsc config get --file last.example.2.dsc.config.yaml
90+
```
91+
92+
```yaml
93+
results:
94+
- name: Region Identifier
95+
type: Microsoft.DSC.Debug/Echo
96+
result:
97+
actualState:
98+
output:
99+
regionCode: us-west-2
100+
zoneSuffix: '2'
101+
description: Zone suffix extracted from region code
102+
messages: []
103+
hadErrors: false
104+
```
105+
106+
The function returns `'2'` as a single-character string, representing the zone
107+
suffix in the region identifier.
108+
109+
### Example 3 - Identify the most recent backup (array of numbers)
110+
111+
Use `last()` with numerical arrays to find the most recent timestamp or version
112+
number. This example shows how to select the latest backup from a sorted list
113+
of timestamps. This example uses [`createArray()`][01] to build the backup
114+
timestamps.
115+
116+
```yaml
117+
# last.example.3.dsc.config.yaml
118+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
119+
resources:
120+
- name: Backup Selection
121+
type: Microsoft.DSC.Debug/Echo
122+
properties:
123+
output:
124+
availableBackups: "[createArray(1704067200, 1704153600, 1704240000, 1704326400)]"
125+
latestBackup: "[last(createArray(1704067200, 1704153600, 1704240000, 1704326400))]"
126+
description: "Most recent backup timestamp (Unix epoch)"
127+
```
128+
129+
```bash
130+
dsc config get --file last.example.3.dsc.config.yaml
131+
```
132+
133+
```yaml
134+
results:
135+
- name: Backup Selection
136+
type: Microsoft.DSC.Debug/Echo
137+
result:
138+
actualState:
139+
output:
140+
availableBackups:
141+
- 1704067200
142+
- 1704153600
143+
- 1704240000
144+
- 1704326400
145+
latestBackup: 1704326400
146+
description: Most recent backup timestamp (Unix epoch)
147+
messages: []
148+
hadErrors: false
149+
```
150+
151+
The function returns `1704326400`, which represents the most recent backup in
152+
the chronologically sorted array.
153+
154+
### Example 4 - Combine with other functions for complex logic
155+
156+
Use `last()` together with [`split()`][02] to extract file extensions or path
157+
components. This example demonstrates parsing a filename to get its extension.
158+
159+
```yaml
160+
# last.example.4.dsc.config.yaml
161+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
162+
resources:
163+
- name: File Extension Parser
164+
type: Microsoft.DSC.Debug/Echo
165+
properties:
166+
output:
167+
filename: config.production.yaml
168+
extension: "[last(split('config.production.yaml', '.'))]"
169+
```
170+
171+
```bash
172+
dsc config get --file last.example.4.dsc.config.yaml
173+
```
174+
175+
```yaml
176+
results:
177+
- name: File Extension Parser
178+
type: Microsoft.DSC.Debug/Echo
179+
result:
180+
actualState:
181+
output:
182+
filename: config.production.yaml
183+
extension: yaml
184+
messages: []
185+
hadErrors: false
186+
```
187+
188+
By combining `split()` and `last()`, you can extract the `yaml` extension from
189+
the full filename.
190+
191+
## Parameters
192+
193+
### arg
194+
195+
The array or string to get the last element or character from. Required.
196+
197+
```yaml
198+
Type: array | string
199+
Required: true
200+
Position: 1
201+
```
202+
203+
## Output
204+
205+
Returns the last element of the array (preserving its original type) or the
206+
last character as a string. For arrays, the return type matches the element
207+
type. For strings, returns a single-character string.
208+
209+
If the input is an empty array, the function returns `null`. If the input is an
210+
empty string, the function returns an empty string.
211+
212+
```yaml
213+
Type: any | string | null
214+
```
215+
216+
## Errors
217+
218+
The function returns an error in the following cases:
219+
220+
- **Invalid type**: The argument is not an array or string
221+
222+
## Related functions
223+
224+
- [`first()`][00] - Returns the first element of an array or character of a string
225+
- [`split()`][02] - Splits a string into an array
226+
- [`createArray()`][01] - Creates an array from provided values
227+
228+
<!-- Link reference definitions -->
229+
[00]: ./first.md
230+
[01]: ./createArray.md
231+
[02]: ./split.md

dsc/tests/dsc_copy.tests.ps1

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,75 @@ resources:
263263
$out.results[3].name | Should -Be 'Server-3'
264264
$out.results[3].result.actualState.output | Should -Be 'Hello'
265265
}
266+
267+
It 'Copy works with copyIndex() in properties' {
268+
$configYaml = @'
269+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
270+
resources:
271+
- name: "[format('Server-{0}', copyIndex())]"
272+
copy:
273+
name: testLoop
274+
count: 3
275+
type: Microsoft.DSC.Debug/Echo
276+
properties:
277+
output: "[format('Instance-{0}', copyIndex())]"
278+
'@
279+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
280+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $testdrive/error.log -Raw | Out-String)
281+
$out.results.Count | Should -Be 3
282+
$out.results[0].name | Should -Be 'Server-0'
283+
$out.results[0].result.actualState.output | Should -Be 'Instance-0'
284+
$out.results[1].name | Should -Be 'Server-1'
285+
$out.results[1].result.actualState.output | Should -Be 'Instance-1'
286+
$out.results[2].name | Should -Be 'Server-2'
287+
$out.results[2].result.actualState.output | Should -Be 'Instance-2'
288+
}
289+
290+
It 'Copy works with copyIndex() with offset in properties' {
291+
$configYaml = @'
292+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
293+
resources:
294+
- name: "[format('Server-{0}', copyIndex())]"
295+
copy:
296+
name: testLoop
297+
count: 3
298+
type: Microsoft.DSC.Debug/Echo
299+
properties:
300+
output: "[format('Port-{0}', copyIndex(8080))]"
301+
'@
302+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
303+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $testdrive/error.log -Raw | Out-String)
304+
$out.results.Count | Should -Be 3
305+
$out.results[0].name | Should -Be 'Server-0'
306+
$out.results[0].result.actualState.output | Should -Be 'Port-8080'
307+
$out.results[1].name | Should -Be 'Server-1'
308+
$out.results[1].result.actualState.output | Should -Be 'Port-8081'
309+
$out.results[2].name | Should -Be 'Server-2'
310+
$out.results[2].result.actualState.output | Should -Be 'Port-8082'
311+
}
312+
313+
It 'Copy works with parameters and copyIndex() combined in properties' {
314+
$configYaml = @'
315+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
316+
parameters:
317+
prefix:
318+
type: string
319+
defaultValue: web
320+
resources:
321+
- name: "[format('Server-{0}', copyIndex())]"
322+
copy:
323+
name: testLoop
324+
count: 2
325+
type: Microsoft.DSC.Debug/Echo
326+
properties:
327+
output: "[concat(parameters('prefix'), '-', string(copyIndex(1)))]"
328+
'@
329+
$out = dsc -l trace config get -i $configYaml 2>$testdrive/error.log | ConvertFrom-Json
330+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $testdrive/error.log -Raw | Out-String)
331+
$out.results.Count | Should -Be 2
332+
$out.results[0].name | Should -Be 'Server-0'
333+
$out.results[0].result.actualState.output | Should -Be 'web-1'
334+
$out.results[1].name | Should -Be 'Server-1'
335+
$out.results[1].result.actualState.output | Should -Be 'web-2'
336+
}
266337
}

dsc/tests/dsc_functions.tests.ps1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,30 @@ Describe 'tests for function expressions' {
504504
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
505505
}
506506

507+
It 'last function works for: <expression>' -TestCases @(
508+
@{ expression = "[last(createArray('hello', 'world'))]"; expected = 'world' }
509+
@{ expression = "[last(createArray(1, 2, 3))]"; expected = 3 }
510+
@{ expression = "[last('hello')]"; expected = 'o' }
511+
@{ expression = "[last('a')]"; expected = 'a' }
512+
@{ expression = "[last(array('mixed'))]"; expected = 'mixed' }
513+
@{ expression = "[last(createArray())]"; expected = $null }
514+
@{ expression = "[last('')]"; expected = '' }
515+
) {
516+
param($expression, $expected)
517+
518+
$config_yaml = @"
519+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
520+
resources:
521+
- name: Echo
522+
type: Microsoft.DSC.Debug/Echo
523+
properties:
524+
output: "$expression"
525+
"@
526+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
527+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
528+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
529+
}
530+
507531
It 'indexOf function works for: <expression>' -TestCases @(
508532
@{ expression = "[indexOf(createArray('apple', 'banana', 'cherry'), 'banana')]"; expected = 1 }
509533
@{ expression = "[indexOf(createArray('apple', 'banana', 'cherry'), 'cherry')]"; expected = 2 }

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ emptyArray = "Cannot get first element of empty array"
343343
emptyString = "Cannot get first character of empty string"
344344
invalidArgType = "Invalid argument type, argument must be an array or string"
345345

346+
[functions.last]
347+
description = "Returns the last element of an array or last character of a string"
348+
invoked = "last function"
349+
invalidArgType = "Invalid argument type, argument must be an array or string"
350+
346351
[functions.greater]
347352
description = "Evaluates if the first value is greater than the second value"
348353
invoked = "greater function"

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,10 @@ impl Configurator {
10371037
};
10381038
new_resource.name = new_name.to_string();
10391039

1040+
if let Some(properties) = &resource.properties {
1041+
new_resource.properties = self.invoke_property_expressions(Some(properties))?;
1042+
}
1043+
10401044
new_resource.copy = None;
10411045
copy_resources.push(new_resource);
10421046
}

0 commit comments

Comments
 (0)