Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Azure/Enterprise-Scale into…
Browse files Browse the repository at this point in the history
… policy-refresh-q1fy25
  • Loading branch information
Springstone committed Aug 15, 2024
2 parents f1554a8 + 328b900 commit 658f2cb
Show file tree
Hide file tree
Showing 20 changed files with 362 additions and 567 deletions.
99 changes: 99 additions & 0 deletions .github/actions-pester/PolicyPesterTestHelper.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,102 @@ function Get-PolicyFiles
return $_
}
}

function Remove-JSONMetadata {

[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[hashtable] $TemplateObject
)
$TemplateObject.Remove('metadata')

# Differantiate case: With user defined types (resources property is hashtable) vs without user defined types (resources property is array)
if ($TemplateObject.resources.GetType().BaseType.Name -eq 'Hashtable') {
# Case: Hashtable
$resourceIdentifiers = $TemplateObject.resources.Keys
for ($index = 0; $index -lt $resourceIdentifiers.Count; $index++) {
if ($TemplateObject.resources[$resourceIdentifiers[$index]].type -eq 'Microsoft.Resources/deployments' -and $TemplateObject.resources[$resourceIdentifiers[$index]].properties.template.GetType().BaseType.Name -eq 'Hashtable') {
$TemplateObject.resources[$resourceIdentifiers[$index]] = Remove-JSONMetadata -TemplateObject $TemplateObject.resources[$resourceIdentifiers[$index]].properties.template
}
}
} else {
# Case: Array
for ($index = 0; $index -lt $TemplateObject.resources.Count; $index++) {
if ($TemplateObject.resources[$index].type -eq 'Microsoft.Resources/deployments' -and $TemplateObject.resources[$index].properties.template.GetType().BaseType.Name -eq 'Hashtable') {
$TemplateObject.resources[$index] = Remove-JSONMetadata -TemplateObject $TemplateObject.resources[$index].properties.template
}
}
}

return $TemplateObject
}

