How to run Chef Test Kitchen in Azure DevOps builds using Microsoft hosted (free) agents.
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:
# 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 `
"[$(azure_subscription)]
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:
---
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'