Skip to content

Commit

Permalink
Testing Bicep Modules with PSRule
Browse files Browse the repository at this point in the history
Example folder for testing bicep modules with PSRule, documentation, pipeline & Git folder structure example.

Fixing linting

Adding GH Actions YAML

Added GH actions YAML CI file example.

Update github-action-psrule-ci.yaml
  • Loading branch information
riosengineer committed Sep 26, 2023
1 parent 0f63f4b commit c64e896
Show file tree
Hide file tree
Showing 10 changed files with 697 additions and 1 deletion.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ We aim to do this by creating real example templates that are easy to digest and
If you find this repository useful, please save the repository by hitting the ⭐

[![MegaLinter](https://github.com/riosengineer/Bicepify/actions/workflows/mega-linter.yml/badge.svg)](https://github.com/riosengineer/Bicepify/actions/workflows/mega-linter.yml) ![Contributors](https://img.shields.io/github/contributors/RiosEngineer/Bicepify?color=dark-green) ![GitHub Repo stars](https://img.shields.io/github/stars/riosengineer/bicepify)
![Static Badge](https://img.shields.io/badge/Bicep-Learning-blue?logo=microsoftazure&color=%230078D4) ![Static Badge](https://img.shields.io/badge/Bicep-DevOps-blue?logo=azuredevops&color=%230078D4)
![Static Badge](https://img.shields.io/badge/Bicep-Learning-blue?logo=microsoftazure&color=%230078D4&link=https%3A%2F%2Fgithub.com%2Friosengineer%2FBicepify%2Ftree%2Fmain%2Fbicep-examples) ![Static Badge](https://img.shields.io/badge/Bicep-Azure_DevOps-blue?logo=azuredevops&color=%230078D4&link=https%3A%2F%2Fgithub.com%2Friosengineer%2FBicepify%2Ftree%2Fmain%2Fbicep-cicd-examples) ![Static Badge](https://img.shields.io/badge/Bicep-GitHub%20Actions-blue?logo=GitHubActions&logoColor=white&color=%230078D4&link=https%3A%2F%2Fgithub.com%2Friosengineer%2FBicepify%2Ftree%2Fmain%2Fbicep-cicd-examples)


## 🧬 New to Bicep? Getting started

Expand Down Expand Up @@ -43,6 +44,8 @@ Where possible, examples will be complimented by a blog post giving a deeper div

## 📎 Useful links

[Rios Engineer - All Things Azure blog](https://rios.engineer/)

[What is Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep)

[Bicep tools](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install)
Expand Down
131 changes: 131 additions & 0 deletions bicep-cicd-examples/module-tests-with-psrule/.scripts/bicep-readme.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#requires -Modules @{ModuleName='PSDocs';ModuleVersion='0.9.0'}, @{ModuleName='PSDocs.Azure';ModuleVersion='0.3.0'}
# Make sure the below modules are installed:
# Install-Module PSDocs
# Install-Module PSDocs.Azure
# If you have done that before, proceed onto the next steps:
# Create a new metadata.json in the relevant modules folder and fill out some details
# Then to generate ReadMe run the below command example BEFORE PR
# .scripts\bicep-readme.ps1 -templatePath file.bicep -Verbose
<#
===============================================
AUTHOR: Tao Yang
DATE: 02/02/2023
NAME: generateBicepReadme.ps1
VERSION: 1.0.0
COMMENT: Generate Readme.md for Bicep template
===============================================
#>

[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true, HelpMessage = "Template Path.")]
[ValidateScript({ test-path $_ -PathType leaf })][String]$templatePath
)

#region functions
Function ValidateMetadata {
[CmdletBinding()]
[outputType([boolean])]
Param
(
[Parameter(Mandatory = $true)][String]$metadataPath
)
$bValidMetadata = $true
$requiredAttributes = 'itemDisplayName', 'description', 'summary'
If (Test-Path $metadataPath) {
try {
$metadata = Get-Content $metadataPath -Raw | convertFrom-Json
Foreach ($attribute in $requiredAttributes) {
if (!$($metadata.$attribute) -or $($($metadata.$attribute).length) -eq 0) {
Write-Error "Metadata is missing attribute: $attribute or $attribute is empty"
$bValidMetadata = $false
break
}
}
} catch {
$bValidMetadata = $false
}
} else {
$bValidMetadata = $false
}
$bValidMetadata
}

Function BicepBuild {
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)][String]$bicepPath
)
$bicepDir = (Get-Item -Path $bicepPath).DirectoryName;
$bicepFileBaseName = (Get-Item -Path $bicepPath).BaseName
$outputFile = Join-Path $bicepDir "$bicepFileBaseName`.json"
Write-Verbose "Building ARM template $outputFile from Bicep file $bicepPath"
az bicep Build --file $bicepPath --outfile $outputFile
$outputFile
}
#endregion
#region main
$docName = "README"
$currentDir = $PWD
Write-Output "Current working directory '$currentDir'"
$templateDir = (Get-Item -Path $templatePath).DirectoryName;
Write-Verbose "Template Directory '$templateDir'"
set-location $templateDir
Write-Verbose "Detecting git root directory"
$gitRootDir = invoke-expression 'git rev-parse --show-toplevel' -ErrorAction SilentlyContinue
if ($gitRootDir.length -gt 0) {
Write-Verbose "Git root directory '$gitRootDir'"
} else {
Write-Verbose "Not a git repository"
}
set-location $currentDir
Import-Module PSDocs.Azure;