function ConvertTo-OrderedHashtable {

[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $JSONInputObject # Must be string to workaround auto-conversion
)

$JSONObject = ConvertFrom-Json $JSONInputObject -AsHashtable -Depth 99 -NoEnumerate
$orderedLevel = [ordered]@{}

if (-not ($JSONObject.GetType().BaseType.Name -eq 'Hashtable')) {
return $JSONObject # E.g. in primitive data types [1,2,3]
}

foreach ($currentLevelKey in ($JSONObject.Keys | Sort-Object -Culture 'en-US')) {

if ($null -eq $JSONObject[$currentLevelKey]) {
# Handle case in which the value is 'null' and hence has no type
$orderedLevel[$currentLevelKey] = $null
continue
}

switch ($JSONObject[$currentLevelKey].GetType().BaseType.Name) {
{ $PSItem -in @('Hashtable') } {
$orderedLevel[$currentLevelKey] = ConvertTo-OrderedHashtable -JSONInputObject ($JSONObject[$currentLevelKey] | ConvertTo-Json -Depth 99)
}
'Array' {
$arrayOutput = @()

# Case: Array of arrays
$arrayElements = $JSONObject[$currentLevelKey] | Where-Object { $_.GetType().BaseType.Name -eq 'Array' }
foreach ($array in $arrayElements) {
if ($array.Count -gt 1) {
# Only sort for arrays with more than one item. Otherwise single-item arrays are casted
$array = $array | Sort-Object -Culture 'en-US'
}
$arrayOutput += , (ConvertTo-OrderedHashtable -JSONInputObject ($array | ConvertTo-Json -Depth 99))
}

# Case: Array of objects
$hashTableElements = $JSONObject[$currentLevelKey] | Where-Object { $_.GetType().BaseType.Name -eq 'Hashtable' }
foreach ($hashTable in $hashTableElements) {
$arrayOutput += , (ConvertTo-OrderedHashtable -JSONInputObject ($hashTable | ConvertTo-Json -Depth 99))
}

# Case: Primitive data types
$primitiveElements = $JSONObject[$currentLevelKey] | Where-Object { $_.GetType().BaseType.Name -notin @('Array', 'Hashtable') } | ConvertTo-Json -Depth 99 | ConvertFrom-Json -AsHashtable -NoEnumerate -Depth 99
if ($primitiveElements.Count -gt 1) {
$primitiveElements = $primitiveElements | Sort-Object -Culture 'en-US'
}
$arrayOutput += $primitiveElements

if ($array.Count -gt 1) {
# Only sort for arrays with more than one item. Otherwise single-item arrays are casted
$arrayOutput = $arrayOutput | Sort-Object -Culture 'en-US'
}
$orderedLevel[$currentLevelKey] = $arrayOutput
}
Default {
# string/int/etc.
$orderedLevel[$currentLevelKey] = $JSONObject[$currentLevelKey]
}
}
}

return $orderedLevel
}
62 changes: 62 additions & 0 deletions .github/actions-pester/Test-BuildPolicies.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
Describe 'UnitTest-BuildPolicies' {

BeforeAll {
Import-Module -Name $PSScriptRoot\PolicyPesterTestHelper.psm1 -Force -Verbose

New-Item -Name "buildout" -Type Directory

# Build the PR policies, initiatives, and role definitions to a temp folder
bicep build ./src/templates/policies.bicep --outfile ./buildout/policies.json
bicep build ./src/templates/initiatives.bicep --outfile ./buildout/initiatives.json
bicep build ./src/templates/roles.bicep --outfile ./buildout/customRoleDefinitions.json
}

Context "Check Policy Builds" {

It "Check policies build done" {
$prFile = "./eslzArm/managementGroupTemplates/policyDefinitions/policies.json"
$buildFile = "./buildout/policies.json"

$buildJson = Remove-JSONMetadata -TemplateObject (Get-Content $buildFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$buildJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $buildJson -Depth 99)

$prJson = Remove-JSONMetadata -TemplateObject (Get-Content $prFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$prJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $prJson -Depth 99)

# Compare files we built to the PR files
(ConvertTo-Json $buildJson -Depth 99) | Should -Be (ConvertTo-Json $prJson -Depth 99) -Because "the [policies.json] should be based on the latest [policies.bicep] file. Please run [` bicep build ./src/templates/policies.bicep --outfile ./eslzArm/managementGroupTemplates/policyDefinitions/policies.json `] using the latest Bicep CLI version."
}

It "Check initiatives build done" {
$PRfile = "./eslzArm/managementGroupTemplates/policyDefinitions/initiatives.json"
$buildFile = "./buildout/initiatives.json"

$buildJson = Remove-JSONMetadata -TemplateObject (Get-Content $buildFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$buildJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $buildJson -Depth 99)

$prJson = Remove-JSONMetadata -TemplateObject (Get-Content $prFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$prJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $prJson -Depth 99)

# Compare files we built to the PR files
(ConvertTo-Json $buildJson -Depth 99) | Should -Be (ConvertTo-Json $prJson -Depth 99) -Because "the [initiatives.json] should be based on the latest [initiatives.bicep] file. Please run [` bicep build ./src/templates/initiatives.bicep --outfile ./eslzArm/managementGroupTemplates/policyDefinitions/initiatives.json `] using the latest Bicep CLI version."
}

It "Check role definitions build done" {
$PRfile = "./eslzArm/managementGroupTemplates/roleDefinitions/customRoleDefinitions.json"
$buildFile = "./buildout/customRoleDefinitions.json"

$buildJson = Remove-JSONMetadata -TemplateObject (Get-Content $buildFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$buildJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $buildJson -Depth 99)

$prJson = Remove-JSONMetadata -TemplateObject (Get-Content $prFile -Raw | ConvertFrom-Json -Depth 99 -AsHashtable)
$prJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $prJson -Depth 99)

# Compare files we built to the PR files
(ConvertTo-Json $buildJson -Depth 99) | Should -Be (ConvertTo-Json $prJson -Depth 99) -Because "the [customRoleDefinitions.json] should be based on the latest [customRoleDefinitions.bicep] file. Please run [` bicep build ./src/templates/roles.bicep --outfile ./eslzArm/managementGroupTemplates/roleDefinitions/customRoleDefinitions.json `] using the latest Bicep CLI version."
}
}

