Skip to content

Instantly share code, notes, and snippets.

Last active April 29, 2021 14:39
Show Gist options
  • Save lamont-granquist/40d26b6fa8178212594f to your computer and use it in GitHub Desktop.
Save lamont-granquist/40d26b6fa8178212594f to your computer and use it in GitHub Desktop.

ChefDK, Test Kitchen Driven NTP Cookbook

This gist uses TK+Berkshelf to drive creating a vagrant virts and converging a simple recipe to install and configure NTPd. This is a simple cookbook that has one recipe, one template (for ntp.conf) and one attribute file. It works on Ubuntu 12.04 and CentOS 6.4 (and derviatives) and the attribute file is used to support both distros.

This should work on Mac (where I developed it) and any chef-supported Linux that you can get Vagrant onto (Ubuntu/CentOS).

Because I use ChefDK and Test Kitchen, I can largely ignore setting up Vagrant and Berkshelf and can get right to work on writing recipe code.

NOTE: Modern (7/6/2014) Recipe Generation

  • We do not create a Vagrantfile (TK does that for us)
  • We do not use vagrant-omnibus or vagrant-berkshelf plugins (TK does that for us)
  • We use ChefDK (aka the chef command) to generate Berkshelf and .kitchen.yml files so we don't have to touch those
  • We use the ChefDK package to install everything for us (except vagrant)
  • We don't use "knife cookbook create" and instead use "chef generate cookbook"

If you're trying to mix+match these instructions with other HOWTOs on the Internet that have you editing your own Vagrantfile or installing vagrant plugins like vagrant-omnibus or vagrant-berkshelf the you'll probably have a bad time. What this HOWTO is tring to do is leverage TK and Berkshelf (and ChefDK to configure both of those for you) in order so that you can quickly move on to converging chef recipes on Ubuntu and CentOS virtual machines.

This is the correct way to start building Chef Cookbooks and leverage ChefDK and Test Kitchen as of this writing. This HOWTO should not build any bad habits, and the cookbook that results from this will only need to be extended to include tests.

Not that we're not using Test Kitchen to actually test our cookbook. We are 'only' using it to run vagrant and berkshelf, sync our cookbook(s), install and/or update the chef-client on the virtual node that we provision, and converge the node (which is quite a lot really -- test-kitchen has to do a lot of heavy lifting in order to run tests, and we can utilize that even if we do not run any tests). Test-driven cookbook design is outside of the scope of this HOWTO, but would fit nicely on top of this. This is a minimum skeleton designed to quickly get to applying configuration to an Ubuntu or CentOS virtual machine.


  1. Install Vagrant
  2. Install ChefDK
  3. Use ChefDK to generate a cookbook skeleton
  4. Add a recipe
  5. Add a template
  6. Add an attribute file
  7. Use test-kitchen to create vagrant VMs and apply the chef cookbook

Install Vagrant

FIXME: steps to install vagrant -- go to the website, download and install.

Install ChefDK

The ChefDK package compiles together a development environment with chef-client, knife, the chef command line tool, test-kitchen, berkshelf and drivers for vagrant. ChefInc has done the work to make sure that all the tools in the package are compatible so that you won't wind up needing to worry about json gem conflicts and become an expert in ruby dependency management just in order to converge a simple cookbook.

curl -L | sudo bash -s -- -P chefdk

Make sure you have at least version 0.2.0 (there was a bug in 0.1.0 that affected this workflow):

chef --version
Chef Development Kit Version: 0.2.0

Create a Repo

I'm going to assume a ~/chef-repo/{cookbooks,data_bags,roles,environments} structure, and we are going to follow a one-git-repo-per-cookbook model:

% mkdir -p ~/chef-repo/cookbooks/ntp
% cd ~/chef-repo/cookbooks/ntp
% git init .
% touch
% git add .
% git commit -a -m 'first commit'

Create Cookbook Skeleton

% cd ~/chef-repo/cookbooks/ntp
% chef generate cookbook .

This magically picks up the name of our cookbook based on the name of the directory we are in. It also creates:

  • metadata.rb
  • chefignore
  • Berksfile
  • .kitchen.yml
  • recipes/default.rb

