Skip to content
Merged
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
145 changes: 76 additions & 69 deletions arm/.global/global.module.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ $script:Subscriptiondeployment = 'https://schema.management.azure.com/schemas/20
$script:MGdeployment = 'https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#'
$script:Tenantdeployment = 'https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#'
$script:moduleFolderPaths = $moduleFolderPaths
$script:moduleFolderPathsFiltered = $moduleFolderPaths | Where-Object {
(Split-Path $_ -Leaf) -notin @( 'AzureNetappFiles', 'TrafficManager', 'PrivateDnsZones', 'ManagementGroups') }
$script:enforcedTokenList = $enforcedTokenList

# For runtime purposes, we cache the compiled template in a hashtable that uses a formatted relative module path as a key
$script:convertedTemplates = @{}

# Import any helper function used in this test script
Import-Module (Join-Path $PSScriptRoot 'shared\helper.psm1')
Import-Module (Join-Path $PSScriptRoot 'shared\helper.psm1') -Force

Describe 'File/folder tests' -Tag Modules {

Expand Down Expand Up @@ -125,15 +123,19 @@ Describe 'Readme tests' -Tag Readme {
if (Test-Path (Join-Path $moduleFolderPath 'deploy.bicep')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.bicep'
$templateContent = az bicep build --file $templateFilePath --stdout | ConvertFrom-Json -AsHashtable
} elseif (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
} elseIf (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.json'
$templateContent = Get-Content $templateFilePath -Raw | ConvertFrom-Json -AsHashtable
} else {
throw "No template file found in folder [$moduleFolderPath]"
}
$convertedTemplates[$moduleFolderPathKey] = $templateContent
$convertedTemplates[$moduleFolderPathKey] = @{
templateFilePath = $templateFilePath
templateContent = $templateContent
}
} else {
$templateContent = $convertedTemplates[$moduleFolderPathKey]
$templateContent = $convertedTemplates[$moduleFolderPathKey].templateContent
$templateFilePath = $convertedTemplates[$moduleFolderPathKey].templateFilePath
}

$readmeFolderTestCases += @{
Expand Down Expand Up @@ -205,7 +207,7 @@ Describe 'Readme tests' -Tag Readme {
}

# Get template data
$templateResources = (Get-NestedResourceList -TemplateContent $templateContent | Where-Object {
$templateResources = (Get-NestedResourceList -TemplateFileContent $templateContent | Where-Object {
$_.type -notin @('Microsoft.Resources/deployments') -and $_ }).type | Select-Object -Unique

# Compare
Expand Down Expand Up @@ -242,7 +244,7 @@ Describe 'Readme tests' -Tag Readme {
}

# Get template data
$templateResources = (Get-NestedResourceList -TemplateContent $templateContent | Where-Object {
$templateResources = (Get-NestedResourceList -TemplateFileContent $templateContent | Where-Object {
$_.type -notin @('Microsoft.Resources/deployments') -and $_ }).type | Select-Object -Unique

# Compare
Expand Down Expand Up @@ -432,7 +434,6 @@ Describe 'Deployment template tests' -Tag Template {
Context 'Deployment template tests' {

$deploymentFolderTestCases = [System.Collections.ArrayList] @()
$deploymentFolderTestCasesException = [System.Collections.ArrayList] @()
foreach ($moduleFolderPath in $moduleFolderPaths) {

# For runtime purposes, we cache the compiled template in a hashtable that uses a formatted relative module path as a key
Expand All @@ -441,15 +442,19 @@ Describe 'Deployment template tests' -Tag Template {
if (Test-Path (Join-Path $moduleFolderPath 'deploy.bicep')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.bicep'
$templateContent = az bicep build --file $templateFilePath --stdout | ConvertFrom-Json -AsHashtable
} elseif (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
} elseIf (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.json'
$templateContent = Get-Content $templateFilePath -Raw | ConvertFrom-Json -AsHashtable
} else {
throw "No template file found in folder [$moduleFolderPath]"
}
$convertedTemplates[$moduleFolderPathKey] = $templateContent
$convertedTemplates[$moduleFolderPathKey] = @{
templateFilePath = $templateFilePath
templateContent = $templateContent
}
} else {
$templateContent = $convertedTemplates[$moduleFolderPathKey]
$templateContent = $convertedTemplates[$moduleFolderPathKey].templateContent
$templateFilePath = $convertedTemplates[$moduleFolderPathKey].templateFilePath
}

# Parameter file test cases
Expand Down Expand Up @@ -477,15 +482,10 @@ Describe 'Deployment template tests' -Tag Template {
$deploymentFolderTestCases += @{
moduleFolderName = $moduleFolderPath.Replace('\', '/').Split('/arm/')[1]
templateContent = $templateContent
templateFilePath = $templateFilePath
parameterFileTestCases = $parameterFileTestCases
}
}
foreach ($moduleFolderPath in $moduleFolderPathsFiltered) {
$deploymentFolderTestCasesException += @{
moduleFolderNameException = $moduleFolderPath.Replace('\', '/').Split('/arm/')[1]
templateContent = $templateContent
}
}

It '[<moduleFolderName>] the template file should not be empty' -TestCases $deploymentFolderTestCases {
param(
Expand All @@ -507,11 +507,11 @@ Describe 'Deployment template tests' -Tag Template {
$SchemaArray = @()
if ($Schemaverion -eq $RGdeployment) {
$SchemaOutput = $true
} elseif ($Schemaverion -eq $Subscriptiondeployment) {
} elseIf ($Schemaverion -eq $Subscriptiondeployment) {
$SchemaOutput = $true
} elseif ($Schemaverion -eq $MGdeployment) {
} elseIf ($Schemaverion -eq $MGdeployment) {
$SchemaOutput = $true
} elseif ($Schemaverion -eq $Tenantdeployment) {
} elseIf ($Schemaverion -eq $Tenantdeployment) {
$SchemaOutput = $true
} else {
$SchemaOutput = $false
Expand Down Expand Up @@ -540,10 +540,10 @@ Describe 'Deployment template tests' -Tag Template {
foreach ($API in $ApiVersion) {
if ($API.Substring(0, 2) -eq '20') {
$ApiVersionOutput = $true
} elseif ($API.substring(1, 10) -eq 'parameters') {
} elseIf ($API.substring(1, 10) -eq 'parameters') {
# An API version should not be referenced as a parameter
$ApiVersionOutput = $false
} elseif ($API.substring(1, 10) -eq 'variables') {
} elseIf ($API.substring(1, 10) -eq 'variables') {
# An API version should not be referenced as a variable
$ApiVersionOutput = $false
} else {
Expand Down Expand Up @@ -679,59 +679,62 @@ Describe 'Deployment template tests' -Tag Template {
}
}

It "[<moduleFolderNameException>] All resources that have a Location property should refer to the Location parameter 'parameters('Location')'" -TestCases $deploymentFolderTestCasesException {
It '[<moduleFolderName>] Location output should be returned for resources that use it' -TestCases $deploymentFolderTestCases {

param(
$moduleFolderNameException,
$templateContent
[string] $moduleFolderName,
$templateContent,
[string] $templateFilePath
)
$LocationParamFlag = @()
$Locmandoutput = $templateContent.resources
foreach ($Locmand in $Locmandoutput) {
if ($Locmand.Keys -contains 'Location' -and $Locmand.Location -eq "[parameters('Location')]") {
$LocationParamFlag += $true
} elseif ($Locmand.Keys -notcontains 'Location') {
$LocationParamFlag += $true
} elseif ($Locmand.Keys -notcontains 'resourceGroup') {
$LocationParamFlag += $true
} else {
$LocationParamFlag += $false
}
foreach ($Locm in $Locmand.resources) {
if ($Locm.Keys -contains 'Location' -and $Locm.Location -eq "[parameters('Location')]") {
$LocationParamFlag += $true
} elseif ($Locm.Keys -notcontains 'Location') {
$LocationParamFlag += $true
} else {
$LocationParamFlag += $false
}
}

$outputs = $templateContent.outputs

$primaryResourceType = (Split-Path $TemplateFilePath -Parent).Replace('\', '/').split('/arm/')[1]
$primaryResourceTypeResource = $templateContent.resources | Where-Object { $_.type -eq $primaryResourceType }

if ($primaryResourceTypeResource.keys -contains 'location') {
# If the main resource has a location property, an output should be returned too
$outputs.keys | Should -Contain 'location'

# It should further reference the location property of the primary resource and not e.g. the location input parameter
$outputs.location.value | Should -Match $primaryResourceType
}
$LocationParamFlag | Should -Not -Contain $false
}

It '[<moduleFolderName>] Standard outputs should be provided (e.g. resourceName, resourceId, resouceGroupName)' -TestCases $deploymentFolderTestCases {
It '[<moduleFolderName>] Resource Group output should exist for resources that are deployed into a resource group scope' -TestCases $deploymentFolderTestCases {

param(
[string] $moduleFolderName,
$templateContent,
[string] $templateFilePath
)

$outputs = $templateContent.outputs.Keys
$deploymentScope = Get-ScopeOfTemplateFile -TemplateFilePath $templateFilePath

if ($deploymentScope -eq 'resourceGroup') {
$outputs | Should -Contain 'resourceGroupName'
}
}

It '[<moduleFolderName>] Resource name output should exist' -TestCases $deploymentFolderTestCases {
param(
$moduleFolderName,
$templateContent
)

$Stdoutput = $templateContent.outputs.Keys
$i = 0
$Schemaverion = $templateContent.'$schema'
if ((($Schemaverion.Split('/')[5]).Split('.')[0]) -eq (($RGdeployment.Split('/')[5]).Split('.')[0])) {
# Resource Group Level deployment
foreach ($Stdo in $Stdoutput) {
if ($Stdo -like '*Name*' -or $Stdo -like '*ResourceId*' -or $Stdo -like '*ResourceGroup*') {
$true | Should -Be $true
$i = $i + 1
}
}
$i | Should -Not -BeLessThan 3
} ElseIf ((($schemaverion.Split('/')[5]).Split('.')[0]) -eq (($Subscriptiondeployment.Split('/')[5]).Split('.')[0])) {
# Subscription Level deployment
$Stdoutput | Should -Not -BeNullOrEmpty
}
$outputs = $templateContent.outputs.Keys
$outputs | Should -Contain 'name'
}

It '[<moduleFolderName>] Resource ID output should exist' -TestCases $deploymentFolderTestCases {
param(
$moduleFolderName,
$templateContent
)

$outputs = $templateContent.outputs.Keys
$outputs | Should -Contain 'resourceId'
}

It "[<moduleFolderName>] parameters' description shoud start with a one word category followed by a dot, a space and the actual description text." -TestCases $deploymentFolderTestCases {
Expand Down Expand Up @@ -844,18 +847,22 @@ Describe "API version tests [All apiVersions in the template should be 'recent']
if (Test-Path (Join-Path $moduleFolderPath 'deploy.bicep')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.bicep'
$templateContent = az bicep build --file $templateFilePath --stdout | ConvertFrom-Json -AsHashtable
} elseif (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
} elseIf (Test-Path (Join-Path $moduleFolderPath 'deploy.json')) {
$templateFilePath = Join-Path $moduleFolderPath 'deploy.json'
$templateContent = Get-Content $templateFilePath -Raw | ConvertFrom-Json -AsHashtable
} else {
throw "No template file found in folder [$moduleFolderPath]"
}
$convertedTemplates[$moduleFolderPathKey] = $templateContent
$convertedTemplates[$moduleFolderPathKey] = @{
templateFilePath = $templateFilePath
templateContent = $templateContent
}
} else {
$templateContent = $convertedTemplates[$moduleFolderPathKey]
$templateContent = $convertedTemplates[$moduleFolderPathKey].templateContent
$templateFilePath = $convertedTemplates[$moduleFolderPathKey].templateFilePath
}

$nestedResources = Get-NestedResourceList -TemplateContent $templateContent | Where-Object {
$nestedResources = Get-NestedResourceList -TemplateFileContent $templateContent | Where-Object {
$_.type -notin @('Microsoft.Resources/deployments') -and $_
} | Select-Object 'Type', 'ApiVersion' -Unique | Sort-Object Type

Expand Down
42 changes: 4 additions & 38 deletions arm/.global/shared/helper.psm1
Original file line number Diff line number Diff line change
@@ -1,39 +1,5 @@
<#
.SYNOPSIS
Get a list of all resources (provider + service) in the given template content
# Load used functions
$repoRootPath = (Get-Item $PSScriptRoot).Parent.Parent.Parent.FullName

.DESCRIPTION
Get a list of all resources (provider + service) in the given template content. Crawls through any children & nested deployment templates.

.PARAMETER TemplateFileContent
Mandatory. The template file content object to crawl data from

.EXAMPLE
Get-NestedResourceList -TemplateFileContent @{ resource = @{}; ... }

Returns a list of all resources in the given template object
#>
function Get-NestedResourceList {

[CmdletBinding()]
param(
[Parameter(Mandatory)]
[hashtable] $TemplateContent
)

$res = @()
$currLevelResources = @()
if ($TemplateContent.resources) {
$currLevelResources += $TemplateContent.resources
}
foreach ($resource in $currLevelResources) {
$res += $resource

if ($resource.type -eq 'Microsoft.Resources/deployments') {
$res += Get-NestedResourceList -TemplateContent $resource.properties.template
} else {
$res += Get-NestedResourceList -TemplateContent $resource
}
}
return $res
}
. (Join-Path $repoRootPath 'utilities' 'pipelines' 'sharedScripts' 'Get-NestedResourceList.ps1')
. (Join-Path $repoRootPath 'utilities' 'pipelines' 'sharedScripts' 'Get-ScopeOfTemplateFile.ps1')
3 changes: 3 additions & 0 deletions arm/Microsoft.AAD/DomainServices/deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,6 @@ output resourceGroupName string = resourceGroup().name

@description('The resource ID of the Azure Active Directory Domain Services(Azure ADDS).')
output resourceId string = domainService.id

@description('The location the resource was deployed into.')
output location string = domainService.location
1 change: 1 addition & 0 deletions arm/Microsoft.AAD/DomainServices/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ $pfxCertificate = [System.Convert]::ToBase64String($rawCertByteStream)

| Output Name | Type | Description |
| :-- | :-- | :-- |
| `location` | string | The location the resource was deployed into. |
| `name` | string | The domain name of the Azure Active Directory Domain Services(Azure ADDS). |
| `resourceGroupName` | string | The name of the resource group the Azure Active Directory Domain Services(Azure ADDS) was created in. |
| `resourceId` | string | The resource ID of the Azure Active Directory Domain Services(Azure ADDS). |
3 changes: 3 additions & 0 deletions arm/Microsoft.AnalysisServices/servers/deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,6 @@ output resourceId string = server.id

@description('The resource group the analysis service was deployed into.')
output resourceGroupName string = resourceGroup().name

@description('The location the resource was deployed into.')
output location string = server.location
1 change: 1 addition & 0 deletions arm/Microsoft.AnalysisServices/servers/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Create a role assignment for the given resource. If you want to assign a service

| Output Name | Type | Description |
| :-- | :-- | :-- |
| `location` | string | The location the resource was deployed into. |
| `name` | string | The name of the analysis service. |
| `resourceGroupName` | string | The resource group the analysis service was deployed into. |
| `resourceId` | string | The resource ID of the analysis service. |
3 changes: 3 additions & 0 deletions arm/Microsoft.ApiManagement/service/deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,6 @@ output resourceGroupName string = resourceGroup().name

@description('The principal ID of the system assigned identity.')
output systemAssignedPrincipalId string = systemAssignedIdentity && contains(apiManagementService.identity, 'principalId') ? apiManagementService.identity.principalId : ''

@description('The location the resource was deployed into.')
output location string = apiManagementService.location
3 changes: 1 addition & 2 deletions arm/Microsoft.ApiManagement/service/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ You can specify multiple user assigned identities to a resource by providing add

| Output Name | Type | Description |
| :-- | :-- | :-- |
| `location` | string | The location the resource was deployed into. |
| `name` | string | The name of the API management service. |
| `resourceGroupName` | string | The resource group the API management service was deployed into. |
| `resourceId` | string | The resource ID of the API management service. |
Expand All @@ -167,5 +168,3 @@ You can specify multiple user assigned identities to a resource by providing add
## Considerations

- *None*

=======
3 changes: 3 additions & 0 deletions arm/Microsoft.Authorization/policyAssignments/deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,6 @@ output principalId string = empty(subscriptionId) && empty(resourceGroupName) ?

@sys.description('Policy Assignment resource ID')
output resourceId string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.resourceId : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.resourceId : policyAssignment_rg.outputs.resourceId)

@sys.description('The location the resource was deployed into.')
output location string = empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_mg.outputs.location : (!empty(subscriptionId) && empty(resourceGroupName) ? policyAssignment_sub.outputs.location : policyAssignment_rg.outputs.location)
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,6 @@ output principalId string = identity == 'SystemAssigned' ? policyAssignment.iden

@sys.description('Policy Assignment resource ID')
output resourceId string = extensionResourceId(tenantResourceId('Microsoft.Management/managementGroups', managementGroupId), 'Microsoft.Authorization/policyAssignments', policyAssignment.name)

@sys.description('The location the resource was deployed into.')
output location string = policyAssignment.location
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ With this module you can perform policy assignments on a management group level.

| Output Name | Type | Description |
| :-- | :-- | :-- |
| `location` | string | The location the resource was deployed into. |
| `name` | string | Policy Assignment Name |
| `principalId` | string | Policy Assignment principal ID |
| `resourceId` | string | Policy Assignment resource ID |
Loading