$metadataPath = Join-Path $templateDir 'metadata.json';
Write-Verbose "metadata.json file path: '$metadataPath'"
#Validate metadata'
$ValidMetaData = ValidateMetadata -metadataPath $metadataPath

#Convert Bicep to ARM template if input file is bicep
$removeARM = $false
if ((get-item $templatePath).Extension -ieq '.bicep') {
Write-Verbose "Comping bicep file $templatePath to ARM template"
$tempPath = BicepBuild -bicepPath $templatePath
$removeARM = $true
} else {
$tempPath = $templatePath
}
Write-Verbose "Generating Document for $tempPath"
if ($ValidMetaData) {
Get-AzDocTemplateFile -Path $tempPath | ForEach-Object {
# Generate a standard name of the markdown file. i.e. <name>_<version>.md
$template = Get-Item -Path $_.TemplateFile;

# Generate markdown
Invoke-PSDocument -Module PSDocs.Azure -OutputPath $templateDir -InputObject $template.FullName -InstanceName $docName -Culture en-GB

}
} else {
Throw "Invalid metadata in $metadataPath"
Exit -1
}
$outputFilePath = join-path $templateDir "$docName`.md"
If (Test-Path $outputFilePath) {
if ($gitRootDir.length -gt 0) {
# replace the git root dir with a relative path in generated markdown file
Write-Verbose "replacing git root dir '$gitRootDir' with '.' in generated markdown file '$outputFilePath'"
(Get-Content $outputFilePath -Raw) -replace $gitRootDir, '.' | Set-Content $outputFilePath
}

Write-Output "Generated document '$outputFilePath'"
} else {
Write-Error "'$outputFilePath' not found"
}
#Remove temp ARM template if required
if ($removeARM) {
Write-Verbose "Remove temp ARM template $tempPath"
Remove-Item -Path $tempPath -Force
}
#endregion
107 changes: 107 additions & 0 deletions bicep-cicd-examples/module-tests-with-psrule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Bicep Test Modules - Folder structure & examples

## [https://rios.engineer/bicep-modules-with-psrule-testing-documentation-ci-pipeline-examples/](https://rios.engineer/bicep-modules-with-psrule-testing-documentation-ci-pipeline-examples/)

## Introduction

In this example you can get some insight on how to:

- Structure your Git repository FOR Bicep modules
- How to create a 'test' file to scan PSRule with
- How you can create documentation for your Bicep modules
- Why you should consider testing in this method vs scanning your templates directly

For a deeper dive on some of the above bullet points I would recommend reading the blog post attached.

## What is PSRule?

PSRule for Azure helps you identify and remediate issues surrounding Azure best practices in your Azure Bicep code. This results in improved quality of solutions deploy to Azure and better IaC templates that align to the [Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/).