It is a good idea now to make a commit so that you can go back to an unmodified skeleton if you want to:

cd ~/chef-repo/cookbooks/ntp
git add .
git commit -m 'Generated cookbook skeleton'

Add Your Recipe Code

edit recipes/default.rb and include some resources:

package "ntp" do
  action :install

template "/etc/ntp.conf" do
  source "ntp.conf.erb"
  variables( :ntp_server => "" )
  notifies :restart, "service[ntp_service]"

service "ntp_service" do
  service_name node[:ntp][:service]
  action [:enable, :start]

Add Your Template

Since we included a template resource, we need to create the content for the template. ChefDK contains a really simple generator for the 'scaffolding':

% cd ~/chef-repo/cookbooks/ntp
% chef generate template ntp.conf

All that does is create the file templates/ntp.conf.erb. You'll need to edit that file and include the contents that you want for ntp.conf.erb anyway (so you can skip the 'chef generate template' step entirely);

restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
restrict -6 ::1
server <%= @ntp_server %>
server     # local clock
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys

Add Your Attribute File

On RHEL based systems we need to start the "ntp" service and use the "/etc/init.d/ntp" init script. On Debian/Ubuntu systems we need to start the "ntpd" service and use the "/etc/init.d/ntpd" init script. To deal with that difference correctly we included the attribute node[:ntp][:service] in the recipe and we need to set it correctly (based on the platform_family) in an attribute file.

Again you can run ChefDK to generate the attribute file or just create it directly:

% cd ~/chef-repo/cookbooks/ntp
% chef generate attribute default

That should create attributes/default.rb which you need to edit to include the content:

default["ntp"]["service"] =
  case node["platform_family"]
  when "rhel", "fedora"
  when "debian"

Commit Our Work To Git

Now is a good point to commit and make a checkpoint since things should be working.

git add .
git commit -m 'Added Cookbook Code'

Run Kitchen List

To see what virts ChefDK sets up to build initially we can do a kitchen list (configured in the .kitchen.yml file that ChefDK generated for us):

% kitchen list
Instance             Driver   Provisioner  Verifier  Transport  Last Action
default-ubuntu-1604  Vagrant  ChefZero     Inspec    Ssh        <Not Created>
default-centos-72    Vagrant  ChefZero     Inspec    Ssh        <Not Created>

Run a Test Kitchen Converge

The TK "converge" command will run chef-client converge and then leave the instance around to be logged into. Use "kitchen test" if you'd like to destroy the instance after you get a successful converge and not leave test VMs lying around.

% cd ~/chef-repo/cookbooks/ntp
% kitchen converge default-ubuntu-1604

Login to Instance

Since you used kitchen converge previously you can login to the instance:

% cd ~/chef-repo/cookbooks/ntp
% kitchen login default-ubuntu-1604

Test CentOS

Similarly we can verify that we do the right thing for CentOS:

% cd ~/chef-repo/cookbooks/ntp
% kitchen converge default-centos-72
% kitchen login default-centos-72

Test Kitchen Matching

The instance argument for test kitchen should be read as a regular expression bounded by wildcards. So if the string you send it matches an instance it will try to converge or login to it. You can only login to a single box, so you need to give it enough to uniquely identify an instance when you're doing a login, but multiple servers can be converged with a single command line:

% kitchen converge             # converge all the instances in .kitchen.yml
% kitchen converge ubuntu      # converge all the ubuntu servers in .kitchen.yml (would match default-ubuntu-1204 and default-ubuntu-1404)
% kitchen converge centos      # converge all the centos servers in .kitchen.yml
% kitchen converge default     # converge all the default test suits in .kitchen.yml (test suites are more advanced TK use)
% kitchen login ubuntu-1604    # login to the ubuntu-1604 instance (if 'default' is the only suite you have defined)

Benefits As Starting Workflow

(This is aimed more at explaining to experienced chef users why to teach Chef in this way)

