Chef Cookbook Pipeline with VSTS

In May 2017 Chef released a new Visual Studio Team Services (VSTS) extension with several tasks to help with cookbook and application development. After these were written I started to play around with VSTS with a view to creating a cookbook CI/CD pipeline, this post is about how I did this. An overview of the tasks can be found here.

Pipeline

VSTS has two distinct phases:

  1. Build
  2. Release

The Chef tasks for VSTS were designed to fit into these phases, but they do not have to be used in that phase. This is because it is the same VSTS agent that runs the build and the release, so you can use Deploy tasks in your Build phase for example.

The process shown in the diagram above is currently running on a private agent in a Docker Swarm cluster. The minimum requirement for the agent is to have ChefDK installed.

I am using VSTS Git Source control in this post, but it could be an external Git source. The problem with the latter is that VSTS would have to poll your source for changes rather than for it to be triggered on a checkin.

NB If you need to setup a VSTS account go to this page.

Build

It is assumed that the build agent has ChefDK installed.

Install Cookbook gems

As is common with cookbooks there are normally some gem dependencies that need to be installed. This is done using the “Command Line” task in VSTS and is configured to run chef exec bundle.

VSTS Task Type: Command Line

Title Value
Display Name Install Cookbook gems
Tool chef
Arguments exec bundle
Advanced
Working folder empty
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Only when all previous tasks have succeeded

Run Cookbook Style Tests

In this scenario the cookbook has a Rakefile that has a task configured to the run cookbook style tests which consist of Foodcritic.

VSTS Task Type: Command Line

Title Value
Display Name Run Cookbook style tests
Tool chef
Arguments exec rake style:chef
Advanced
Working folder empty
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Only when all previous tasks have succeeded

Run Chefstyle tests

Again the tests to check Rubocop styles is a Rakefile task.

VSTS Task Type: Command Line

Title Value
Display Name Run Chefstyle tests
Tool chef
Arguments exec rake style:ruby
Advanced
Working folder empty
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Only when all previous tasks have succeeded

Update Cookbook version number

This task is one of the ones that is from the VSTS Chef Extension. It will modify the version number in the metadata.rb file to the one specified in the task. It is recommended that the Build Number in VSTS is used for the cookbook version.

When using the build number, it must be configured correctly in the build definition. This is because the metadata.rb file must be a semantic version (semver) number. To ensure that it is a semver go to the “Options” tab of the build definition and set the following:

Title Value Example
Build number format x.y$(Rev:.r) 0.7$(Rev.r)

Where x is the desired major number and y is the desired minor number. The last number, revision will be automatically incremented by the build if none of the other numbers have changed since the last build and if they have this value will be 1.

VSTS Task Type: Update Cookbook Version Numver (Chef)

Title Value
Display Name Update cookbook version number
Cookbook version $(Build.BuildNumber)
Metadata file $(Build.SourcesDirectory)/metadata.rb
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Custom condition
Custom Condition and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))

In this case the task should only run when it is built from master, which is what the custom condition states. In actual fact on this task it does not matter if it runs or not because the version number only matters when it uploaded to the Chef server which only happens if the cookbook is published.

Copy files to artifact directory

Once all the tests have been run and the version number has been incremented, the cookbook needs to be copied to the artifact directory so that it can be published (in the next step).

VSTS Task Type: Copy Files

Title Value
Display name Copy files to artifact directory
Source Folder $(Build.SourcesDirectory)
Contents **
Target Folder $(Build.ArtifactStagingDirectory)
Advanced
Clean Target Folder Checked
Overwrite Checked
Flatten Folders Unchecked
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Custom condition
Custom Condition and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))

This task states that all files in the main build directory should be copied to the artifact staging directory. The ** means recursively copy all files..

Notice that the condition has been set to only run when the build is running from the master branch is source control.

Publish Artifact: drop

Once the cookbook has been copied to the artifact directory it needs to be published so that it can be released.

VSTS Task Type: Publish Build Artifacts

Title Value
Display Name Publish Artifact: drop
Path to Publish $(Build.ArtifactsStagingDirectory)
Artifact Name drop
Artifact Type Server
Control Options
Enabled Checked
Continue on error Unchecked
Timeout 0
Run this task Custom condition
Custom Condition and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))

