Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New policy build workflow #1725

Merged
merged 17 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
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...
}
}
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
6 changes: 6 additions & 0 deletions docs/wiki/Whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ Here's what's changed in Enterprise Scale/Azure Landing Zones:

### August 2024

> NOTE TO CONTRIBUTORS: 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 (assuming you have bicep installed):
>
> - `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`

#### Other

- Cleaned up the Log Analytics "solutions" in portal ARM template, as these are no longer required and deployed by ALZ.
Expand Down
Loading