Projects

How I delivered SanctionedList.com with a solo developer setup (Part 2)

This guide series will be about setting up http://sanctionedlist.com with configuration management and TDD.

In the previous post, we set up the Chef Server on the development workstation and can set up nodes. But we need to commit to a TDD pattern as we will be setting up a lot of cookbooks! Lets look at the ways of testing Chef cookbooks. The below are the standard for testing Chef code.

Unit tests Code style tests Integration (smoke) tests
Seconds Seconds Minutes
ChefSpec (RSpec) Rubocop and Foodcritic Kitchen, InSpec

Unit tests Continuous Deployment with Chef and TDD
We will begin the wrapper cookbook. What is a wrapper cookbook? We’re going to use this cookbook to wrap around many official cookbooks with our own settings. We also don’t want to maintain the official cookbooks.

$ cd ~/sanctionedlist/chef/cookbooks
$ chef generate cookbook sanctionedlist
$ git init
$ git add .
$ git commit -m "Initial commit"

Run the first test! It should pass cleanly. The example is just a generic test from default.rb in the spec tests.

$ chef exec rspec
Finished in 14.38 seconds (files took 1.24 seconds to load)
1 example, 0 failures

We got the cookbook. Now lets start implementing PostgreSQL recipe.

$ chef generate recipe database

Lets use the Opscode official PostgreSQL cookbook. If we look at usage, we need to run recipe[postgresql::server] and this recipe will eventually call the postgresql::server_debian and install a package.

Generate the basic test to install package PostgreSQL (Based on the official cookbook)

require 'spec_helper'

describe 'sanctionedlist::database' do
let(:chef_run) { ChefSpec::ServerRunner.new.converge(described_recipe) }

it 'installs postgresql' do
expect(chef_run).to include_recipe('postgresql::server')
end

end
$ chef exec rspec
WARNING: you must specify a platform and platform_version to your ChefSpec Runner and/or Fauxhai constructor, in the future omitting these will become a hard error
FWARNING: you must specify a platform and platform_version to your ChefSpec Runner and/or Fauxhai constructor, in the future omitting these will become a hard error
.

Failures:
1) sanctionedlist::database installs a package with the default action
Failure/Error: expect(chef_run).to install_package('postgresql')

expected "package[postgresql]" with action :install to be in Chef run. Other package resources:

# ./spec/unit/recipes/database_spec.rb:14:in 'block (2 levels) in '

Finished in 0.24907 seconds (files took 1.26 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/unit/recipes/database_spec.rb:12 # sanctionedlist::database installs postgresql

There’s several errors here. With the warnings, it needs a specific platform. There’s two choices:

# Amend the chef run line to below, this needs to be added to every spec!
let(:chef_run) { ChefSpec::ServerRunner..new(platform: 'ubuntu', version: '16.04').converge(described_recipe) }
# This is keeping our spec files dry. Recommended!
require 'chefspec'
require 'chefspec/berkshelf'
RSpec.configure do |config|
# Specify the operating platform to mock Ohai data from (default: nil)
config.platform = 'ubuntu'

# Specify the operating version to mock Ohai data from (default: nil)
config.version = '16.04'
end

Lets fix the expected install issue

include_recipe 'postgresql::server'
$ chef exec rspec

1) sanctionedlist::database installs a package with the default action
Failure/Error: let(:chef_run) { ChefSpec::ServerRunner.converge(described_recipe) }

Chef::Exceptions::CookbookNotFound:
Cookbook postgresql not found. If you're loading postgresql from another cookbook, make sure you configure the dependency in your metadata
# /tmp/chefspec20170219-8525-1y7yu39file_cache_path/cookbooks/sanctionedlist/recipes/database.rb:7:in 'from_file'
# ./spec/unit/recipes/database_spec.rb:10:in 'block (2 levels) in '
# ./spec/unit/recipes/database_spec.rb:13:in 'block (2 levels) in '

To fix the missing PostgreSQL cookbook, add the dependency to the last line from the PostgreSQL Cookbook version in Github (Not PostgreSQL version)
Get the version number from site or via this command

chef exec knife cookbook site show postgresql | grep latest_version
latest_version: https://supermarket.chef.io/api/v1/cookbooks/postgresql/versions/6.0.1
[…]
depends 'postgresql', '~> 6.0.1'
$ chef exec rspec
Failures:

1) sanctionedlist::database installs a package with the default action
Failure/Error: let(:chef_run) { ChefSpec::ServerRunner.converge(described_recipe) }

ChefSpec::Error::CommandNotStubbed:
Executing a real command is disabled. Unregistered command:

command("ls /var/lib/postgresql/9.5/main/recovery.conf")

You can stub this command with:

stub_command("ls /var/lib/postgresql/9.5/main/recovery.conf").and_return(...)
# ./spec/unit/recipes/database_spec.rb:10:in 'block (2 levels) in '
# ./spec/unit/recipes/database_spec.rb:13:in 'block (2 levels) in '

Finished in 22.85 seconds (files took 1.25 seconds to load)
2 examples, 1 failure

It doesn’t say but running the above command actually installed the PostgreSQL cookbook with Berkshelf! Now the new error says we need to stub the command. Lets do it

require 'spec_helper'

describe 'sanctionedlist::database' do
let(:chef_run) { ChefSpec::ServerRunner.converge(described_recipe) }

it 'installs a package with the default action' do
stub_command("ls /var/lib/postgresql/9.5/main/recovery.conf").and_return(true)
expect(chef_run).to install_package('postgresql')
end

end
$ chef exec rspec

Failures:

1) sanctionedlist::database installs a package with the default action
Failure/Error: expect(chef_run).to install_package('postgresql')

expected "package[postgresql]" with action :install to be in Chef run. Other package resources:

apt_package[postgresql-client-9.5, libpq-dev]
apt_package[postgresql-9.5]

# ./spec/unit/recipes/database_spec.rb:14:in 'block (2 levels) in '

It’s telling us we can’t just say PostgreSQL package, it has to include the version. Amend the line to include package version

[…]
expect(chef_run).to install_package('postgresql-9.5')
[…]
chef exec rspec

Finished in 3.2 seconds (files took 1.25 seconds to load)
2 examples, 0 failures

Great work, our first test passed.
For unit tests, we want to ensure that the recipe is processed correctly. Even though when it’s run, it may not work. Also, we won’t be testing the PostgreSQL cookbook as they are likely to have tests themselves.

Note: At this time, I’m not sure how to create a unit test with the resources that create users/databases for the Database Cookbook. Online searches usually yield results about integration tests or barely test the resources.

Some good sources on unit testing:

In next post, I’ll show how integration testing is done.

2 thoughts on “How I delivered SanctionedList.com with a solo developer setup (Part 2)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s