Skip to content

Commit 95a5241

Browse files
authored
Merge pull request #1117 from Gijsreyn/gh-1116/main/support-expression-validation-name
Support expression validation in resource name
2 parents 3f98ff7 + d5ae57e commit 95a5241

File tree

3 files changed

+311
-18
lines changed

3 files changed

+311
-18
lines changed

dsc/tests/dsc_expressions.tests.ps1

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,258 @@ resources:
242242
$log | Should -BeLike "*ERROR* Arguments must be of the same type*"
243243

244244
}
245+
246+
Context 'Resource name expression evaluation' {
247+
It 'Simple parameter expression in resource name: <expression>' -TestCases @(
248+
@{ expression = "[parameters('resourceName')]"; paramValue = 'TestResource'; expected = 'TestResource' }
249+
@{ expression = "[parameters('serviceName')]"; paramValue = 'MyService'; expected = 'MyService' }
250+
) {
251+
param($expression, $paramValue, $expected)
252+
$yaml = @"
253+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
254+
parameters:
255+
resourceName:
256+
type: string
257+
defaultValue: $paramValue
258+
serviceName:
259+
type: string
260+
defaultValue: $paramValue
261+
resources:
262+
- name: "$expression"
263+
type: Microsoft/OSInfo
264+
properties: {}
265+
"@
266+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
267+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
268+
$out.results[0].name | Should -Be $expected
269+
}
270+
271+
It 'Concat function in resource name: <expression>' -TestCases @(
272+
@{ expression = "[concat('prefix-', parameters('name'))]"; paramValue = 'test'; expected = 'prefix-test' }
273+
@{ expression = "[concat(parameters('prefix'), '-', parameters('suffix'))]"; expected = 'start-end' }
274+
@{ expression = "[concat('Resource-', string(parameters('index')))]"; expected = 'Resource-42' }
275+
) {
276+
param($expression, $paramValue, $expected)
277+
$yaml = @"
278+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
279+
parameters:
280+
name:
281+
type: string
282+
defaultValue: ${paramValue}
283+
prefix:
284+
type: string
285+
defaultValue: start
286+
suffix:
287+
type: string
288+
defaultValue: end
289+
index:
290+
type: int
291+
defaultValue: 42
292+
resources:
293+
- name: "$expression"
294+
type: Microsoft/OSInfo
295+
properties: {}
296+
"@
297+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
298+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
299+
$out.results[0].name | Should -Be $expected
300+
}
301+
302+
It 'Format function in resource name: <expression>' -TestCases @(
303+
@{ expression = "[format('Service-{0}', parameters('id'))]"; expected = 'Service-123' }
304+
@{ expression = "[format('{0}-{1}-{2}', parameters('env'), parameters('app'), parameters('ver'))]"; expected = 'prod-web-v1' }
305+
) {
306+
param($expression, $expected)
307+
$yaml = @"
308+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
309+
parameters:
310+
id:
311+
type: string
312+
defaultValue: '123'
313+
env:
314+
type: string
315+
defaultValue: prod
316+
app:
317+
type: string
318+
defaultValue: web
319+
ver:
320+
type: string
321+
defaultValue: v1
322+
num:
323+
type: int
324+
defaultValue: 5
325+
resources:
326+
- name: "$expression"
327+
type: Microsoft/OSInfo
328+
properties: {}
329+
"@
330+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
331+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
332+
$out.results[0].name | Should -Be $expected
333+
}
334+
335+
It 'Complex expression in resource name: <expression>' -TestCases @(
336+
@{ expression = "[concat(parameters('prefix'), '-', string(add(parameters('base'), parameters('offset'))))]"; expected = 'server-105' }
337+
@{ expression = "[format('{0}-{1}', parameters('type'), if(equals(parameters('env'), 'prod'), 'production', 'development'))]"; expected = 'web-production' }
338+
339+
) {
340+
param($expression, $expected)
341+
$yaml = @"
342+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
343+
parameters:
344+
prefix:
345+
type: string
346+
defaultValue: server
347+
base:
348+
type: int
349+
defaultValue: 100
350+
offset:
351+
type: int
352+
defaultValue: 5
353+
type:
354+
type: string
355+
defaultValue: web
356+
env:
357+
type: string
358+
defaultValue: prod
359+
region:
360+
type: string
361+
defaultValue: EASTUS
362+
service:
363+
type: string
364+
defaultValue: WebApp
365+
resources:
366+
- name: "$expression"
367+
type: Microsoft/OSInfo
368+
properties: {}
369+
"@
370+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
371+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
372+
$out.results[0].name | Should -Be $expected
373+
}
374+
375+
It 'Expression with object parameter access: <expression>' -TestCases @(
376+
@{ expression = "[parameters('config').name]"; expected = 'MyApp' }
377+
@{ expression = "[concat(parameters('config').prefix, '-', parameters('config').id)]"; expected = 'app-001' }
378+
@{ expression = "[parameters('servers')[0]]"; expected = 'web01' }
379+
@{ expression = "[parameters('servers')[parameters('config').index]]"; expected = 'db01' }
380+
) {
381+
param($expression, $expected)
382+
$yaml = @"
383+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
384+
parameters:
385+
config:
386+
type: object
387+
defaultValue:
388+
name: MyApp
389+
prefix: app
390+
id: '001'
391+
index: 1
392+
servers:
393+
type: array
394+
defaultValue:
395+
- web01
396+
- db01
397+
- cache01
398+
resources:
399+
- name: "$expression"
400+
type: Microsoft/OSInfo
401+
properties: {}
402+
"@
403+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
404+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
405+
$out.results[0].name | Should -Be $expected
406+
}
407+
408+
It 'Resource name expression error cases: <expression>' -TestCases @(
409+
@{ expression = "[parameters('nonexistent')]"; errorPattern = "*Parameter 'nonexistent' not found*" }
410+
@{ expression = "[concat()]"; errorPattern = "*requires at least 2 arguments*" }
411+
@{ expression = "[add('text', 'more')]"; errorPattern = "*Function 'add' does not accept string arguments, accepted types are: Number*" }
412+
@{ expression = "[parameters('config').nonexistent]"; errorPattern = "*Parser: Member 'nonexistent' not found*" }
413+
@{ expression = "[parameters('array')[10]]"; errorPattern = "*Parser: Index is out of bounds*" }
414+
) {
415+
param($expression, $errorPattern)
416+
$yaml = @"
417+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
418+
parameters:
419+
config:
420+
type: object
421+
defaultValue:
422+
name: test
423+
array:
424+
type: array
425+
defaultValue:
426+
- item1
427+
- item2
428+
resources:
429+
- name: "$expression"
430+
type: Microsoft/OSInfo
431+
properties: {}
432+
"@
433+
dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null
434+
$LASTEXITCODE | Should -Be 2
435+
$errorLog = Get-Content $TestDrive/error.log -Raw
436+
$errorLog | Should -BeLike $errorPattern
437+
}
438+
439+
It 'Resource name expression must evaluate to string' {
440+
$yaml = @'
441+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
442+
parameters:
443+
number:
444+
type: int
445+
defaultValue: 42
446+
resources:
447+
- name: "[parameters('number')]"
448+
type: Microsoft/OSInfo
449+
properties: {}
450+
'@
451+
dsc config get -i $yaml 2>$TestDrive/error.log | Out-Null
452+
$LASTEXITCODE | Should -Be 2
453+
$errorLog = Get-Content $TestDrive/error.log -Raw
454+
$errorLog | Should -BeLike "*Resource name result is not a string*"
455+
}
456+
457+
It 'Resource name expression with conditional logic' {
458+
$yaml = @'
459+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
460+
parameters:
461+
isProd:
462+
type: bool
463+
defaultValue: true
464+
serviceName:
465+
type: string
466+
defaultValue: api
467+
resources:
468+
- name: "[concat(parameters('serviceName'), if(parameters('isProd'), '-prod', '-dev'))]"
469+
type: Microsoft/OSInfo
470+
properties: {}
471+
'@
472+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
473+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
474+
$out.results[0].name | Should -Be 'api-prod'
475+
}
476+
477+
It 'Resource name with nested function calls' {
478+
$yaml = @'
479+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
480+
parameters:
481+
config:
482+
type: object
483+
defaultValue:
484+
services:
485+
- web
486+
- api
487+
- db
488+
selectedIndex: 1
489+
resources:
490+
- name: "[concat('SERVICE-', parameters('config').services[parameters('config').selectedIndex])]"
491+
type: Microsoft/OSInfo
492+
properties: {}
493+
'@
494+
$out = dsc config get -i $yaml 2>$TestDrive/error.log | ConvertFrom-Json
495+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
496+
$out.results[0].name | Should -Be 'SERVICE-api'
497+
}
498+
}
245499
}

dsc_lib/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ unrollingCopy = "Unrolling copy for resource '%{name}' with count %{count}"
7676
copyModeNotSupported = "Copy mode is not supported"
7777
copyBatchSizeNotSupported = "Copy batch size is not supported"
7878
copyNameResultNotString = "Copy name result is not a string"
79+
nameResultNotString = "Resource name result is not a string"
7980
userFunctionAlreadyDefined = "User function '%{name}' in namespace '%{namespace}' is already defined"
8081
addingUserFunction = "Adding user function '%{name}'"
8182

0 commit comments

Comments
 (0)