Monthly Archives: November 2016

test-kitchen config hijinks part 2

In part one we looked at how to share chef zero data across multiple cookbooks by creating a central repository and using environment variables to reference it.

In part two we look at how to share the config file in its entirety. This isn’t chef specific like part 1, there is chef cookbook content (setting a variable of cookbook name), but overall its easily ignored if not pertinent to you.

In part one we put essentially pointers back to the common location for all of our chef zero bits, but you have to put those pointers in every kitchen config for all the cookbooks.

Step 1, we need a default kitchen config.

In my case most kitchen configs were nearly identical, they came out of chef code generation (as ruby templates) and the cookbook name was poked into a couple places and some default values set for security groups and IAM roles. So taking the most vanilla one I had I copied that into the root of the kitchen_shared repo as ‘.kitchen.yml.erb’ and worked from there. If your starting from scratch, just copy the stock config.

Now that we have a common kitchen config, its time to reference it from all the cookbooks. Templates can essentially include other templates. There are a couple ways to include template info, this is a way I found that works.

<% @cb_name = File.readlines("./metadata.rb").select { |line| line =~ /^name/ }[0].split(" ")[1].gsub("'", "") %>
<%= ERB.new(File.read("#{ENV['kitchen_shared']}/kitchen_yml.erb"), nil, nil, eoutvar='_kitchen_yml').result(binding) %>

This now becomes your default kitchen config for all current and future cookbooks. It does some ruby to dynamically figure out the cookbook name from your cookbooks `metadata.rb` file, sets that as a variable which is available to the new instance of a template generated by the `ERB.new(…)` call, from you guessed it, our common kitchen config.

Step 2, now move on to the common ‘.kitchen.yml.erb’.

Below are excerpts of config files, none of which are complete valid configs, they are just to help explain the concepts.

In your ‘.kitchen.yml.erb’ you may have the environment variable references from part 1 or it may be just a bone stock kitchen config which is fine. Here are some examples of what you can put inside the common config

Simple template syntax time, putting ruby inside <%= %> makes it display the result of said ruby code in the resulting templated output. Putting ruby inside <% %> means no output from that block. That pretty much sums it up for what we do here but there is no lack of docs about ruby templating out there if you want to know more.

If your using kitchen-ec2 to spin up your instances, its handy to tag the instances with some metadata:

driver:
  tags: { Name: "test_kitchen-<%= ENV['USER'] %>-<%= @cb_name %>" }

When you look in aws at your instances, you will end up with your kitchen instances being self descriptive with a Name tag like:

test_kitchen-bob-redis

This is a test-kitchen instance started by userid bob (on the host that created it) which is for a cookbook called ‘redis’.

Again in kitchen-ec2/aws, if you needed your instance to use a different ssh key depending if you were in your CI environment or on your laptop:

driver:
  <% if ENV['USER'] == 'jenkins' %>
  aws_ssh_key_id: jenkins-test-kitchen
  <% else %>
  aws_ssh_key_id: test-kitchen
  <% end %>

For a non-kitchen-ec2 example, lets change runlist depending on cb_name:

driver:
  suites:
  - name: <%= @cb_name %>-kitchen-1
    run_list:
      <% if @cb_name == 'elasticsearch' %>
      - recipe[elasticsearch::default_2x]
      <% elsif @cb_name == 'redis' %>
      - recipe[base_cb::default]
      - recipe[redis::test]
      <% else %>
      - recipe[<%= @cb_name %>::default]
      <% end %>

You can see we set the name of the instance to have the cookbook name in it via the cb_name variable, and depending on cookbook name we set runlists, with a fall-thru default.

Again this is chef stuff, but the message of this post is about the templating and sharing not chef specifically.

Finally how do we test our new-fangled scheme? Super easy. From the root of the cookbook where you would run your kitchen commands just feed our new 2 line kitchen config file to the ‘erb’ binary. Lets look at our last example of the suite section of the config from a cookbook named redis:

$ erb ./.kitchen.yml

