Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/ALZ/Private/Config-Helpers/ConvertTo-DnsSafeTags.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
function ConvertTo-DnsSafeTags {
<#
.SYNOPSIS
Converts tags to DNS-safe format by removing spaces and parentheses from tag keys.

.DESCRIPTION
Azure DNS zones don't support the use of spaces or parentheses in tag keys, or tag keys that start with a number.
This function sanitizes tag keys to make them DNS-safe.
Reference: https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources

.PARAMETER tags
The hashtable or PSCustomObject containing tags to sanitize.

.EXAMPLE
ConvertTo-DnsSafeTags -tags @{"Business Application" = "ALZ"; "Owner" = "Platform"}
Returns: @{"BusinessApplication" = "ALZ"; "Owner" = "Platform"}

.EXAMPLE
ConvertTo-DnsSafeTags -tags @{"Business Unit (Primary)" = "IT"; "1stTag" = "value"}
Returns: @{"BusinessUnitPrimary" = "IT"; "_1stTag" = "value"}
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[object] $tags
)

if ($null -eq $tags) {
return $null
}

$dnsSafeTags = @{}

# Handle both hashtables and PSCustomObjects
if ($tags -is [hashtable]) {
foreach ($key in $tags.Keys) {
$safeKey = $key -replace '\s+', '' # Remove all whitespace
$safeKey = $safeKey -replace '[()]', '' # Remove parentheses
# Ensure key doesn't start with a number by prepending underscore if needed
if ($safeKey -match '^\d') {
$safeKey = "_$safeKey"
}
if ($safeKey -ne "") {
$dnsSafeTags[$safeKey] = $tags[$key]
} else {
Write-Warning "Tag key '$key' resulted in empty string after sanitization and was skipped"
}
}
} elseif ($tags -is [PSCustomObject]) {
foreach ($property in $tags.PSObject.Properties) {
$safeKey = $property.Name -replace '\s+', '' # Remove all whitespace
$safeKey = $safeKey -replace '[()]', '' # Remove parentheses
# Ensure key doesn't start with a number by prepending underscore if needed
if ($safeKey -match '^\d') {
$safeKey = "_$safeKey"
}
if ($safeKey -ne "") {
$dnsSafeTags[$safeKey] = $property.Value
} else {
Write-Warning "Tag key '$($property.Name)' resulted in empty string after sanitization and was skipped"
}
}
} else {
Write-Verbose "Tag format is neither hashtable nor PSCustomObject, returning as-is"
return $tags
}

return $dnsSafeTags
}
96 changes: 96 additions & 0 deletions src/ALZ/Private/Config-Helpers/Set-DnsSafeTagsForVirtualHubs.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
function Set-DnsSafeTagsForVirtualHubs {
<#
.SYNOPSIS
Processes virtual_hubs configuration to ensure DNS zone tags are DNS-safe.

.DESCRIPTION
This function processes the virtual_hubs configuration object and sanitizes tags for private_dns_zones
to ensure they don't contain spaces or other characters not supported by Azure DNS.
Implements fallback logic: private_dns_zones.tags -> connectivity_tags -> overall tags (all sanitized)

.PARAMETER virtualHubs
The virtual_hubs configuration object to process.

.PARAMETER connectivityTags
Optional connectivity-level tags to use as fallback.

.PARAMETER overallTags
Optional overall/global tags to use as final fallback.

.EXAMPLE
Set-DnsSafeTagsForVirtualHubs -virtualHubs $config -connectivityTags @{"Business Unit" = "IT"}
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[object] $virtualHubs,

[Parameter(Mandatory = $false)]
[object] $connectivityTags = $null,

[Parameter(Mandatory = $false)]
[object] $overallTags = $null
)

if ($null -eq $virtualHubs) {
return $virtualHubs
}

# Process each virtual hub
foreach ($hubProperty in $virtualHubs.PSObject.Properties) {
$hub = $hubProperty.Value

if ($null -eq $hub) {
continue
}

# Check if this hub has private_dns_zones configuration
$privateDnsZonesProperty = $hub.PSObject.Properties | Where-Object { $_.Name -eq "private_dns_zones" }

if ($null -ne $privateDnsZonesProperty) {
$privateDnsZones = $privateDnsZonesProperty.Value

if ($null -ne $privateDnsZones) {
# Check if DNS zone has its own tags
$dnsTagsProperty = $privateDnsZones.PSObject.Properties | Where-Object { $_.Name -eq "tags" }

if ($null -ne $dnsTagsProperty -and $null -ne $dnsTagsProperty.Value) {
# DNS zone has its own tags - sanitize them
Write-Verbose "Sanitizing DNS zone tags for hub: $($hubProperty.Name)"
$sanitizedTags = ConvertTo-DnsSafeTags -tags $dnsTagsProperty.Value
$privateDnsZones.tags = $sanitizedTags
} else {
# No DNS-specific tags, implement fallback logic
Write-Verbose "No DNS-specific tags found for hub: $($hubProperty.Name), applying fallback logic"

$tagsToUse = $null

# Try connectivity tags first
if ($null -ne $connectivityTags) {
Write-Verbose "Using connectivity tags as fallback"
$tagsToUse = $connectivityTags
}
# Fall back to overall tags if connectivity tags not available
elseif ($null -ne $overallTags) {
Write-Verbose "Using overall tags as fallback"
$tagsToUse = $overallTags
}

# Sanitize and apply the fallback tags
if ($null -ne $tagsToUse) {
$sanitizedTags = ConvertTo-DnsSafeTags -tags $tagsToUse

# Add tags property if it doesn't exist
if ($null -eq $dnsTagsProperty) {
$privateDnsZones | Add-Member -NotePropertyName "tags" -NotePropertyValue $sanitizedTags -Force
} else {
$privateDnsZones.tags = $sanitizedTags
}
}
}
}
}
}

