Getting Started with Testing Infrastructure Code

Hi, my name is Bernie White, I am a Premier Field Engineer at Microsoft. Today I'd like to show you how you can get started with testing infrastructure code using an open source project called PSRule within Azure Pipelines.

Within a culture, implementing testing embedded in a (CI) process is an essential practice. Integrating testing into a CI process allows issues to be found early, ideally before code is released into an environment.

For application code, testing within CI is widely accepted and adopted as a best practice.

Testing infrastructure code, on the other hand is often less widely adopted. Most customers understand the benefits of implementing security, best practices and organization standards into CI but are not comfortable implementing such controls.

This post aims to introduce you to an open source tool that allows you to:

  1. Quickly implement best practice testing within CI for Azure Resource Manager (ARM) templates.
  2. Evolve over time to meet organizational standards and compliance requirements.

Introducing PSRule

PSRule is a cross-platform PowerShell module (Windows, , and MacOS) with commands to test infrastructure code. PSRule is modular, allowing the creation of rules using PowerShell code.

For Azure, a set of over 100 rules already exist. These rules can be added to an (IaC) pipeline quickly and then extended as organizational requirements and standards mature.

Three (3) steps for getting started with PSRule for Azure:

  1. Link parameters files to templates
  2. Configure a CI pipeline
  3. Suppress exceptions

Step 1: Linking parameters files to templates

One of the challenges testing infrastructure code is that ARM templates are designed to be reusable. Template reusability is a big pro, however in the context of testing it is hard to understand if a templated resource is compliant. A simple parameter may be the difference between a compliant or non-compliant configuration.

Consider secureTransferRequired = false, does not enforce data transfer for a account while secureTransferRequired = true does.

    "comments": "Storage Account",
    "type": "Microsoft.Storage/storageAccounts",
    "apiVersion": "2019-06-01",
    "name": "[parameters('storageAccountName')]",
    "location": "[parameters('location')]",
    "sku": {
        "name": "[parameters('sku')]",
        "tier": "Standard"
    "kind": "StorageV2",
    "properties": {
        "supportsHttpsTrafficOnly": "[parameters('secureTransferRequired')]"
    "tags": "[parameters('tags')]"

To solve this context issue, PSRule provides a method to link parameter files to template files and resolve parameters, variables, conditions and functions within the ARM template.

To link parameter files to a template file, include a metadata block within the parameter file.

    "$schema": "",
    "contentVersion": "",
    "metadata": {
        "template": "./Resources.Storage.Template.json"
    "parameters": { }

The metadata block is ignored by ARM. By specifying the metadata.template property, we can identify the specific template this parameter file refers to. The example above refers to a template file in the same directory as the parameter file by using “./”. Similarly, the following example refers to a template file relative to the current working directory, which would for this example be the root of the repository.

    "$schema": "",
    "contentVersion": "",
    "metadata": {
        "template": "templates/keyvault/v1/template.json"
    "parameters": {}

Step 2: Configure a CI pipeline

In the example, we are going to use the PSRule extension for Azure DevOps in YAML pipelines. Install the PSRule extension from the Virtual Marketplace here:

After installing the extension, create a new YAML pipeline by:

  • Signing into your Azure DevOps organization and navigate to your project.
  • In your project, navigate to the Pipelines page. Then choose the action to create a new pipeline.
  • Walk through the steps of the wizard by first selecting Azure Repos Git (YAML) as the location of your source code.
  • When the list of repositories appears, select your desired infrastructure code repository.
  • Choose the starter pipeline template.
  • Replace the contents of azure-pipelines.yml with the following code.
# PSRule with Azure Pipelines

- master

  vmImage: 'ubuntu-latest'


# Install PSRule.Rules.Azure from the PowerShell Gallery
- task: ps-rule-install@0
    module: PSRule.Rules.Azure   # Install PSRule.Rules.Azure from the PowerShell Gallery.

# Export resource data from parameter files within the current working directory.
- powershell: Get-AzRuleTemplateLink | Export-AzTemplateRuleData -OutputPath out/templates/;
  displayName: 'Export template data'

# Run analysis from JSON files using the `PSRule.Rules.Azure` module and custom rules from `.ps-rule/`.
- task: ps-rule-assert@0
    inputType: inputPath
    inputPath: 'out/templates/*.json'        # Read exported resource data from 'out/templates/'.
    modules: 'PSRule.Rules.Azure'            # Analyze objects using the rules within the PSRule.Rules.Azure PowerShell module.
    # Optionally, also analyze objects using custom rules from '.ps-rule/'.
    source: '.ps-rule/'
    # Optionally, save results to an NUnit report.
    outputFormat: NUnit3
    outputPath: reports/ps-rule-resources.xml

# Publish NUnit report as test results
- task: PublishTestResults@2
  displayName: 'Publish PSRule results'
    testRunTitle: 'PSRule'                          # The title to use for the test run.
    testRunner: NUnit                               # Import report using the NUnit format.
    testResultsFiles: 'reports/ps-rule-*.xml'       # Use previously saved NUnit reports.
    mergeTestResults: true                          # Merge multiple reports.
  condition: succeededOrFailed()                    # Run this task if previous steps succeeded of failed.
  • Select Save and run, then select Commit, and then choose Save and run again.
  • A new run is started. Wait for the run to finish.

If any issue were found, they will be called out as errors in the output.

Errors on a pull request blocking a mergeErrors on a pull request blocking a merge

Errors from pipeline run in Azure PipelinesErrors from pipeline run in Azure Pipelines

Job output from a previous Azure Pipeline runJob output from a previous Azure Pipeline run

Step 3: Suppress exceptions

While best practices are great, there are situations that may need to deviate from pre-built rules. To do this, we can suppress or exclude rules from running. To add an exception, create the ps-rule.yaml file in the root of the repository. Add a key for each rule name to be suppressed and an array of suppressed objects under the suppression property. The rule name and object name can be retrieved from the error output.

In the example below Azure.AKS.Version is the name of the rule, and cluster1 is the name of the object to be suppressed.

# YAML: Using the suppression property
  Azure.AKS.Version: # Rule name
  - cluster1         # Suppressed object 1
  - cluster2         # Suppressed object 2

You can also read PSRule FAQ for additional options. You can also report an issue here:

In closing, I hope this post gives you a few ideas to start moving forward with testing infrastructure code.

The sample are not supported under any Microsoft standard support program or service. The sample are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.


This article was originally published by Microsoft's Core Infrastructure and Security Blog. You can find the original article here.