Chefspec Custom Matchers for LWRP’s

I’ve been getting serious about adding proper testing to my Chef cookbooks lately. I previously mentioned a wrapper cookbook that I’m working on that uses an LWRP from a library cookbook I wrote. I haven’t put the library cookbook out in the public because it’s fairly narrowly scoped and there’s probably something else out there that does the same thing.

So my recipe looks like this:

include_recipe 'heat-nginx::default'
 
heat_nginx_vhost node[:heat_deploy][:fqdn] do
  server_name node[:heat_deploy][:fqdn]
  listen 80
  ssl false
  locations({
    '/' => {
      'root' => node[:heat_deploy][:web_root]
    }
  })
  access_log "/var/log/nginx/deploy-access.log"
  error_log "/var/log/nginx/deploy-error.log"
  action [:create, :enable]
  notifies :reload, 'service[nginx]', :delayed
end

My spec looks like this:

require_relative '../spec_helper'
 
describe 'heat_deploy::default' do
  let(:chef_run) do
    runner = ChefSpec::Runner.new(step_into: ['heat_nginx_vhost'])
    runner.node.set[:heat_deploy][:fqdn] = 'deploy.test'
    runner.node.set[:heat_deploy][:web_root] = '/srv/deploy'
    runner.converge(described_recipe)
  end
 
  let(:nginx_config) do
    'server {
    server_name deploy.test;
 
    listen 80;
 
    location / {
        root /srv/deploy;
    }
 
    access_log /var/log/nginx/deploy-access.log custom_log;
    error_log /var/log/nginx/deploy-error.log;
}
'
  end
 
  it 'includes recipe heat_nginx::default' do
    expect(chef_run).to include_recipe('heat-nginx::default')
  end
 
  context 'configuring nginx virtual host' do
    it 'creates heat_nginx_vhost[deploy.test]' do
      expect(chef_run).to create_heat_nginx_vhost('deploy.test')
    end
 
    it 'steps into heat_nginx_vhost and creates template[/etc/nginx/sites-available/deploy.test]' do
      expect(chef_run).to render_file('/etc/nginx/sites-available/deploy.test').with_content(nginx_config)
    end
 
    it 'eanbles heat_nginx_vhost[deploy.test]' do
      expect(chef_run).to enable_heat_nginx_vhost('deploy.test')
    end
 
    it 'notifies service[nginx] to reload delayed' do
      resource = chef_run.heat_nginx_vhost('deploy.test')
      expect(resource).to notify('service[nginx]').to(:reload).delayed
    end
  end
end

To make the references to the heat_nginx_vhost resource work, I created libraries/matchers.rb in the heat-nginx cookbook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# heat-nginx/libraries/matchers.rb
 
if defined?(ChefSpec)
  ChefSpec::Runner.define_runner_method(:heat_nginx_vhost)
 
  def create_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :create, resource)
  end
 
  def enable_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :enable, resource)
  end
 
  def delete_heat_nginx_vhost(resource)
    ChefSpec::Matchers::ResourceMatcher.new(:heat_nginx_vhost, :delete, resource)
  end
 
end

On line 4, I’m defining a method for the Runner so that chef_run.heat_nginx_vhost('deploy.test') is a thing, which allows me to check that it has indeed notified service[nginx] to reload.

The other methods defined enabled me to be able to say expect(chef_run).to create_heat_nginx_vhost('deploy.test').

Note that I’m running chefspec on my wrapper cookbook and it’s pulling those in from the heat-nginx cookbook. I think it’s pretty neat, but something that, unfortunately, isn’t documented terribly clearly.

Chefspec and Berkshelf Gem Dependency Duel

I was trying to get some custom matchers for an LWRP in one of my Chef library cookbooks to be recognized in the wrapper cookbook I was writing chefspec tests for. I was on chefspec 3.0.2 and found this which made me think I should upgrade, so upgrade I did to 3.1.4. Along with it, the i18n gem was also upgraded to 0.6.9 due to this issue.

So what? Well, berkshelf has a dependency on activesupport (~> 3.2.0), of which I had activesupport 3.2.13 and it required i18n 0.6.1. The first thing I saw was

