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 9 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...
}
}
74 changes: 74 additions & 0 deletions .github/workflows/check-policy-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
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"

env:
github_user_name: "github-actions"
github_email: "41898282+github-actions[bot]@users.noreply.github.com"
github_pr_number: ${{ github.event.number }}

permissions:
contents: write
Springstone marked this conversation as resolved.
Show resolved Hide resolved

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

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

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Show env
run: env | sort

- name: Check out PR
run: |
echo "==> Check out PR..."
gh pr checkout "$github_pr_number"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# - name: Configure local git
# run: |
# echo "git user name : $github_user_name"
# git config --global user.name "$github_user_name"
# echo "git user email : $github_email"
# git config --global user.email "$github_email"

Springstone marked this conversation as resolved.
Show resolved Hide resolved
- 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: 8 additions & 0 deletions docs/wiki/Whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ This article will be updated as and when changes are made to the above and anyth

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`
jtracey93 marked this conversation as resolved.
Show resolved Hide resolved

### July 2024

#### Policy
Expand Down
Loading