As can be seen from the task it is copying the cookbook from the same location that the previous task copied the cookbook into.

Build Trigger - Continuous Integration

Now that the build tasks have been created, the trigger needs to be configured. This states how builds are started. There are a few triggers that are available including checkin builds and time of day builds. In this example we will use the checkin trigger.

From the options across the top click on Triggers and enable the Continuous Integration trigger. This will allow the source to monitored and from which branch. As I want the build to start on every checkin regardless of branch I use the wildcard * to specify the branch.

Save the definition

Release

There is only one action to run in the Release phase and that is to upload the cookbook to the Chef server. When using the tasks to upload cookbooks it needs a target Chef server. As this requires secrets a Chef service within VSTS needs to be created.

Chef Service Endpoint

Within your VSTS account select Services from the menu item. From the left hand side click on the ‘New Service Endpoint’ button and select ‘Chef Server’.

A new dialog box will be displayed that requires the following options.

Title Description
Connection Name Identifiable name for the connection
Chef Server URL Chef server that should be targetted. This must include the organization
Username (node name) Chef account with permissions to upload to the Chef server
Client Key Private key for the specified user
SSL Verification WHether to verify certificates. Set to false if using self signed certificates

NB If you wish to change the settings after creation be aware that the Client Key value will be removed as it is a secret value so it needs to be added back on each edit.

Release Definition

Now got back to Build & Release from the top navigation and then select Releases from the sub menu underneath. From the left hand menu click on the green plus icon to “Create Release Definition”.

  • Create an empty definition and click Next >
  • Select the source from which this release will be based. In this case it the build definition that has been created before. Click Create.

By default the name of the definition will be based on the template name and the date on which it was created. This can be modified by clicking on the icon to the right of the definition title.

On new definitions a single environment is created called Environment 1. In this example there is no need to create more environments, but this can be used to push code to different systems using different rules. For the single environment we will add a new task to the Run on agent group.

  • Click on Add Tasks. Select Build from the category on the left and then click Add to the right of the Upload cookbook to Chef Server (Chef) task.
  • Press the Close button after adding the task

You will see that the task is emboldened in red, this is because there are options that are mandatory that have not been specified. In this case it is the “Chef Server endpoint”. As this was create earlier in this example it will be present in the dropdown list so select the appropriate one.

NB If you did not create the endpoint before click on the Add link to the right of the text box to bring up the “Chef Server” endpoint dialog box to create one.

Title Value
Chef Server endpoint Name of endpoint to use, e.g. Turtlesystems
Cookbook Path $(System.DefaultWorkingDirectory)/swarm_deploy-CI/drop
Control Options
Enabled Checked
Continue on error Unchecked
Always run Unchecked
Timeout 0

The cookbook path needs to be modified so that it points to the CI drop folder of the build.

Trigger - Continuous Deployment

The final thing to do is to enable a trigger on the release so that it runs whenever the cookbook is published from the build definition.

Click on the Triggers item below the definition name then check the Continuous Deployment option. At this point the source that should be used must be set in the drop down for “Set trigger on artifact source”. There is no need to select a branch as the only one that will publish the cookbook will be the master branch.

Conclusion

What started out as a simple idea to create a pipeline for cookbooks in VSTS has now become my main method of building and releasing cookbooks. I have long since wanted to have a system whereby I did not have to do the push to the Chef server and source control separately manually but have it automated and it really works.

I do, however, trip myself up at times. For example when I make a quick change to the ruby code and do not bother to run the style tests locally I get a failed build message about the offense. I have setup Guard in the past, and I even have a blog post for it, but I do not always run it - shame on me.

The upshot is that I now have a very stable and reusable pipeline for getting cookbooks onto my chef server. The added bonus being that I have not cheated in that I only have to check-in to source control and the build happens automatically; meaning I do not have different versions all over the place.

To Do

  • The build number major and minor versions should be retrieved from the metedata.rb file and the revsision to come from the build.
  • Investigate whether zipping up the cookbook before copying to the artifact directory improved performance as it would be on 1 larger file as opposed to lots of little ones.
  • Run integration tests within the build. As I am using a docker build agent I want to use Kitchen Docker to run the tests, but need to get docker in docker working properly.
  • Make a build and release definition template so that I do not have to recreate these steps manually each time on new cookbook definitions.
Share Comments