$ rspec spec/recipes/default_spec.rb
/opt/local/lib/ruby1.9/gems/1.9.1/gems/chefspec-3.1.4/lib/chefspec/berkshelf.rb:4:in `rescue in ': I could not load the Berkshelf gem! You must have Berkshelf installed on your local system before you can use the `berkshelf' plugin. You can install berkshelf by running `gem install berkshelf', or add Berkshelf to your Gemfile and run the `bundle' command to install. (ChefSpec::Error::GemLoadError)

I tried just installing the lastest activesupport with gem install activesupport, which installed 4.0.2. Unfortunately, that didn’t satisfy berkshelf’s dependency. After some frustration, I listed all of the versions of activesupport available.

$ gem list activesupport --remote --all

*** REMOTE GEMS ***

activesupport (4.0.2, 4.0.1, 4.0.0, 3.2.16, 3.2.15, 3.2.14, 3.2.13, 3.2.12, 3.2.11, 3.2.10, 3.2.9, 3.2.8, 3.2.7, 3.2.6, 3.2.5, 3.2.4, 3.2.3, 3.2.2, 3.2.1, 3.2.0, 3.1.12, 3.1.11, 3.1.10, 3.1.9, 3.1.8, 3.1.7, 3.1.6, 3.1.5, 3.1.4, 3.1.3, 3.1.2, 3.1.1, 3.1.0, 3.0.20, 3.0.19, 3.0.18, 3.0.17, 3.0.16, 3.0.15, 3.0.14, 3.0.13, 3.0.12, 3.0.11, 3.0.10, 3.0.9, 3.0.8, 3.0.7, 3.0.6, 3.0.5, 3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 2.3.18, 2.3.17, 2.3.16, 2.3.15, 2.3.14, 2.3.12, 2.3.11, 2.3.10, 2.3.9, 2.3.8, 2.3.7, 2.3.6, 2.3.5, 2.3.4, 2.3.3, 2.3.2, 2.2.3, 2.2.2, 2.1.2, 2.1.1, 2.1.0, 2.0.5, 2.0.4, 2.0.2, 2.0.1, 2.0.0, 1.4.4, 1.4.3, 1.4.2, 1.4.1, 1.4.0, 1.3.1, 1.3.0, 1.2.5, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2, 1.0.1, 1.0.0)
...

Based on that, I ended up installing activesupport 3.2.16. It was happy with i18n 0.6.9 and chefspec had no problem loading berkshelf. Now, my custom matchers in my library cookbook are recognized in my wrapper cookbook’s chefspec tests and all is right with the world.

Galera with MariaDB on Debian Wheezy

I’ve been working on a new project at work getting to setup a new environment from scratch. It’s amazing.

For our database, I decided to use MariaDB with Galera. I haven’t managed traditional Master/Master replication, but I’ve always heard that it’s incredibly brittle. I didn’t want to have to worry about resyncing boxes should one of the boxes go down. That led me to Galera and that gave me the excuse I had been looking for to check out MariaDB. Continue reading

bind10 Init Script

ISC release the first release candidate for BIND 10 1.0.0 this week. I decided to give it a try since I’ve been a fan of ISC BIND and DHCPD for a long time, and BIND 10 plans to bring the two together.

There are plenty of guides on building it out there, but I was unable to find a sysvinit style init script for my Debian needs. I found an upstart example in the Ubuntu 12.04 LTS System Notes on the wiki, but I’m not going to dirty up any Debian box with upstart. Here’s what I came up with.

### BEGIN INIT INFO
# Provides:          bind10
# Required-Start:    networking
# Required-Stop:     networking
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start and stop bind10
# Description:       bind10 is a Domain Name Server (DNS) with experimental 
#        DHCPv4 and DHCPv6 server support
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
DAEMON=/usr/local/sbin/bind10
BINDCTL=/usr/local/bin/bindctl
BINDUSER=bind
PIDFILE=/var/run/bind10/bind10.pid
OPTIONS="--user=${BINDUSER} --pid-file=${PIDFILE}"

test -f /etc/default/bind10 && . /etc/default/bind10

. /lib/lsb/init-functions

mkdir -p /var/run/bind10
chown root:bind /var/run/bind10
chmod 775 /var/run/bind10

case "$1" in
    start)
        log_daemon_msg "Starting domain name service..." "bind10"
        if start-stop-daemon --start --oknodo --quiet --background --exec ${DAEMON} -- ${OPTIONS}
        then
            log_end_msg 0
        else
            log_end_msg 1
        fi
    ;;

    stop)
        log_daemon_msg "Stopping domain name service..." "bind10"
        start-stop-daemon --stop --oknodo --quiet --pidfile ${PIDFILE}
        log_end_msg 0
    ;;

    restart)
        $0 stop
        $0 start
    ;;

    status)
        ret=0
        status_of_proc -p /var/run/bind10/bind10.pid /usr/local/libexec/bind10/b10-init bind10 2>/dev/null || ret=$?
        exit $ret
    ;;

    *)
        log_action_msg "Usage: /etc/init.d/bind10 {start|stop|restart|status}"
        exit 1
    ;;
esac

exit 0

Assuming you installed bind10 under the default prefix of /usr/local, you should be able to copy and paste that into /etc/init.d/bind10, then run the following:

sudo chmod +x /etc/init.d/bind10
sudo update-rc.d bind10 defaults
sudo service bind10 start

You'll still need to follow the steps in the BIND 10 Guide to get things going, but the starting and stopping was something I had to get sorted before I got any farther into the setup process.

Cobbler, uWSGI, and Nginx

I know. The title sounds like crazy talk. Why would you want to run Cobbler with uWSGI under Nginx? Well, I inherited a box that was already setup with Nginx and had Graphite running via uWSGI, so I wasn’t going to be the one setup Nginx to proxy to Apache.

Why not just run the perfectly good WSGI services Cobbler has directly? Well, Nginx’s mod_wsgi module is old and busted. It does, however, have support for the uWSGI protocol, and the uWSGI application server runs WSGI apps similarly to how you would use mod_wsgi in Apache. You end up with a leaner setup than you would probably get from a similar Apache config.
Continue reading