Testing your Bicep modules with PSRule is a great way to identify that your code is meeting Azure best practice.
But how do you actually test your Bicep modules with PSRule properly?
How can you structure your Git repository?
How can I configure PSRule to test the modules? Can I document what the modules do, and their params?
I’ll go into depth on this below 😄
All of the content shared here is uploaded to my GitHub repository which can be found here: bicep-module-tests/ at main · riosengineer/bicep-module-tests (github.com).
PSRule cannot run reliably against your actual Bicep templates. Errors like
"The parameter named 'someParam' was not set or a defaultValue was defined." may be familiar if you’ve stumbled across this post.
Why? PSRule needs actual parameter values in order to expand and run the ruleset to determine what could be deployed. The PSRule creator Bernie White explains this well in this GitHub issue: Expansion & defaultValues · microsoft/PSRule · Discussion #1256 (github.com)
With this in mind, it’s best to try and not run PSRule directly against your Bicep templates to get a more reliable CI pipeline experience.
. └── bicep/ ├── modules/ │ ├── storageAccount/ │ │ ├── storageAccount.bicep │ │ ├── metadata.json │ │ └── README.md │ └── .tests/ │ └── storageAccount.tests.bicep └── prod/ ├── main.bicep └── prd.bicepparam
By structuring the repository like the above example we’re going to be able to target PSRule to run against the ‘.tests’ files to get a consistent scan output on our Azure best practices.
This will mean we can get PSRule to scan the module and tell us if there’s any best practice gaps we may want to consider adding to the module.
You can see we have a sub-modules folder for in-line Bicep modules. We also have a separate folder for our actual deployments which reference our modules as seen in the example above. This means we can shift a lot of the scanning onto the tests file and off the templates that may be problematic, this is because the modules will be called by the main templates for deployments.
The test file will comprise of only the required parameters with actual values populated. As mentioned earlier, this will ensure PSRule can fully scan the code and analyse it for us.
The test file needs to be within it’s own sub-folder of the module like the above structure hierarchy.
Here’s an example test Bicep module file:
The Bicep modules are located within an inline ‘modules’ folder, separating out each module like so:
└── bicep/ └── modules/ ├── storageAccount/ ├── storage.bicep └── metadata.json
A great way to display the parameters of the module, along with all relevant metadata & descriptions is to have a readme present in the module folder. This helps other people understand the module, and it’s parameters. This metadata json file sits within the modules/storageAccount folder (as seen above in the structure).
Here’s an example of what the
metadata.json file looks like:
With this JSON file we can run an absolutely AWESOME readme script created by Microsoft MVP Tao Yang which auto-generates a markdown file with all the information. Tao’s blog on how this works is here: Generating README for Bicep Files – Managing Cloud and Datacenter by Tao Yang (tyang.org)
I would highly recommend you head over there and check it out.
Here’s a sample output from my example module:
Azure DevOps Pipeline
Next is the ADO pipeline, you’ll need to install the PSRule extension into your Organisation if you haven’t got it already for this to work. Additionally, you’ll likely need a build validation policy so this will trigger on PR automatically. I’ve written a guide about that before here.
In my example Repository in GitHub you can find the structure on all of this so far, and also the pipeline file like so:
This file needs to be part of your build validation so you can run PSRule when changes are made to existing modules, or new ones added to the repository.
PSRule module scan failure
Amending my tests file to have
storageSkuName: 'Standard_LRS' we can see the pipeline gives us a best practice recommendation from PSRule:
PSRule module scan success
However, when changing the value to
storageSkuName: 'Standard_GRS' we can see the pipeline is happy with the Bicep module: