Skip to content

Commit

Permalink
Fix Duplicate conflicting deployments with DeployAllMultipleTemplateP…
Browse files Browse the repository at this point in the history
…arameterFiles (#887)

* Update

* Update

* Update
  • Loading branch information
Jefajers authored Jun 25, 2024
1 parent 882f748 commit ff3d3dd
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 28 deletions.
67 changes: 44 additions & 23 deletions src/functions/Invoke-AzOpsPush.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
#region Case: Parameters File
if (($fileItem.Name.EndsWith('.parameters.json')) -or ($fileItem.Name.EndsWith('.bicepparam'))) {
$result.TemplateParameterFilePath = $fileItem.FullName
$deploymentName = $fileItem.Name -replace (Get-PSFConfigValue -FullName 'AzOps.Core.TemplateParameterFileSuffix'), '' -replace ' ', '_' -replace '\.bicepparam', ''
$deploymentName = $fileItem.Name -replace "\.parameters\$(Get-PSFConfigValue -FullName 'AzOps.Core.TemplateParameterFileSuffix')$", '' -replace ' ', '_' -replace '\.bicepparam$', ''
if ($deploymentName.Length -gt 53) { $deploymentName = $deploymentName.SubString(0, 53) }
$result.DeploymentName = 'AzOps-{0}-{1}' -f $deploymentName, $deploymentRegionId

Expand All @@ -167,12 +167,12 @@
{ $_.EndsWith('.parameters.json') } {
if ((Get-PSFConfigValue -FullName 'AzOps.Core.AllowMultipleTemplateParameterFiles') -eq $true -and $fileItem.FullName.Split('.')[-3] -match $(Get-PSFConfigValue -FullName 'AzOps.Core.MultipleTemplateParameterFileSuffix').Replace('.','')) {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.MultipleTemplateParameterFile' -LogStringValues $FilePath
$templatePath = $fileItem.FullName -replace "\.$($fileItem.FullName.Split('.')[-3])\.parameters.json$", '.json'
$bicepTemplatePath = $fileItem.FullName -replace "\.$($fileItem.FullName.Split('.')[-3])\.parameters.json$", '.bicep'
$templatePath = $fileItem.FullName -replace "\.$($fileItem.FullName.Split('.')[-3])\.parameters\.json$", '.json'
$bicepTemplatePath = $fileItem.FullName -replace "\.$($fileItem.FullName.Split('.')[-3])\.parameters\.json$", '.bicep'
}
else {
$templatePath = $fileItem.FullName -replace '\.parameters.json$', (Get-PSFConfigValue -FullName 'AzOps.Core.TemplateParameterFileSuffix')
$bicepTemplatePath = $fileItem.FullName -replace '\.parameters.json$', '.bicep'
$templatePath = $fileItem.FullName -replace '\.parameters\.json$', (Get-PSFConfigValue -FullName 'AzOps.Core.TemplateParameterFileSuffix')
$bicepTemplatePath = $fileItem.FullName -replace '\.parameters\.json$', '.bicep'
}
if (Test-Path $templatePath) {
if ($CompareDeploymentToDeletion) {
Expand Down Expand Up @@ -299,17 +299,21 @@
if ($paramFileList) {
$multiResult = @()
foreach ($paramFile in $paramFileList) {
if ($CompareDeploymentToDeletion) {
# Avoid adding files destined for deletion to a deployment list
if ($paramFile.VersionInfo.FileName -in $deleteSet -or $paramFile.VersionInfo.FileName -in ($deleteSet | Resolve-Path).Path) {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.DeployDeletionOverlap' -LogStringValues $paramFile.VersionInfo.FileName
continue
# Check if the parameter file's name matches the expected pattern
$escapedBaseName = $fileItem.BaseName -replace '\.', '\.'
if ($paramFile.BaseName -match "^$escapedBaseName(\$(Get-PSFConfigValue -FullName 'AzOps.Core.MultipleTemplateParameterFileSuffix'))") {
if ($CompareDeploymentToDeletion) {
# Avoid adding files destined for deletion to a deployment list
if ($paramFile.VersionInfo.FileName -in $deleteSet -or $paramFile.VersionInfo.FileName -in ($deleteSet | Resolve-Path).Path) {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.DeployDeletionOverlap' -LogStringValues $paramFile.VersionInfo.FileName
continue
}
}
# Process parameter files for template equivalent
if (($fileItem.FullName.Split('.')[-2] -eq $paramFile.FullName.Split('.')[-3]) -or ($fileItem.FullName.Split('.')[-2] -eq $paramFile.FullName.Split('.')[-4])) {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.MultipleTemplateParameterFile' -LogStringValues $paramFile.FullName
$multiResult += Resolve-ArmFileAssociation -ScopeObject $scopeObject -FilePath $paramFile -AzOpsMainTemplate $AzOpsMainTemplate -ConvertedTemplate $ConvertedTemplate -ConvertedParameter $ConvertedParameter -CompareDeploymentToDeletion:$CompareDeploymentToDeletion
}
}
# Process possible parameter files for template equivalent
if (($fileItem.FullName.Split('.')[-2] -eq $paramFile.FullName.Split('.')[-3]) -or ($fileItem.FullName.Split('.')[-2] -eq $paramFile.FullName.Split('.')[-4])) {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.MultipleTemplateParameterFile' -LogStringValues $paramFile.FullName
$multiResult += Resolve-ArmFileAssociation -ScopeObject $scopeObject -FilePath $paramFile -AzOpsMainTemplate $AzOpsMainTemplate -ConvertedTemplate $ConvertedTemplate -ConvertedParameter $ConvertedParameter -CompareDeploymentToDeletion:$CompareDeploymentToDeletion
}
}
if ($multiResult) {
Expand All @@ -318,20 +322,16 @@
}
else {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.ParameterNotFound' -LogStringValues $FilePath, $parameterPath
if (-not (Test-TemplateDefaultParameter -FilePath $FilePath)) {
continue
}
}

}
}
else {
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.ParameterNotFound' -LogStringValues $FilePath, $parameterPath
if ((Get-PSFConfigValue -FullName 'AzOps.Core.AllowMultipleTemplateParameterFiles') -eq $true) {
# Check for template parameters without defaultValue
$defaultValueContent = Get-Content $FilePath
$missingDefaultParam = $defaultValueContent | jq '.parameters | with_entries(select(.value.defaultValue == null))' | ConvertFrom-Json -AsHashtable
if ($missingDefaultParam.Count -ge 1) {
# Skip template deployment when template parameters without defaultValue are found and no parameter file identified
$missingString = foreach ($item in $missingDefaultParam.Keys.GetEnumerator()) {"$item,"}
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.NotFoundParamFileDefaultValue' -LogStringValues $FilePath, ($missingString | Out-String -NoNewline)
if (-not (Test-TemplateDefaultParameter -FilePath $FilePath)) {
continue
}
}
Expand All @@ -344,6 +344,27 @@
$result
#endregion Case: Template File
}
function Test-TemplateDefaultParameter {
param(
[string]$FilePath
)

# Read the template file
$defaultValueContent = Get-Content $FilePath
# Check for parameters without a default value using jq
$missingDefaultParam = $defaultValueContent | jq '.parameters | with_entries(select(.value.defaultValue == null))' | ConvertFrom-Json -AsHashtable
if ($missingDefaultParam.Count -ge 1) {
# Skip template deployment when template parameters without defaultValue are found and no parameter file identified
$missingString = foreach ($item in $missingDefaultParam.Keys.GetEnumerator()) {"$item,"}
# Log a debug message with the missing parameters
Write-AzOpsMessage -LogLevel Debug -LogString 'Invoke-AzOpsPush.Resolve.NotFoundParamFileDefaultValue' -LogStringValues $FilePath, ($missingString | Out-String -NoNewline)
# Missing default value were found
return $false
} else {
# Default values found
return $true
}
}
#endregion Utility Functions

$WhatIfPreferenceState = $WhatIfPreference
Expand Down
30 changes: 25 additions & 5 deletions src/tests/integration/Repository.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Describe "Repository" {
$script:resourceGroupParallelDeploy = (Get-AzResourceGroup | Where-Object ResourceGroupName -eq "ParallelDeploy-azopsrg")
$script:roleAssignments = (Get-AzRoleAssignment -ObjectId "023e7c1c-1fa4-4818-bb78-0a9c5e8b0217" | Where-Object { $_.Scope -eq "/subscriptions/$script:subscriptionId" -and $_.RoleDefinitionId -eq "acdd72a7-3385-48ef-bd42-f606fba81ae7" })
$script:policyExemptions = Get-AzPolicyExemption -Name "PolicyExemptionTest" -Scope "/subscriptions/$script:subscriptionId"
$script:routeTable = (Get-AzResource -Name "RouteTable" -ResourceGroupName $($script:resourceGroup).ResourceGroupName)
$script:routeTable = (Get-AzResource -Name "RouteTable" -ResourceGroupName $script:resourceGroup.ResourceGroupName)
$script:policyAssignmentsDeletion = Get-AzPolicyAssignment -Name "TestPolicyAssignmentDeletion" -Scope "/subscriptions/$script:subscriptionId/resourceGroups/$($script:resourceGroupCustomDeletion.ResourceGroupName)"
$script:ruleCollectionGroups = (Get-AzResource -ExpandProperties -Name "TestPolicy" -ResourceGroupName $($script:resourceGroup).ResourceGroupName).Properties.ruleCollectionGroups.id.split("/")[-1]
$script:logAnalyticsWorkspace = (Get-AzResource -Name "thisisalongloganalyticsworkspacename123456789011121314151617181" -ResourceGroupName $($script:resourceGroup).ResourceGroupName)
Expand Down Expand Up @@ -1162,7 +1162,7 @@ Describe "Repository" {
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Start-Sleep -Seconds 5
$script:bicepMultiParamPathDeployment = Get-AzResource -ResourceGroupName $($script:resourceGroup).ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "rtmultibasex*"}
$script:bicepMultiParamPathDeployment = Get-AzResource -ResourceGroupName $script:resourceGroup.ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "rtmultibasex*"}
$script:bicepMultiParamPathDeployment.Count | Should -Be 2
}
#endregion
Expand All @@ -1178,7 +1178,7 @@ Describe "Repository" {
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Start-Sleep -Seconds 5
$script:bicepRepeatSuffixPathDeployment = Get-AzResource -ResourceGroupName $($script:resourceGroup).ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "rtsuffix*"}
$script:bicepRepeatSuffixPathDeployment = Get-AzResource -ResourceGroupName $script:resourceGroup.ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "rtsuffix*"}
$script:bicepRepeatSuffixPathDeployment.Count | Should -Be 2
Set-PSFConfig -FullName AzOps.Core.MultipleTemplateParameterFileSuffix -Value ".x"
}
Expand Down Expand Up @@ -1214,11 +1214,31 @@ Describe "Repository" {
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Start-Sleep -Seconds 5
$script:deployAllRtParamPathDeployment = Get-AzResource -ResourceGroupName $($script:resourceGroup).ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "deployallrtbasex*"}
$script:deployAllRtParamPathDeployment = Get-AzResource -ResourceGroupName $script:resourceGroup.ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "deployallrtbasex*"}
$script:deployAllRtParamPathDeployment.Count | Should -Be 2
}
#endregion

#region Bicep template with change, AzOps set to resolve corresponding parameter files and create multiple deployments [3] and avoid [1] decoy
It "Deploy Bicep template with change, AzOps set to resolve corresponding parameter files and create multiple deployments [3] and avoid [1] decoy" {
Set-PSFConfig -FullName AzOps.Core.AllowMultipleTemplateParameterFiles -Value $true
Set-PSFConfig -FullName AzOps.Core.DeployAllMultipleTemplateParameterFiles -Value $true
$script:deployAllRtFilesPath = Get-ChildItem -Path "$($global:testRoot)/templates/deployallrt.westeurope*" | Copy-Item -Destination $script:resourceGroupDirectory -PassThru -Force
$script:deployAllRt2FilesPath = Get-ChildItem -Path "$($global:testRoot)/templates/deployallrt2.westeurope.bicep" | Copy-Item -Destination $script:resourceGroupDirectory -PassThru -Force
$script:decoyRtFilesPath = Get-ChildItem -Path "$($global:testRoot)/templates/decoy.westeurope*" | Copy-Item -Destination $script:resourceGroupDirectory -PassThru -Force
$changeSet = @(
"A`t$($script:deployAllRtFilesPath.FullName[0])",
"A`t$($script:deployAllRt2FilesPath.FullName)"
)
$testFiles = Invoke-AzOpsPush -ChangeSet $changeSet
$? | Should -Be $true
$testFiles.Count | Should -Be 3
Start-Sleep -Seconds 5
$script:deployAllRtDeployment = Get-AzResource -ResourceGroupName $script:resourceGroup.ResourceGroupName -ResourceType 'Microsoft.Network/routeTables' | Where-Object {$_.name -like "deployallrtwex*" -or $_.name -like "deployallrt2wex*"}
$script:deployAllRtDeployment.Count | Should -Be 3
}
#endregion

#region Multiple deployments to test parallel deployment logic
It "Deploy parallel storage accounts and compare to serial timing" {
Set-PSFConfig -FullName AzOps.Core.AllowMultipleTemplateParameterFiles -Value $true
Expand All @@ -1232,7 +1252,7 @@ Describe "Repository" {
)
{Invoke-AzOpsPush -ChangeSet $changeSet} | Should -Not -Throw
Start-Sleep -Seconds 30
$script:deployAllStaParamPathDeployment = Get-AzResource -ResourceGroupName $($script:resourceGroupParallelDeploy).ResourceGroupName -ResourceType 'Microsoft.Storage/storageAccounts'
$script:deployAllStaParamPathDeployment = Get-AzResource -ResourceGroupName $script:resourceGroupParallelDeploy.ResourceGroupName -ResourceType 'Microsoft.Storage/storageAccounts'
$script:deployAllStaParamPathDeployment.Count | Should -Be 4
$query = "resourcechanges | where resourceGroup =~ '$($($script:resourceGroupParallelDeploy).ResourceGroupName)' and properties.targetResourceType == 'microsoft.storage/storageaccounts' and properties.changeType == 'Create' | extend changeTime=todatetime(properties.changeAttributes.timestamp), targetResourceId=tostring(properties.targetResourceId) | summarize arg_max(changeTime, *) by targetResourceId | project changeTime, targetResourceId, properties.changeType, properties.targetResourceType | order by changeTime asc"
$createTime = Search-AzGraph -Query $query -Subscription $script:subscriptionId
Expand Down
12 changes: 12 additions & 0 deletions src/tests/templates/decoy.westeurope.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
param name string
param location string = resourceGroup().location

resource symbolicname 'Microsoft.Network/routeTables@2023-04-01' = {
name: name
location: location
properties: {
disableBgpRoutePropagation: false
routes: [
]
}
}
9 changes: 9 additions & 0 deletions src/tests/templates/decoy.westeurope.x123.parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "decoywex123"
}
}
}
12 changes: 12 additions & 0 deletions src/tests/templates/deployallrt.westeurope.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
param name string
param location string = resourceGroup().location

resource symbolicname 'Microsoft.Network/routeTables@2023-04-01' = {
name: name
location: location
properties: {
disableBgpRoutePropagation: false
routes: [
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "deployallrtwex123"
}
}
}
3 changes: 3 additions & 0 deletions src/tests/templates/deployallrt.westeurope.xabc.bicepparam
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using './deployallrt.westeurope.bicep'

param name = toLower('deployallrtwexabc')
12 changes: 12 additions & 0 deletions src/tests/templates/deployallrt2.westeurope.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
param name string = 'deployallrt2wex123'
param location string = resourceGroup().location

resource symbolicname 'Microsoft.Network/routeTables@2023-04-01' = {
name: name
location: location
properties: {
disableBgpRoutePropagation: false
routes: [
]
}
}

0 comments on commit ff3d3dd

Please sign in to comment.