AfterAll {
# These are not the droids you are looking for...
}
}
15 changes: 15 additions & 0 deletions .github/actions-pester/Test-ModifiedPolicies.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ Describe 'UnitTest-ModifiedPolicies' {
}
}

It "Check policy metadata name matches policy filename" {
$ModifiedAddedFiles | ForEach-Object {
$PolicyJson = Get-Content -Path $_ -Raw | ConvertFrom-Json
$PolicyFile = Split-Path $_ -Leaf
$PolicyMetadataName = $PolicyJson.name
$PolicyFileNoExt = [System.IO.Path]::GetFileNameWithoutExtension($PolicyFile)
if ($PolicyFileNoExt.Contains("AzureChinaCloud") -or $PolicyFileNoExt.Contains("AzureUSGovernment"))
{
$PolicyFileNoExt = $PolicyFileNoExt.Substring(0, $PolicyFileNoExt.IndexOf("."))
}
Write-Warning "$($PolicyFileNoExt) - This is the policy metadata name: $($PolicyMetadataName)"
$PolicyMetadataName | Should -Be $PolicyFileNoExt
}
}

}

Context "Validate policy parameters" {
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/check-policy-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: Check Policy Build

##########################################
# Start the job on PR for all branches #
##########################################

# yamllint disable-line rule:truthy
on:
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
paths:
- "eslzArm/**.json"
- "src/Alz.Tools/**"
- "src/**.json"
- "src/**.bicep"

###############
# Set the Job #
###############

jobs:
check-policy:
name: Check Policy Build
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Check build
shell: pwsh
run: |
Import-Module Pester -Force
$pesterConfiguration = @{
Run = @{
Container = New-PesterContainer -Path "./.github/actions-pester/Test-BuildPolicies.Tests.ps1"
PassThru = $true
}
Output = @{
Verbosity = 'Detailed'
}
}
$result = Invoke-Pester -Configuration $pesterConfiguration
exit $result.FailedCount
125 changes: 0 additions & 125 deletions .github/workflows/update-portal.yml

This file was deleted.

8 changes: 7 additions & 1 deletion docs/wiki/ALZ-Contribution-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@ For a policy set definition, additional code should be added inside of the `load

`loadTextContent('../resources/Microsoft.Authorization/policySetDefinitions/Deploy-Sql-Security.json')`

The policy definition files will be compiled into a `policies.json` file from the `policy.bicep` file which was amended.
~~The policy definition files will be compiled into a `policies.json` file from the `policy.bicep` file which was amended.~~

> Due to security compliance requirements, we've made core changes that mean we no longer automatically build the policies, initiatives and roles templates after changes in the `src` folder are committed. This means that you as a contributor must run the bicep build commands to generate the required outputs as part of your pull request. Depending on the files you've updated these are the commands:
>
> - `bicep build ./src/templates/policies.bicep --outfile ./eslzArm/managementGroupTemplates/policyDefinitions/policies.json`
> - `bicep build ./src/templates/initiatives.bicep --outfile ./eslzArm/managementGroupTemplates/policyDefinitions/initiatives.json`
> - `bicep build ./src/templates/roles.bicep --outfile ./eslzArm/managementGroupTemplates/roleDefinitions/customRoleDefinitions.json`

Once the policy work has been completed, a pull request should be submitted to the repository:

Expand Down
Loading

0 comments on commit 658f2cb

Please sign in to comment.