## Modules Folder Structure - Example

The folder structure should resemble the following for the CI pipeline and PSRule to work as intended. With this layout, we can use the `ps-rule.yaml` configuration file to scan only the `*.tests.bicep` files as part of the CI pipeline as the tests file will have the required parameter defined. This results in a more reliable CI experience.

```json
└── bicep/
└── modules/
├── storageAccount/
│ └── .tests/
│ └── storageAccount.tests.bicep
├── storage.bicep
└── metadata.json
```

## Bicep Modules test file - Example

The `.tests.bicep` file will have the required parameters defined for PSRule to scan the module code. The module is called in-line from the `\modules` folder in this directory, for example:

```javascript
// Test with only required parameters
module test_required_params '../storageAccount.bicep' = {
name: 'test_required_params'
params: {
location: 'uksouth'
tags: {
Demo: 'Rios Engineer Blog'
}
storageName: 'riosengineerst001'
storageSkuName: 'Standard_GRS'
storagePleBlobName: 'someBlob'
storagePleFileName: 'someFileshare'
subnetId: 'test'
virtualNetworkId: 'test'
}
}
```

## Bicep Module documentation - Example

The `metadata.json` file is to contain some basic summary information and description of the module. This is so the PowerShell script in `.scripts\` can be ran and uses this metadata to create a `README` file for the module (example README.md output in the `\storageAccount` folder).

```json

{
"itemDisplayName": "Storage Account Module.",
"description": "Storage Account Bicep Module",
"summary": "Storage Account Bicep standard for deployments"
}

```

## Continuous Integration pipeline

There is an example CI pipeline `azure-devops-psrule-ci.yaml` and `github-actions-psrule-ci.yaml` files which you can review and use as part of your build validation pipelines for either Azure DevOps or GitHub. For ADO once the file is created within the repository, you can use it as the [build pipeline](https://learn.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops&tabs=browser#build-validation).

For GitHub Actions, you can follow the documentation if you need a step-by-step [here](https://docs.github.com/en/actions/quickstart).

## PSRule configuration

Lastly, the `ps-rule.yaml` file defines our PSRule scan configuration. Using this file we can define the tests file path for scanning.

Snippet taken from the file:

```yaml
input:
pathIgnore:
# Ignore common files that don't need analysis.
- "**/bicepconfig.json"
- "*.yaml"
- "*.yml"
- "*.md"
- "*.ps1"

# Exclude Bicep module files.
- "bicep/modules/**/*.bicep"

# Exclude JSON module files.
- "bicep/modules/**/*.json"

# Include bicep files from modules.
- "!bicep/modules/**/.tests/*.bicep"
```
This enables the test files to be scanned for Azure best practice. As the tests file have the required parameters defined PSRule can run the scan against the values.
There is a lot of fantastic documentation around this [here](https://azure.github.io/PSRule.Rules.Azure/setup/configuring-options/).
There is more detailed information in the attached blog post with a full demo on this setup.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

trigger: none
name: $(SourceBranchName)_$(date:yyyyMMdd)$(rev:.r)
pool:
vmImage: "ubuntu-latest"

jobs:
- job: 'PSRuleAnalysis'
displayName: "Run PSRule analysis"
steps:
- task: bewhite.ps-rule.assert.ps-rule-assert@2
inputs:
source: "$(System.DefaultWorkingDirectory)/ps-rule.yaml"
modules: PSRule.Rules.Azure, PSRule.Rules.CAF, PSRule.Rules.Kubernetes
outputFormat: Sarif
outputPath: "reports/ps-rule-results.sarif"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Test with only required parameters
module test_required_params '../storageAccount.bicep' = {
name: 'test_required_params'
params: {
location: 'uksouth'
tags: {
Demo: 'Rios Engineer Blog'
}
storageName: 'riosengineerst001'
storageSkuName: 'Standard_GRS'
storagePleBlobName: 'someBlob'
storagePleFileName: 'someFileshare'
subnetId: 'test'
virtualNetworkId: 'test'
}
}
Loading

0 comments on commit c64e896

Please sign in to comment.