There's no explicit interaction with a Chef Server involved in this example. Under the covers, your kitchen config will be firing up a chef-zero server and using it to converge the node, but this detail is hidden and we do not need to install a chef-server first before doing work. There's no additional setup of EC2 or Digital Ocean keys as well. We also do not scribble over the User's desktop configuration. We install two utilities and get right to converging recipes as fast as possible on a virt. There is probably less initial overhead compared to setting up chef-solo and explaining dna.json and solo.rb files. The focus is as much as possible on writing Chef recipes, but done with correct tooling first.

There's no going down the wrong path. This outline conforms to the Law of Primacy -- that you will fall back on what you learn first. So we do not use chef-solo because fundamentally that approach leads to dead ends, and does not teach that chef is best used with a server. We also start the user down the path of using git-repos-per-cookbook. We use tools like test-kitchen which can grow into fully TDD design later, and we converge CentOS and Ubuntu virts on a workstation that might be MacOS (or soon Windows) because later that is the best workflow to solve that problem (rather than, say, firing up an EC2 cloud instance and logging in and using chef-zero every time you want to test a cookbook).

What Magic Just Happened

Notice that you:

  • Didn't have to configure or touch Berkshelf
  • Didn't have to find vagrant boxes
  • Didn't have to deal with installing chef on your vagrant box
  • Didn't have to deal with old installed versions of chef on your vagrant box
  • Didn't have to use hosted chef
  • Didn't have to setup a chef server (either open source or private enterprise chef)
  • Didn't have to touch a Vagrantfile
  • Didn't have to touch the Test-Kitchen config (bit of a lie until ChefDK 0.1.0 comes out)
  • Didn't have to install any vagrant plugins
  • Didn't have to fight with installing ruby tools and gem dependencies

Most of the work in this HOWTO was on editing your Chef cookbook and using test kitchen to converge virts using your cookbook.

Copy link

ghost commented Jul 7, 2014

Thank you, Lamont! I can't wait to try this out! :)

Copy link

I disagree with your statement "Not that we're not using Test Kitchen to actually test our cookbook." As you mentioned, Test Kitchen will exercise the cookbook and will fail if the chef-client (or chef-solo) run fails.

Ask anyone who's developed a cookbook if they think a successful chef-client or chef-solo run over that cookbook without errors is a good test. I'm pretty sure most will agree that it is.

Copy link

Yeah, I think I'm using "test" in a more software development TDD driven sense of the word. I can 'test' my code changes to chef-client by writing a recipe and converging it and seeing how Chef behaves (and use that to validate end-to-end behavior reasonably often), but that is the opposite of TDD. That is the kind of 'testing' i used to do all the time before I became a software dev. We are not writing any functional or unit tests here, and are not using software to validate the end results (other than the exit status of the Chef run and test-kitchen) we are doing the thing and watching the thing do its thing.

And I think that distinction is important because there's a gulf of understanding between "watching it work" testing and "unit testing".

Copy link

I also think that a lot of system-administration-minded users have anxiety over 'testing' in the sense that software developers use the word. So 'unit testing', TDD, etc are scary concepts. To the extent that those users associate test-kitchen with TDD infrastructure they'll likely get anxiety over using the tool. But what we're doing here is not TDD infrastructure or unit testing or anything that requires a software development background at all. We're spinning up a virt, sync'ing some cookbooks to it, and converging chef against it -- which is a very systems-oriented kind of end-to-end test with no (percieved) TDD black magic going on. So this is 'just' the thing that users are used to seeing -- a node getting converged. And I think its fairly important to call out that test-kitchen can be used this way. I think it can be used to dispel some of the perceived voodoo involved in test-kitchen. Because you can compose what test-kitchen does by looking at the automation it does around spinning up virts and converging the node and then when that has been digested you can layer the spec testing on top of that.

Copy link

pmocek commented Jul 31, 2014

It looks like you need to either s/git touch (likely, since you're following with git add) or reference something like git-cp.

Copy link

This would be a great Gist to share with @tpetchel as he develops scenario-based articles on learnchef.

Copy link

tknerr commented Jan 29, 2015

I like this! 👍 :-)

Copy link

@pmocek finally fixed =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment