Skip to content

Commit

Permalink
325 blank property exception improvement (#550)
Browse files Browse the repository at this point in the history
* Fixing #325:  Allowing acceptable blanks list to have blank nested properties

* Fixing #325:  Adding test for Blanks in AppSettings

* Updating Blank Exemptions (allowing blanks per resource type)

* Template Should Not Contain Blanks:  Updating Parameter Help

* Adding failure case to validate #325
  • Loading branch information
StartAutomating authored Jan 21, 2022
1 parent 38e6f44 commit 1fc9c76
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,41 @@
param(
[Parameter(Mandatory = $true)]
[string]
$TemplateText
$TemplateText,

[Parameter(Mandatory = $true)]
[PSObject]
$TemplateObject,

# Some properties can be empty for readability
[string[]]
$PropertiesThatCanBeEmpty = @('resources',
'outputs',
'variables',
'parameters',
'functions',
'properties',
'template',
'defaultValue', # enables optional parameters
'accessPolicies', # keyVault requires this
'value', # Microsoft.Resources/deployments - passing empty strings to a nested deployment
'promotionCode', # Microsoft.OperationsManagement/soltuions/plan object
'inputs', # Microsoft.Portal/dashboard
'notEquals', # Microsoft.Authorization/policyDefinitions policyRule'
'clientId', # Microsoft.ContainerService/managedClusters.properties.servicePrincipalProfile
'allowedCallerIpAddresses', # Microsoft.Logic/workflows Access Control
'workerPools', # Microsoft.Web/hostingEnvironments
'AzureMonitor' # Microsoft.Insights/VMDiagnosticsSettings
),

# Some properties can be empty within a given resource.
# The key is the full resource type name, and the value is a list of JSON paths to acceptable blanks.
# For instance, a value of "properties" would allow blanks in any subproperty named properties
# A value of "properties.settings" would allow blanks in any subproperty of settings.
[Collections.IDictionary]
$ResourcePropertiesThatCanBeEmpty = @{
"Microsoft.Web/sites/config" = "properties"
}
)

# Check for any text to remove empty property values - PowerShell handles empty differently in objects so check the JSON source (i.e. text)
Expand All @@ -30,33 +64,70 @@ $emptyItems = @([Regex]::Matches($TemplateText, "${colon}\{\s{0,}\}")) + # Empty

$lineBreaks = [Regex]::Matches($TemplateText, "`n|$([Environment]::NewLine)")

# Some properties can be empty for readability
$PropertiesThatCanBeEmpty = 'resources',
'outputs',
'variables',
'parameters',
'functions',
'properties',
'template',
'defaultValue', # enables optional parameters
'accessPolicies', # keyVault requires this
'value', # Microsoft.Resources/deployments - passing empty strings to a nested deployment
'promotionCode', # Microsoft.OperationsManagement/soltuions/plan object
'inputs', # Microsoft.Portal/dashboard
'notEquals', # Microsoft.Authorization/policyDefinitions policyRule'
'clientId', # Microsoft.ContainerService/managedClusters.properties.servicePrincipalProfile
'allowedCallerIpAddresses', # Microsoft.Logic/workflows Access Control
'workerPools', # Microsoft.Web/hostingEnvironments
'AzureMonitor' # Microsoft.Insights/VMDiagnosticsSettings

if ($emptyItems) {
foreach ($emptyItem in $emptyItems) {
if ($emptyItems) { # If we found empty items
# Do what we need to in order to determine if they are "really" empty

# Find any instances of these properties within the document.
$foundPropertiesThatCanBeEmpty = @(Find-JsonContent -Key "(?>$($PropertiesThatCanBeEmpty -join '|'))" -Match -InputObject $TemplateObject)

# Find the short or long name for each resource type
$resourceTypesThatCanBeEmpty = @($ResourcePropertiesThatCanBeEmpty.Keys) + @(
foreach ($k in $ResourcePropertiesThatCanBeEmpty.Keys) {
@($k -split '/')[-1]
}
)


# Find all resources of interest
$foundResourcesThatCanContainBlanks =
@(Find-JsonContent -Key "type" -Value "(?>$($resourceTypesThatCanBeEmpty -join '|'))" -Match -InputObject $TemplateObject |
Foreach-Object {
$jsonMatch = $_

$resourceTypeBlankPaths = # Find the properties within this resource that can be blank
@(if (-not $ResourcePropertiesThatCanBeEmpty[$jsonMatch.type]) { # If the type is not a full name
# Walk the hierarchy to find the full name
$fullTypeList = @($jsonMatch.type) + @($jsonMatch.Parent | Where-Object Type | Select-Object -ExpandProperty Type)
[Array]::Reverse($fullTypeList)
if (-not $ResourcePropertiesThatCanBeEmpty[$fullTypeList -join '/']) { # If this was a resource we did not care about
return # return
} else {
$ResourcePropertiesThatCanBeEmpty[$fullTypeList -join '/']
}
} else {
$ResourcePropertiesThatCanBeEmpty[$jsonMatch.type]
})

foreach ($blankPath in $resourceTypeBlankPaths) { # Each acceptable blank path is appended to the JSON path of the return object
Resolve-JSONContent -JSONText $TemplateText -JSONPath "$($jsonMatch.JSONPath).$blankPath" # and resolved.
}
})

$resourceBlankRange = # All resolved blank ranges are turned into a list of indexes
@(
foreach ($_ in $foundResourcesThatCanContainBlanks) {
for ($i = $_.Index; $i -lt $_.Index + $_.Length; $i++) {
$i
}
}
)


:nextBlank foreach ($emptyItem in $emptyItems) {
if ($emptyItem.Index -in $resourceBlankRange) { continue } # If the blank is within a range of acceptable indexes, continue.
$nearbyContext = [Regex]::new('"(?<PropertyName>[^"]{1,})"\s{0,}:', "RightToLeft").Match($TemplateText, $emptyItem.Index)
if ($nearbyContext -and $nearbyContext.Success) {
$emptyPropertyName = $nearbyContext.Groups["PropertyName"].Value
# exceptions
if ($PropertiesThatCanBeEmpty -contains $emptyPropertyName) {
continue
if ($PropertiesThatCanBeEmpty -contains $emptyPropertyName) { # If the property was one we said could be empty,
continue # continue to the next blank.
}
foreach ($potentialException in $foundPropertiesThatCanBeEmpty) { # Otherwise, walk over each potential exception found
if ($potentialException.psobject.properties -and # and see if it has this property.
$potentialException.psobject.properties[$emptyPropertyName]) {
continue nextBlank # If so we will continue to the next blank.
}
}
# userAssigned Identity can have an expression for the property name
# it could also be a literal resourceId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"resources": [
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(variables('AppServiceName'), '/appsettings')]",
"apiVersion": "2020-09-01",
"location": "[parameters('location')]",
"foo": ""
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"resources": [
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(variables('AppServiceName'), '/appsettings')]",
"apiVersion": "2020-09-01",
"location": "[parameters('location')]",
"properties": {
"SIGNAL_TO_SOMEBODY_THAT_DEFAULT_VALUE_IS_EMPTY": ""
}
}
]
}

0 comments on commit 1fc9c76

Please sign in to comment.