Learn how to use free Microsoft hosted agents in Azure DevOps to run Chef Test Kitchen. Examples of azure-pipeline files using only PowerShell as well as the Chef Integration from the marketplace using variables and variable groups.

There is a limit of one parallel (concurrent) build on private azure devops projects. If your project is public, you can run 10 parallel builds for free.

Assumptions

This posts assumes you understand the basics steps required for setting up a test kitchen using the azurerm driver, if you need to review them, go here

Where to start

The Microsoft hosted free build agents, do not come with Chef on them.

In order for us to run test kitchen, we’ve got to orchestrate the same steps required to get test kitchen running locally but in the azure-pipelines.yml file:

Steps to Replicate

  • Install the Chef DK
  • Add the Chef DK to the system environment variable for the path (since this will be executed in one powershell session)
  • Create the .azure folder with a credentials file
  • Install the gem for azurerm test kitchen
  • Run test kitchen
  • Before we start writing code, I’d recommend staging your variables in Azure DevOps. I created a variable group for all my test kitchen variables called test-kitchen

Variable groups

I created a variable group for all my test kitchen variables called test-kitchen

variables-groups

All pipelines can access this variable group in the screen grab above but you can also restrict access. You should also encrypt your sensitive variables (i.e. ClientSecret), you can do that by clicking the lock icon to the right after you enter them.

Now that we’ve pre-staged our variables, there are two ways you can setup CI. Which one you want to select is up to you.

  • PowerShell Steps
  • Chef Integration for Azure Pipelines

let’s look at what our azure-pipelines.yml looks like…

PowerShell Steps

# Starter pipeline

# Start with a minimal pipeline that you can customize to build and deploy your code.

# Add steps that build, run tests, deploy, and more:

# https://aka.ms/yaml


trigger:
  branches:
    include:
      - '*'

pool:
  vmImage: 'VS2017-Win2016'

variables:
  - group: test-kitchen
  - name: cookbookPath
    value: "$(Build.Repository.LocalPath)\\< cookbook source location >"
  - name: agentUserPath
    value: "C:/Users/VssAdministrator"

steps:
  - powershell: |
      . { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; install -channel current -project chefdk

    displayName: 'Install Chef DK'

  - powershell: |
      Write-Host "##vso[task.setvariable variable=PATH;]${env:PATH};C:\opscode\chefdk\bin";

    displayName: 'Add Chef to Path'

  - powershell: |
      New-Item -ItemType Directory -Path '$(agentUserPath)/.azure'

    displayName: 'Create .azure directory'

  - powershell: |
      New-Item -ItemType File -Path '$(agentUserPath)/.azure/credentials' -Force

    displayName: 'Create credentials file'

  - powershell: |
      Set-Content -Path '$(agentUserPath)/.azure/credentials' -Value `

      "[$env:azure_subscription]
      client_id = $(ClientId)
      client_secret = $(ClientSecret)
      tenant_id = $(TenantId)"
    env:
      ClientSecret: $(Client_Secret) # required for referencing secure variable

    displayName: 'Set content for credentials file'

  - powershell: |
      Set-Location $(cookbookPath)

      kitchen test --destroy==always
    displayName: 'Run Test Kitchen'

All the powershell commands above are run inline but you could also put them into a script and call that specific script in your azure-pipelines.yml file.

Chef Integration for Azure Pipelines

In order to run these steps you must have the Chef Integration extension from the marketplace. It is free to download.

Just hit the shopping bag icon in the upper right and search for Chef Integration. Once installed, you can call the tasks.

A heads up that in the documentation (here) for installing the chef-dk it notes that a hosted windows agent is not compatible but I haven’t had any issues.

trigger:
  branches:
    include:
      - '*'

pool:
  vmImage: 'windows-2019'

variables:
  - group: test-kitchen
  - name: cookbookPath
    value: "$(Build.Repository.LocalPath)\\< cookbook source location >"
  - name: agentUserPath
    value: "C:/Users/VssAdministrator"

steps:
  - task: vsts-chef-task-install-chefdk@1
    inputs:
      chefDKChannel: 'stable'
    displayName: 'Install Chefdk'

  - powershell: |
      Set-Location "$(agentUserPath)"

      $null = New-Item -Type Directory -Path '.azure'
      Set-Content .azure\credentials "[$env:azure_subscription]
      client_id = $env:ClientID
      client_secret = $env:ClientSecret
      tenant_id = $env:TenantId"
    env:
      ClientSecret: $(Client_Secret)
    displayName: 'Configure credentials for kitchen-azurem'

  - task: vsts-chef-task-test-kitchen@1
    inputs:
      tkAzureEndpoint: 'chef-test-kitchen'
      tkCommand: 'test --destroy==always'
      tkKitchenFile: 'kitchen.yml'
      tkKitchenFolder: '$(cookbookPath)'
    displayName: 'Run Test Kitchen'

If you do decide to use a variable group, take note of the syntax. We must follow the styling to reference group variables and also set variables in this file.

There is also a very specific way for referencing encrypted secrets, explained by Microsoft here

Add chef to the path environment variable

In order to add the chef executable to the path we must do it by running a logging command (referenced from stack overflow here).

Running a build

My build with minimal chef logic takes about 17 minutes to run all steps (no matter which method is selected):

all-build-steps

During my “Run Test Kitchen” there is a TON of output, but here are some screen grabs of a chef-client running finishing and tests passing:

inspec-tests

my test kitchen resources needed to be manually deleted from Azure before I got a fully functional pipe, so make sure they aren’t sitting out there costing you $$$.

If you are curious about my .kitchen.yml file, I am keeping it as simple as possible:

---
driver:
  name: azurerm
  subscription_id: "<%= ENV['azure_subscription'] %>" # make sure the environment variable is set

  location: 'EastUS2'
  machine_size: 'Standard_D2s_v3'
  username: "azure"

transport:
  name: winrm
  elevated: true

verifier:
  name: inspec

provisioner:
  name: chef_zero
  deprecations_as_errors: true
  retry_on_exit_code:
    - 20
    - 35
    - 259 # required exit code for powershell 5 to install without a chef client failure

  max_retries: 15
  wait_for_retry: 180
  client_rb:
    exit_status: :enabled

platforms:
  - name: windows2012R2-sql2016
    driver:
      image_urn: "MicrosoftSQLServer:SQL2016-WS2012R2:SQLDEV:latest"

suites:
  - name: default
    run_list:
      - recipe[powershell::powershell5]
    attributes:
      powershell:
        installation_reboot_mode: 'delayed_reboot'