Use rspec-puppet To Organize Your Work

Reading time ~4 minutes

Test-Driven Development for your Puppet code can feel intimidating if you’re just starting out. I had been writing Puppet for years before I answered the clarion call of rspec-puppet. If you keep at it, though, I can promise you it’s worth it. I’ve found that even the simplest manifests can often benefit from testing, if for no other reason because it can help you organize your thoughts as you’re getting started.

Recently I needed to puppetize an old internal app. Needing to braindump what I knew of the app’s existing system requirements, I decided to use test-writing as my tasklist right from the jump.

Let’s Do It

The class I would be creating would be role::xyz_app_server, so I went into our role module repo and created some files:

touch manifests/xyz_app_server.pp
touch spec/classes/xyz_app_server_spec.rb

Then, before writing any Puppet code, I started my braindump in xyz_app_server_spec.rb:

# spec/classes/xyz_app_server_spec.rb
require 'spec_helper'

describe 'role::xyz_app_server', :type => :class do
  it 'should create the app user'
  it 'should give the app user sudo access'
  it 'should install some sort of java'
  it 'should mount the /data nfs share'
  it 'should install a bunch of language packs and fonts, i guess'
  it 'should ulimit for open files 90000'
  it 'might need to do some MTA config'
  it 'should have that special imagemagick im going to have to build into an rpm'
  it 'needs epel but i dont know why yet'
end

Now when we run rake spec, we should see this:

rake spec
...
  should create the app user (PENDING: Not yet implemented)
  should give the app user sudo access (PENDING: Not yet implemented)
  should install some sort of java (PENDING: Not yet implemented)
  should mount the /data nfs share (PENDING: Not yet implemented)
  should install a bunch of language packs and fonts, i guess (PENDING: Not yet implemented)
  should ulimit for open files 90000 (PENDING: Not yet implemented)
  might need to do some MTA config (PENDING: Not yet implemented)
  should have that special imagemagick im going to have to build into an rpm (PENDING: Not yet implemented)
  needs epel but i dont know why yet (PENDING: Not yet implemented)
...
Finished in 0.00102 seconds (files took 0.5452 seconds to load)
9 examples, 0 failures, 9 pending

This is taking advantage of a feature of rspec where not creating the it block at all puts the tests into a pending state. This can be useful, as just creating an empty it block makes it pass, and that doesn’t help you know if you’ve finished writing all your tests yet.

Now you can start peeling these off one at a time:

# spec/classes/xyz_app_server_spec.rb
require 'spec_helper'

describe 'role::xyz_app_server', :type => :class do
  it 'should create the app user' do
    should contain_user('app').with({
      :uid    => '555',
      :groups => ['wheel'],
    })
  end
  it 'should give the app user sudo access'
  it 'should install some sort of java'
  it 'should mount the /data nfs share'
  it 'should install a bunch of language packs and fonts, i guess'
  it 'should ulimit for open files 90000'
  it 'might need to do some MTA config'
  it 'should have that special imagemagick im going to have to build into an rpm'
  it 'needs epel but i dont know why yet'
end
# manifests/xyz_app_server.pp
class role::xyz_app_server {
  user { 'app':
    uid    => '555',
    groups => ['wheel'],
  }
}
$ rake spec
...
  should create the app user
  should give the app user sudo access (PENDING: Not yet implemented)
  should install some sort of java (PENDING: Not yet implemented)
  should mount the /data nfs share (PENDING: Not yet implemented)
  should install a bunch of language packs and fonts, i guess (PENDING: Not yet implemented)
  should ulimit for open files 90000 (PENDING: Not yet implemented)
  might need to do some MTA config (PENDING: Not yet implemented)
  should have that special imagemagick im going to have to build into an rpm (PENDING: Not yet implemented)
  needs epel but i dont know why yet (PENDING: Not yet implemented)
...
Finished in 2.31 seconds (files took 0.5161 seconds to load)
9 examples, 0 failures, 8 pending

It passed! One down, 8 to go.

Conclusion

Part of deving all the ops is applying the same sort of best practices that software devs have been formulating for several years now. Testing is a great example of this. Don’t fight it; it really does make things easier in the long run!

Congratulations, You're Elmo

It will happen to all of us at some point. You’ve been writing puppet manifests for a few years now; you’ve moved your modules into some ...… Continue reading