Skip to content

Commit fe8eba6

Browse files
Resolve-JSONContent: Fixes for Inner Template (#488)
* Resolve-JSONContent: Casting TargetIndex to int to avoid comparison isuses. * Resolve-JSONContent: Support for bracket escape sequence. Fixes #468. * Expand-AzTemplate: More gracefully handling problems extracting innerTemplates * Expand-AzTemplate: Expanding innermost templates first. This avoids issues with recursive templates. * Erroring instead of warning when innertemplate extraction fails * Updating tests for Resolve-JSONContent (adding test for bracket escape sequences) * Adding innerTemplate module tests (nested templates and escape sequences) * Fixing typo in innerTemplates test * Fixing JSONC test (ensuring it only tests the single file).
1 parent d7f6cd7 commit fe8eba6

File tree

8 files changed

+527
-28
lines changed

8 files changed

+527
-28
lines changed

arm-ttk/Expand-AzTemplate.ps1

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,17 +292,25 @@ function Expand-AzTemplate
292292

293293
$innerTemplates = @(if ($templateText -and $TemplateText.Contains('"template"')) {
294294
Find-JsonContent -InputObject $templateObject -Key template |
295-
Where-Object { $_.expressionEvaluationOptions.scope -eq 'inner' -or $_.jsonPath -like '*.policyRule.*' }
295+
Where-Object { $_.expressionEvaluationOptions.scope -eq 'inner' -or $_.jsonPath -like '*.policyRule.*' } |
296+
Sort-Object JSONPath -Descending
296297
})
297298

298299
if ($innerTemplates) {
300+
$anyProblems = $false
301+
$originalTemplateText = "$TemplateText"
299302
foreach ($it in $innerTemplates) {
300303
$foundInnerTemplate = $it | Resolve-JSONContent -JsonText $TemplateText
304+
if (-not $foundInnerTemplate) { $anyProblems = $true; break }
301305
$TemplateText = $TemplateText.Remove($foundInnerTemplate.Index, $foundInnerTemplate.Length)
302306
$TemplateText = $TemplateText.Insert($foundInnerTemplate.Index, '"template": {}')
303307
}
304308

305-
$TemplateObject = $TemplateText | ConvertFrom-Json
309+
if (-not $anyProblems) {
310+
$TemplateObject = $TemplateText | ConvertFrom-Json
311+
} else {
312+
Write-Error "Could not extract inner templates for '$TemplatePath'." -ErrorId InnerTemplate.Extraction.Error
313+
}
306314
}
307315

308316

arm-ttk/Resolve-JSONContent.ps1

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
[string]
3535
$JSONText
3636
)
37-
begin {
37+
begin {
3838
$jsonProperty = [Regex]::new(@'
3939
(?<= # After
4040
[\{\,] # a bracket or comma
@@ -65,20 +65,21 @@
6565
\{ # An open brace
6666
(?> # Followed by...
6767
[^\{\}]+| # any number of non-brace character OR
68-
\{(?<Depth>)| # an open brace (in which case increment depth) OR
69-
\}(?<-Depth>) # a closed brace (in which case decrement depth)
70-
)*(?(Depth)(?!)) # until depth is 0.
68+
\{(?<BraceDepth>)| # an open brace (in which case increment depth) OR
69+
\}(?<-BraceDepth>) # a closed brace (in which case decrement depth)
70+
)*(?(BraceDepth)(?!)) # until depth is 0.
7171
\} # followed by a closing brace
7272
)
7373
| # OR
7474
(?<List> # a list, which is
7575
\[ # An open bracket
76-
(?> # Followed by...
77-
[^\[\]]+| # any number of non-bracket character OR
78-
\[(?<Depth>)| # an open bracket (in which case increment depth) OR
79-
\](?<-Depth>) # a closed bracket (in which case decrement depth)
80-
)*(?(Depth)(?!)) # until depth is 0.
81-
\] # followed by a closing bracket
76+
(?> # Followed by...
77+
(?<!\[)\[(?<Depth>) | # an open single bracket (in which case increment depth) OR
78+
\](?<-Depth>) | # a closed bracket (in which case decrement depth)
79+
[^\[\]]+ | # any number of non-bracket character OR
80+
(?<=\[)\[ # a double left bracket
81+
)*(?(Depth)(?!)) # until depth is 0.
82+
\] # followed by a closing bracket
8283
)
8384
| # OR
8485
(?<String> # A string, which is
@@ -106,7 +107,7 @@
106107
)
107108
\s{0,} # Optionally match following whitespace
108109
)
109-
'@, 'IgnoreCase,IgnorePatternWhitespace', '00:00:05')
110+
'@, 'Singleline,IgnoreCase,IgnorePatternWhitespace', '00:00:05')
110111

111112

112113
$jsonList = [Regex]::new(@'
@@ -136,12 +137,13 @@
136137
| # OR
137138
(?<List> # a list, which is
138139
\[ # An open bracket
139-
(?> # Followed by...
140-
[^\[\]]+| # any number of non-bracket character OR
141-
\[(?<Depth>)| # an open bracket (in which case increment depth) OR
142-
\](?<-Depth>) # a closed bracket (in which case decrement depth)
143-
)*(?(Depth)(?!)) # until depth is 0.
144-
\] # followed by a closing bracket
140+
(?> # Followed by...
141+
(?<!\[)\[(?<Depth>) | # an open single bracket (in which case increment depth) OR
142+
\](?<-Depth>) | # a closed bracket (in which case decrement depth)
143+
[^\[\]]+ | # any number of non-bracket character OR
144+
(?<=\[)\[ # a double left bracket
145+
)*(?(Depth)(?!)) # until depth is 0.
146+
\] # followed by a closing bracket
145147
)
146148
| # OR
147149
(?<String> # A string, which is
@@ -206,7 +208,7 @@
206208
}
207209
$cursor = $null
208210
} elseif ($part.Groups['Index'].Success) {
209-
$targetIndex = $part.Groups['Index'].Value
211+
$targetIndex = $part.Groups['Index'].Value -as [int]
210212
$listMatch = $jsonList.Match($JSONText, $cursor)
211213
$values = $listMatch.Groups["JSON_Value"].Captures
212214
if ($targetIndex -gt $values.Count) {

arm-ttk/Test-AzTemplate.ps1

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -500,13 +500,19 @@ Each test script has access to a set of well-known variables:
500500
$TemplateObject = $fileInfo.Object
501501
$TemplateText = $fileInfo.Text
502502
if ($InnerTemplates.Count) {
503-
foreach ($it in $innerTemplates) {
504-
$foundInnerTemplate = $it | Resolve-JSONContent -JsonText $TemplateText
505-
$TemplateText = $TemplateText.Remove($foundInnerTemplate.Index, $foundInnerTemplate.Length)
506-
$templateText = $templateText.Insert($foundInnerTemplate.Index, '"template": {}')
507-
}
503+
$anyProblems = $false
504+
foreach ($it in $innerTemplates) {
505+
$foundInnerTemplate = $it | Resolve-JSONContent -JsonText $TemplateText
506+
if (-not $foundInnerTemplate) { $anyProblems = $true; break }
507+
$TemplateText = $TemplateText.Remove($foundInnerTemplate.Index, $foundInnerTemplate.Length)
508+
$TemplateText = $TemplateText.Insert($foundInnerTemplate.Index, '"template": {}')
509+
}
508510

509-
$TemplateObject = $TemplateText | ConvertFrom-Json
511+
if (-not $anyProblems) {
512+
$TemplateObject = $TemplateText | ConvertFrom-Json
513+
} else {
514+
Write-Error "Could not extract inner templates for '$TemplatePath'." -ErrorId InnerTemplate.Extraction.Error
515+
}
510516
}
511517
}
512518
foreach ($groupName in $matchingGroups) {

unit-tests/_arm-ttk-module/InnerTemplate.module.tests.ps1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ describe InnerTemplates {
2525
Select-Object -ExpandProperty Count |
2626
Should -Be 1
2727
}
28+
29+
it 'Will expand innermost templates first' {
30+
$templatePath = $here | Join-Path -ChildPath NestedInnerTemplates.json
31+
$expanded = Expand-AzTemplate -TemplatePath $templatePath
32+
$expanded.innerTemplates.Count | Should -be 4
33+
}
34+
35+
it 'Will expand templates containing bracket escape sequences' {
36+
$templatePath = $here | Join-Path -ChildPath InnerTemplateWithEscapeSequence.json
37+
$expanded = Expand-AzTemplate -TemplatePath $templatePath
38+
$expanded.innerTemplates.Count | Should -be 1
39+
}
2840
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
3+
"contentVersion": "1.0.0.0",
4+
"parameters": {
5+
"_artifactsLocation": {
6+
"type": "string",
7+
"defaultValue": "[substring(deployment().properties.templateLink.uri, 0, lastIndexOf(deployment().properties.templateLink.uri, '/'))]"
8+
},
9+
"_artifactsLocationSasToken": {
10+
"type": "securestring",
11+
"defaultValue": ""
12+
},
13+
"location": {
14+
"type": "string",
15+
"defaultValue": "[deployment().location]"
16+
},
17+
"diagnosticPolicies": {
18+
"type": "array",
19+
"defaultValue": [
20+
"diagnostics-aa-deploy-policy",
21+
"diagnostics-adf-deploy-policy",
22+
"diagnostics-agw-deploy-policy"
23+
]
24+
}
25+
},
26+
"resources": [
27+
{
28+
"name": "getManagementGroupName",
29+
"type": "Microsoft.Resources/deployments",
30+
"apiVersion": "2021-01-01",
31+
"location": "[parameters('location')]",
32+
"properties": {
33+
"expressionEvaluationOptions": {
34+
"scope": "Inner"
35+
},
36+
"mode": "Incremental",
37+
"template": {
38+
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
39+
"contentVersion": "1.0.0.0",
40+
"resources": [ ]
41+
}
42+
}
43+
},
44+
{
45+
"name": "[parameters('diagnosticPolicies')[copyIndex()]]",
46+
"type": "Microsoft.Resources/deployments",
47+
"apiVersion": "2019-08-01",
48+
"location": "[parameters('location')]",
49+
"properties": {
50+
"mode": "Incremental",
51+
"parameters": { },
52+
"templateLink": {
53+
"uri": "[format('{0}{1}.json{2}', parameters('_artifactsLocation'), parameters('diagnosticPolicies')[copyIndex()], parameters('_artifactsLocationSasToken'))]"
54+
}
55+
},
56+
"copy": {
57+
"name": "diagnosticPolicies",
58+
"count": "[length(parameters('diagnosticPolicies'))]"
59+
}
60+
},
61+
{
62+
"type": "Microsoft.Authorization/policySetDefinitions",
63+
"apiVersion": "2019-09-01",
64+
"name": "diagnostics-loganalytics-deploy-initiative",
65+
"dependsOn": [
66+
"diagnosticPolicies"
67+
],
68+
"properties": {
69+
"displayName": "Deploy Diagnostics & Metrics for Azure Resource to a Log Analytics workspace",
70+
"description": "Apply diagnostic & metric settings for Azure Resources to stream data to a Log Analytics workspace when any Azure Resource which is missing this diagnostic settings is created or updated.",
71+
"policyType": "Custom",
72+
"metadata": {
73+
"category": "Monitoring",
74+
"version": "1.0.0"
75+
},
76+
"parameters": {
77+
"profileName": {
78+
"type": "string",
79+
"metadata": {
80+
"displayName": "Profile name",
81+
"description": "The diagnostic settings profile name"
82+
},
83+
"defaultValue": "setbypolicy_logAnalytics"
84+
},
85+
"logAnalytics": {
86+
"type": "string",
87+
"metadata": {
88+
"displayName": "Log Analytics workspace",
89+
"description": "Select Log Analytics workspace from dropdown list. If this workspace is outside of the scope of the assignment you must manually grant 'Log Analytics Contributor' permissions (or similar) to the policy assignment's principal ID.",
90+
"strongType": "omsWorkspace",
91+
"assignPermissions": true
92+
}
93+
}
94+
},
95+
"copy": [
96+
{
97+
"name": "policyDefinitions",
98+
"count": "[length(parameters('diagnosticPolicies'))]",
99+
"input": {
100+
"policyDefinitionId": "[extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', split(reference('getManagementGroupName', '2020-10-01', 'Full').scope, '/')[2]), 'Microsoft.Authorization/policyDefinitions', parameters('diagnosticPolicies')[copyIndex('policyDefinitions')])]",
101+
"policyDefinitionReferenceId": "[parameters('diagnosticPolicies')[copyIndex('policyDefinitions')]]",
102+
"parameters": {
103+
"profileName": {
104+
"value": "[[[parameters('profileName')]"
105+
},
106+
"logAnalytics": {
107+
"value": "[[[parameters('logAnalytics')]"
108+
}
109+
}
110+
}
111+
}
112+
]
113+
}
114+
}
115+
]
116+
}

unit-tests/_arm-ttk-module/JSONC.module.tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Push-Location $PSScriptRoot
22

33
describe "JSONC" {
4-
it "Supports JSONC files" {
5-
Test-AzTemplate -TemplatePath (Join-Path $pwd "Sample.jsonc") -Test 'DeploymentTemplate Schema Is Correct' |
4+
it "Supports JSONC files" {
5+
Test-AzTemplate -TemplatePath (Join-Path $pwd "Sample.jsonc") -Test 'DeploymentTemplate Schema Is Correct' -File "Sample.jsonc" |
66
Select-Object -ExpandProperty Passed |
77
Should -Be $true
88
}

unit-tests/_arm-ttk-module/JSONContentCommands.tests.ps1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,17 @@ describe Resolve-JSONContent {
8888
$resolved.Line | Should -Be 4
8989
$resolved.Content | Should -Be "1"
9090
}
91+
92+
it 'Can resolve a JSON property with an ARM double-bracket escape sequence' {
93+
$resolved = Resolve-JSONContent -JSONPath 'a[0].b.c' -JSONText @'
94+
{
95+
"a": [{
96+
'b': {
97+
c: "[[0,1,2]"
98+
}
99+
}]
100+
}
101+
'@
102+
$resolved.Line | Should -Be 4
103+
}
91104
}

0 commit comments

Comments
 (0)