suites:
  - name: redis-kitchen-1
    run_list:

      - recipe[redis::test]

Now if you look at that gap between ‘run_list:’ and ‘- recipe’ you see where the template engine pulled out the conditional stuff masked by the <% %>. There are ways to strip those lines out, but it wasn’t playing along and in the end we never see the result so I didn’t spend time on it, and kitchen doesn’t care.

So just iterate from here, whenever you need to deviate from the defaults, throw in a conditional in your shared config and check your work.

But also this lets you do things like change the chef omnibus version for every test-kitchen run from one place, no conditional, just shared code.

provisioner:
  require_chef_omnibus: 12.13.1

Never do the “now we go through all the cookbooks and change the ” dance again… after you set this up 🙂

test-kitchen config hijinks part 1

In part 1 we will look at DRYing up chef zero configs for test-kitchen and chef cookbooks.

A few things become apparent while making test-kitchen work in all your chef cookbooks, there is a chuck of this which is repeated and would be best to do once and reference everywhere.

Most notably the chef zero environments, data bags, nodes, etc stubs you need to service cookbooks that search and use data bags and all do all the things. This is what we will look at here.

The ‘.kitchen.yml’ config file is not only yaml, its also parsed by a Ruby template engine. This lets us do interesting things.

First, we need a common place to refer to from all the test-kitchen configs, lets for example make a new Git repo for this task called ‘kitchen_shared’.

Inside kitchen_shared repo make a simple directroy structure.

kitchen_shared/
├── data_bags
│   └── users
├── environments
└── nodes

Now place inside those directories files representing the appropriate subject. Here are some examples:

# cat kitchen_shared/data_bags/users/bob.json
{
  "id"       : "bob",
  "comment"  : "Bob Smith",
  "action"   : "create",
  "ssh_keys" : [
    "ssh-rsa thiskeyiswaaaayshortAAAAB3NzaC1yc2EAAARCV/g9OK1Oc0X04DPV0MiECguZmH/FqS13 bob@foo.local"
  ],
  "password" : "wooooooosekret!"
}

$ cat kitchen_shared/environments/database.json
{
  "name": "database",
  "default_attributes": {
  },
  "json_class": "Chef::Environment",
  "description": "kitchen database env",
   "cookbook_versions": {
  },
  "chef_type": "environment"
}
$ cat kitchen_shared/nodes/database01.json
{
  "name": "database01",
  "chef_type": "node",
  "json_class": "Chef::Node",
  "chef_environment": "database",
  "run_list": [],
  "automatic": {
    "ipaddress": "1.1.1.1",
    "hostname": "database01"
  }
}

Thses match what you would get from knife if you did a show on a node or an environment etc. They aren’t special, they are legit configs that match whats in a real server, they are just loaded into chef zero instead.

Add the appropriate things for your needs, the above are just random examples.

The .kitchen.yml docs show how to reference locations for this info, but relative to the current path and for a typically local and unique to each cookbook set of data. Ex:

provisioner:
  name: chef_zero
  nodes_path: '../../nodes'
  data_bags_path: '../../data_bags'
  environments_path: '../../environments'

So now to share this we can start using the Ruby template-fu.

Add an environment variable which contains the path to where the kitchen_shared repo is checked out to your shell so its always set through whatever means appropriate, Ex:

$ echo "export kitchen_shared=~/repos/kitchen_shared" >> ~/.bash_profile
$ . .bash_profile

Now use that to reference the paths in the kitchen_shared repo from your .kitchen.yml like this:

provisioner:
  name: chef_zero
  nodes_path: '<%= ENV['kitchen_shared'] %>/nodes'
  data_bags_path: '<%= ENV['kitchen_shared'] %>/data_bags'
  environments_path: '<%= ENV['kitchen_shared'] %>/envs'

Update your cookbook creation procedure to include this as standard and your set.

Now you have one consistent and shared set of data to populate the test-kitchen chef zero servers. Make a change there, push the repo, and everyone working on chef as well as any CI/CD processes can track that repo and use and expand and be DRY.

More to come in part 2.