This article is contributed. See the original author and article here.

 

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 DevOps culture, implementing testing embedded in a continuous integration (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, Linux, and MacOS) with commands to test infrastructure code. PSRule is modular, allowing the creation of rules using PowerShell code.

 

https://github.com/microsoft/PSRule

 

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

 

https://github.com/microsoft/PSRule.Rules.Azure

 

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 encrypted data transfer for a storage 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": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "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": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "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: https://marketplace.visualstudio.com/items?itemName=bewhite.ps-rule

 

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
#

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

steps:

# Install PSRule.Rules.Azure from the PowerShell Gallery
- task: ps-rule-install@0
  inputs:
    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
  inputs:
    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'
  inputs:
    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
suppression:
  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: https://github.com/microsoft/PSRule.Rules.Azure/issues

 

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

 

Disclaimer
The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts 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 scripts 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.

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.