Reminder: 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.

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

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:

  • 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

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

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 prestaged our variables, let’s look at what our azure-pipelines.yml looks like:

# 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:


      - '*'

  vmImage: 'VS2017-Win2016'

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

  - powershell: |
      . { iwr -useb } | 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 `

      client_id = $(ClientId)
      client_secret = $(ClientSecret)
      tenant_id = $(TenantId)"
    displayName: 'Set content for credentials file'

  - powershell: |
      chef gem install kitchen-azurerm

    displayName: 'Install Azure Test Kitchen gem'

  - powershell: |
      Set-Location $(cookbookPath)

      kitchen test
    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.

If you do decide to use a variable group, take note of the syntax above. We must follow the styling to reference group variables and also set variables in this file. In order to add the chef executable to the path we must do it by running a logging command (referenced from stack overflow here).

My build with minimal chef logic takes about 17 minutes to run all 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:

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

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

  name: winrm
  elevated: true

  name: inspec

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

  max_retries: 15
  wait_for_retry: 180
    exit_status: :enabled

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

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