return $virtualHubs
}
20 changes: 20 additions & 0 deletions src/ALZ/Private/Config-Helpers/Write-TfvarsJsonFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ function Write-TfvarsJsonFile {

$jsonObject = [ordered]@{}

# Extract connectivity and overall tags for DNS fallback logic
$connectivityTags = $null
$overallTags = $null

$connectivityTagsProperty = $configuration.PSObject.Properties | Where-Object { $_.Name -eq "connectivity_tags" }
if ($null -ne $connectivityTagsProperty) {
$connectivityTags = $connectivityTagsProperty.Value.Value
}

$tagsProperty = $configuration.PSObject.Properties | Where-Object { $_.Name -eq "tags" }
if ($null -ne $tagsProperty) {
$overallTags = $tagsProperty.Value.Value
}

foreach ($configurationProperty in $configuration.PSObject.Properties | Sort-Object Name) {
if ($skipItems -contains $configurationProperty.Name) {
Write-Verbose "Skipping configuration property: $($configurationProperty.Name)"
Expand All @@ -36,6 +50,12 @@ function Write-TfvarsJsonFile {
$configurationValue = [System.IO.Path]::GetFileName($configurationValue)
}

# Process virtual_hubs to sanitize DNS zone tags
if ($configurationProperty.Name -eq "virtual_hubs" -and $null -ne $configurationValue) {
Write-Verbose "Processing virtual_hubs configuration to apply DNS-safe tags"
$configurationValue = Set-DnsSafeTagsForVirtualHubs -virtualHubs $configurationValue -connectivityTags $connectivityTags -overallTags $overallTags
}

Write-Verbose "Writing to tfvars.json - Configuration Property: $($configurationProperty.Name) - Configuration Value: $configurationValue"
$jsonObject.Add("$($configurationProperty.Name)", $configurationValue)
}
Expand Down
135 changes: 135 additions & 0 deletions src/Tests/Unit/Private/ConvertTo-DnsSafeTags.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
Describe "ConvertTo-DnsSafeTags" {
BeforeAll {
# Directly source the function file
. "$PSScriptRoot/../../../ALZ/Private/Config-Helpers/ConvertTo-DnsSafeTags.ps1"
}

Context "When converting hashtable tags" {
It "Should remove spaces from tag keys" {
$tags = @{
"Business Application" = "ALZ"
"Owner" = "Platform"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "BusinessApplication"
$result.Keys | Should -Contain "Owner"
$result.Keys | Should -Not -Contain "Business Application"
$result["BusinessApplication"] | Should -Be "ALZ"
$result["Owner"] | Should -Be "Platform"
}

It "Should remove parentheses from tag keys" {
$tags = @{
"Business Unit (Primary)" = "IT"
"Cost Center (Backup)" = "12345"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "BusinessUnitPrimary"
$result.Keys | Should -Contain "CostCenterBackup"
$result["BusinessUnitPrimary"] | Should -Be "IT"
$result["CostCenterBackup"] | Should -Be "12345"
}

It "Should prefix tag keys that start with a number" {
$tags = @{
"1stTag" = "value1"
"2ndTag" = "value2"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "_1stTag"
$result.Keys | Should -Contain "_2ndTag"
$result["_1stTag"] | Should -Be "value1"
$result["_2ndTag"] | Should -Be "value2"
}

It "Should handle tags with multiple spaces and special characters" {
$tags = @{
"Business Application (Main)" = "ALZ"
" Owner " = "Platform"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "BusinessApplicationMain"
$result.Keys | Should -Contain "Owner"
$result["BusinessApplicationMain"] | Should -Be "ALZ"
$result["Owner"] | Should -Be "Platform"
}

It "Should return null for null input" {
$result = ConvertTo-DnsSafeTags -tags $null
$result | Should -Be $null
}

It "Should handle empty hashtable" {
$tags = @{}
$result = ConvertTo-DnsSafeTags -tags $tags
$result.Count | Should -Be 0
}
}

Context "When converting PSCustomObject tags" {
It "Should remove spaces from tag keys in PSCustomObject" {
$tags = [PSCustomObject]@{
"Business Application" = "ALZ"
"Owner" = "Platform"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "BusinessApplication"
$result.Keys | Should -Contain "Owner"
$result["BusinessApplication"] | Should -Be "ALZ"
$result["Owner"] | Should -Be "Platform"
}

It "Should handle PSCustomObject with special characters" {
$tags = [PSCustomObject]@{
"Business Unit (Test)" = "IT"
"1stTag" = "value"
}

$result = ConvertTo-DnsSafeTags -tags $tags

$result.Keys | Should -Contain "BusinessUnitTest"
$result.Keys | Should -Contain "_1stTag"
$result["BusinessUnitTest"] | Should -Be "IT"
$result["_1stTag"] | Should -Be "value"
}
}

Context "When handling edge cases" {
It "Should skip tags that result in empty keys" {
$tags = @{
" " = "value"
"Owner" = "Platform"
}

# Should issue a warning but not fail
$result = ConvertTo-DnsSafeTags -tags $tags -WarningAction SilentlyContinue

$result.Keys | Should -Contain "Owner"
$result.Keys | Should -Not -Contain " "
$result.Keys | Should -Not -Contain ""
$result.Count | Should -Be 1
}

It "Should handle tags with only spaces and parentheses" {
$tags = @{
"( )" = "value"
"Owner" = "Platform"
}

$result = ConvertTo-DnsSafeTags -tags $tags -WarningAction SilentlyContinue

$result.Keys | Should -Contain "Owner"
$result.Count | Should -Be 1
}
}
}
Loading
Loading