PSRule/Rules/AAC.Tags.Rule.ps1
|
# Azure.Admin.Console's own PSRule rules, run by Invoke-AACPSRule with PSRule # for Azure's. They check what PSRule for Azure leaves to each organisation: # which tags resources must carry, and the values they may have. They check # the tags in Get-AACTagDefault below - empty as shipped, so they check # nothing until tags are named there or in -Configuration (which wins): # # Invoke-AACPSRule -Rule 'AAC.*' -Configuration @{ # AAC_REQUIRED_TAGS = @('Owner', 'CostCenter', 'Environment') # AAC_ALLOWED_TAG_VALUES = @{ Environment = @('prod', 'test', 'dev') } # } # # Invoke-AACPSRule says so when one of them runs with no tags to check. # It reads Get-AACTagDefault too (without running this file): keep it a plain # table of strings. # # Their help (synopsis, recommendation, links) is in en\<rule name>.md next to # this file. Leave them out with -ExcludeRule 'AAC.*'. # # To add rules of your own, write them the same way - PowerShell # (*.Rule.ps1), YAML (*.Rule.yaml) or JSON (*.Rule.jsonc); see # https://microsoft.github.io/PSRule/v2/authoring/writing-rules/ - and pass # their files or folder to Invoke-AACPSRule -RulePath. # The tags checked when -Configuration names none. Add your organisation's # here, e.g. RequiredTags = @('Owner', 'CostCenter', 'Environment') and # AllowedValues = @{ Environment = @('prod', 'test', 'dev') }. function global:Get-AACTagDefault { @{ RequiredTags = @() AllowedValues = @{} } } # The required tags: -Configuration's AAC_REQUIRED_TAGS, else the defaults. function global:Get-AACRequiredTag { $configured = @($Configuration.GetStringValues('AAC_REQUIRED_TAGS') | Where-Object { $_ }) if ($configured.Count) { return $configured } @((Get-AACTagDefault).RequiredTags | Where-Object { $_ }) } # The allowed values: -Configuration's AAC_ALLOWED_TAG_VALUES, else the # defaults - a hashtable, or an object when read from JSON; $null for none. function global:Get-AACAllowedTagValue { $configured = $Configuration.GetValueOrDefault('AAC_ALLOWED_TAG_VALUES', $null) if ($null -ne $configured) { return $configured } $defaults = (Get-AACTagDefault).AllowedValues if ($defaults -and $defaults.Count) { return $defaults } } # A resource that can carry tags: anything under a resource provider - not a # resource group or subscription, which have rules of their own. function global:Test-AACTaggableResource { [string]$TargetObject.id -match '/providers/' -and [string]$TargetObject.type -notmatch '^microsoft\.(resources|subscription)' } # Synopsis: Resources carry every tag your organisation requires. Rule 'AAC.Resource.RequiredTags' -Ref 'AAC-001' -Level Error -Tag @{ release = 'GA'; 'Azure.WAF/pillar' = 'Operational Excellence' } -If { (Test-AACTaggableResource) -and @(Get-AACRequiredTag).Count -gt 0 } { foreach ($name in Get-AACRequiredTag) { $Assert.HasFieldValue($TargetObject, "tags.$name").Reason('The required tag ''{0}'' is missing or empty.', $name) } } # Synopsis: Resource groups carry every tag your organisation requires. Rule 'AAC.ResourceGroup.RequiredTags' -Ref 'AAC-002' -Level Error -Type 'Microsoft.Resources/resourceGroups' -Tag @{ release = 'GA'; 'Azure.WAF/pillar' = 'Operational Excellence' } -If { @(Get-AACRequiredTag).Count -gt 0 } { foreach ($name in Get-AACRequiredTag) { $Assert.HasFieldValue($TargetObject, "tags.$name").Reason('The required tag ''{0}'' is missing or empty.', $name) } } # Synopsis: Tags with a fixed set of values use one of them. Rule 'AAC.Resource.AllowedTagValues' -Ref 'AAC-003' -Level Warning -Tag @{ release = 'GA'; 'Azure.WAF/pillar' = 'Operational Excellence' } -If { ((Test-AACTaggableResource) -or [string]$TargetObject.type -eq 'Microsoft.Resources/resourceGroups') -and $null -ne (Get-AACAllowedTagValue) } { $allowed = Get-AACAllowedTagValue $names = if ($allowed -is [System.Collections.IDictionary]) { @($allowed.Keys) } else { @($allowed.PSObject.Properties.Name) } $checked = 0 foreach ($name in $names) { $values = @($(if ($allowed -is [System.Collections.IDictionary]) { $allowed[$name] } else { $allowed.$name }) | ForEach-Object { [string]$_ }) $tag = $TargetObject.tags.PSObject.Properties | Where-Object { $_.Name -eq $name } | Select-Object -First 1 if ($tag) { $checked++ $Assert.In($TargetObject, "tags.$($tag.Name)", $values).Reason('The tag ''{0}'' is ''{1}''; it should be one of: {2}.', $name, $tag.Value, ($values -join ', ')) } } if ($checked -eq 0) { $Assert.Pass() } } |