TDD with Puppet
!
LOADays
2014-04-06
Antwerp, BE!
!
Garrett Honeycutt
@learnpuppet
gh@learnpuppet.com
http://learnpuppet.c...
# whoami
Where are we
going?
• Why test?	

• What makes a good module	

• Tools	

• SetupVM	

• Hack	

• Travis-ci	

• More Hacking...
LearnPuppet.com
• Training	

• 3 day Intro course	

• 2 day advanced course	

• Consulting	

• Auditing
4
Why test?
5
• Confidence to change things
Why test?
6
• Confidence to change things
• Know when you break something before deploying it
Why test?
7
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Pu...
Why test?
8
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Pu...
Why test?
9
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Pu...
Why test?
10
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of P...
Why test?
11
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of P...
Why test first?
• Puts a focus on what you want to accomplish.	

• Documents the functionality that you care about.	

• Mak...
What to test?
• Each parameter	

• Each resource	

• Ensure that failure occurs when that’s expected	

• Conditional logic...
What is actually
tested?
• Catalog is compiled with inputs such as setting values for
facts and parameters	

• We test tha...
Semver
• Explanation of semantic versioning - semver.org
15
What is 1.0.0
• README explains all parameters	

• Passes lint	

• Works with at least Ruby 1.8.7, 1.9.3, and 2.0.0	

• Va...
approach to
writing modules
• Write the README first, explaining all of your parameters and their
valid values and their de...
18
Get VM
• Install VirtualBox - https://www.virtualbox.org/
• Install Vagrant - http://www.vagrantup.com/
• git clone https:...
Testing tools
• Only if you are not using the providedVM	

$ sudo gem install -V puppet-lint rspec rspec-puppet
puppetlabs...
RVM
http://www.rvm.io/	

!
• Allows you to easily switch between multiple versions of Ruby
23
Ruby Versions
• 1.8.7	

• 1.9.3	

• 2.0.0	

• 2.1.0 (Coming with Puppet v3.5.0)
24
rspec-puppet
http://rspec-puppet.com/ 	

Thanks,Tim!
25
Puppet Module
Skeleton
• $ git clone https://github.com/ghoneycutt/puppet-
module-skeleton
• $ mkdir -p `puppet config pri...
Create a module
• generate motd module	

$ puppet module generate forgename-motd
27
Components
ghoneycutt-motd
ghoneycutt-motd/.fixtures.yml
ghoneycutt-motd/.gitignore
ghoneycutt-motd/.travis.yml
ghoneycutt...
Components
ghoneycutt-motd
ghoneycutt-motd/.fixtures.yml
ghoneycutt-motd/.gitignore
ghoneycutt-motd/.travis.yml
ghoneycutt...
.fixtures.yml
• List all of your dependencies from Modulefile
30
Gemfile
• Used by Bundler
31
.travis.yml
• Configure travis-ci.org
32
spec_helper.rb
• Code that is run before your spec tests.	

• Configures the spec testing environment.
33
Rakefile
• Validate syntax	

rake validate
!
• Validate style	

rake lint
34
Rakefile
• show all tasks	

rake -T
35
rake spec
• rake spec calls	

• rake spec_prep
• rake spec_standalone
• rake spec_clean
36
run tests
$ SPEC_OPTS="--format documentation" rake
spec_standalone
37
first test
it {
should contain_file('motd').with({
'ensure' => 'file',
'path' => '/etc/motd',
'owner' => 'root',
'group' =>...
run tests
• It fails! Now let’s fill in the code.
39
testing params
• Each attribute of the file resource should be configurable
through params.	

• Let’s test for values that s...
testing paramsdescribe 'with path specified' do
context 'as a valid path' do
let(:params) { { :path => '/usr/local/etc/mot...
testing file content
describe 'with content parameter specified' do
let(:params) { { :content => "Welcome to
puppet.learnpu...
reading tests
$ grep -ie describe -e context spec/classes/init_spec.rb
describe 'motd' do
context 'with default values for...
Exercise
Test all params
• All attributes of file resource should be configurable.	

• Write tests first.	

• Then add code t...
four digit mode
describe 'with motd_mode specified' do
context 'as a valid four digit entry' do
let(:params) { { :mode => ...
for loops
['666','66666','invalid',true].each do |mode|
context "as invalid value #{mode}" do
let(:params) { { :motd_mode ...
Exercise
Validate mode
• Validate mode with validate_re()	

https://github.com/puppetlabs/puppetlabs-stdlib/tree/3.2.0#val...
resource relationships# package
it {
should contain_package('ntp_package').with({
...
})
}
!
# file
it {
should contain_fi...
file content
# check for a specific line
!
it { should contain_file('ntp_conf').with_content(/^tinker panic 0$/) }
49
file content
# what if the whole line is optional?
# in this case we test that it is not present
!
it { should_not contain_...
Exercise
ntp module
• Use the last few slides to guide you on a module for NTP	

• Do the minimum amount of work to get th...
specify facts
context 'with default values for parameters on
EL 6' do
let(:facts) do
{ :osfamily => 'RedHat',
:lsbmajdistr...
Exercise
add OS to ntp
• Add support for another OS.This OS should have at least a
different name for the package or servi...
54
GitHub HowTo
Travis-ci.org
• Free!	

• Matrix testing	

• Integrates with GitHub	

• Tests every pull request automatically	

• Free!
55
.travis.yml---
env:
- PUPPET_VERSION=3.3.2
- PUPPET_VERSION=3.4.2
notifications:
email: false
rvm:
- 1.8.7
- 1.9.3
- 2.0.0...
57
Integrate with Travis
Test functions
# lib/puppet/parser/functions/yell.rb
module Puppet::Parser::Functions
newfunction(:yell, :type => :rvalue,...
Test functions
# spec/functions/yell_spec.rb
require 'spec_helper'
describe 'yell' do
it 'should run with correct number o...
Defines# spec/defines/mkdir_p_spec.rb
require 'spec_helper'
describe 'common::mkdir_p' do
context 'should create new direct...
Exercise
Defines
• Create a define,‘say’, that takes a param,‘msg’ or if msg is not sent,
use the title and pass that to a n...
Hashes
• https://github.com/ghoneycutt/puppet-module-vim/blob/master/
spec/classes/init_spec.rb
62
Exercise
refactor ntp
• Refactor ntp module to use a hash to specify differences between
OS’s
63
TDD with Puppet
!
LOADays
2014-04-06
Antwerp, BE!
!
Garrett Honeycutt
@learnpuppet
gh@learnpuppet.com
http://learnpuppet.c...
20140406 loa days-tdd-with_puppet_tutorial
20140406 loa days-tdd-with_puppet_tutorial
Upcoming SlideShare
Loading in …5
×

20140406 loa days-tdd-with_puppet_tutorial

1,320 views

Published on

Published in: Technology, Spiritual

20140406 loa days-tdd-with_puppet_tutorial

  1. 1. TDD with Puppet ! LOADays 2014-04-06 Antwerp, BE! ! Garrett Honeycutt @learnpuppet gh@learnpuppet.com http://learnpuppet.com
  2. 2. # whoami
  3. 3. Where are we going? • Why test? • What makes a good module • Tools • SetupVM • Hack • Travis-ci • More Hacking 3
  4. 4. LearnPuppet.com • Training • 3 day Intro course • 2 day advanced course • Consulting • Auditing 4
  5. 5. Why test? 5 • Confidence to change things
  6. 6. Why test? 6 • Confidence to change things • Know when you break something before deploying it
  7. 7. Why test? 7 • Confidence to change things • Know when you break something before deploying it • Quickly test a matrix of Puppet and Ruby versions
  8. 8. Why test? 8 • Confidence to change things • Know when you break something before deploying it • Quickly test a matrix of Puppet and Ruby versions • Test all OS’s without having to deploy it everywhere
  9. 9. Why test? 9 • Confidence to change things • Know when you break something before deploying it • Quickly test a matrix of Puppet and Ruby versions • Test all OS’s without having to deploy it everywhere • Fast feedback
  10. 10. Why test? 10 • Confidence to change things • Know when you break something before deploying it • Quickly test a matrix of Puppet and Ruby versions • Test all OS’s without having to deploy it everywhere • Prevent regression of old problems • Fast feedback
  11. 11. Why test? 11 • Confidence to change things • Know when you break something before deploying it • Quickly test a matrix of Puppet and Ruby versions • Test all OS’s without having to deploy it everywhere • Prevent regression of old problems • Fast feedback • Even in an agile world, we still have design specs.
  12. 12. Why test first? • Puts a focus on what you want to accomplish. • Documents the functionality that you care about. • Makes you think about your design. • Save time by building the minimum viable product first. • You can refactor later. 12
  13. 13. What to test? • Each parameter • Each resource • Ensure that failure occurs when that’s expected • Conditional logic 13
  14. 14. What is actually tested? • Catalog is compiled with inputs such as setting values for facts and parameters • We test that things are or are not in the catalog • Simple :) 14
  15. 15. Semver • Explanation of semantic versioning - semver.org 15
  16. 16. What is 1.0.0 • README explains all parameters • Passes lint • Works with at least Ruby 1.8.7, 1.9.3, and 2.0.0 • Validates params • Tests all params • Tests all flows in logic 16
  17. 17. approach to writing modules • Write the README first, explaining all of your parameters and their valid values and their default values. • Add all of the parameters to your manifests with default values from the README. • Write the tests from the README. • Write just enough code to get your tests to pass. • Refactor as necessary. 17
  18. 18. 18
  19. 19. Get VM • Install VirtualBox - https://www.virtualbox.org/ • Install Vagrant - http://www.vagrantup.com/ • git clone https://github.com/ghoneycutt/learnpuppet-tdd-vagrant • cd learnpuppet-tdd-vagrant • vagrant up 21
  20. 20. Testing tools • Only if you are not using the providedVM $ sudo gem install -V puppet-lint rspec rspec-puppet puppetlabs_spec_helper --no-ri --no-rdoc • https://github.com/puppetlabs/puppet-syntax-vim • https://github.com/puppetlabs/puppet-syntax-emacs 22
  21. 21. RVM http://www.rvm.io/ ! • Allows you to easily switch between multiple versions of Ruby 23
  22. 22. Ruby Versions • 1.8.7 • 1.9.3 • 2.0.0 • 2.1.0 (Coming with Puppet v3.5.0) 24
  23. 23. rspec-puppet http://rspec-puppet.com/ Thanks,Tim! 25
  24. 24. Puppet Module Skeleton • $ git clone https://github.com/ghoneycutt/puppet- module-skeleton • $ mkdir -p `puppet config print vardir`/puppet- module/skeleton/ • $ rsync -avp --exclude .git puppet-module- skeleton/ `puppet config print vardir`/puppet- module/skeleton/ 26
  25. 25. Create a module • generate motd module $ puppet module generate forgename-motd 27
  26. 26. Components ghoneycutt-motd ghoneycutt-motd/.fixtures.yml ghoneycutt-motd/.gitignore ghoneycutt-motd/.travis.yml ghoneycutt-motd/Gemfile ghoneycutt-motd/LICENSE ghoneycutt-motd/Modulefile ghoneycutt-motd/README.md ghoneycutt-motd/Rakefile ghoneycutt-motd/manifests ghoneycutt-motd/manifests/init.pp ghoneycutt-motd/spec ghoneycutt-motd/spec/classes ghoneycutt-motd/spec/classes/init_spec.rb ghoneycutt-motd/spec/fixtures ghoneycutt-motd/spec/fixtures/manifests ghoneycutt-motd/spec/fixtures/manifests/site.pp ghoneycutt-motd/spec/fixtures/modules ghoneycutt-motd/spec/spec_helper.rb 28
  27. 27. Components ghoneycutt-motd ghoneycutt-motd/.fixtures.yml ghoneycutt-motd/.gitignore ghoneycutt-motd/.travis.yml ghoneycutt-motd/Gemfile ghoneycutt-motd/LICENSE ghoneycutt-motd/Modulefile ghoneycutt-motd/README.md ghoneycutt-motd/Rakefile ghoneycutt-motd/manifests ghoneycutt-motd/manifests/init.pp ghoneycutt-motd/spec ghoneycutt-motd/spec/classes ghoneycutt-motd/spec/classes/init_spec.rb ghoneycutt-motd/spec/fixtures ghoneycutt-motd/spec/fixtures/manifests ghoneycutt-motd/spec/fixtures/manifests/site.pp ghoneycutt-motd/spec/fixtures/modules ghoneycutt-motd/spec/spec_helper.rb 29
  28. 28. .fixtures.yml • List all of your dependencies from Modulefile 30
  29. 29. Gemfile • Used by Bundler 31
  30. 30. .travis.yml • Configure travis-ci.org 32
  31. 31. spec_helper.rb • Code that is run before your spec tests. • Configures the spec testing environment. 33
  32. 32. Rakefile • Validate syntax rake validate ! • Validate style rake lint 34
  33. 33. Rakefile • show all tasks rake -T 35
  34. 34. rake spec • rake spec calls • rake spec_prep • rake spec_standalone • rake spec_clean 36
  35. 35. run tests $ SPEC_OPTS="--format documentation" rake spec_standalone 37
  36. 36. first test it { should contain_file('motd').with({ 'ensure' => 'file', 'path' => '/etc/motd', 'owner' => 'root', 'group' => 'root', 'mode' => '0644', 'content' => nil, }) } 38
  37. 37. run tests • It fails! Now let’s fill in the code. 39
  38. 38. testing params • Each attribute of the file resource should be configurable through params. • Let’s test for values that should should work as well as what should produce an error. 40
  39. 39. testing paramsdescribe 'with path specified' do context 'as a valid path' do let(:params) { { :path => '/usr/local/etc/motd' } } ! it { should contain_file('motd').with({ 'path' => '/usr/local/etc/motd', }) } end ! context 'as an invalid path' do let(:params) { { :path => 'invalid/path' } } ! it 'should fail' do expect { should contain_class('motd') }.to raise_error(Puppet::Error) end end 41
  40. 40. testing file content describe 'with content parameter specified' do let(:params) { { :content => "Welcome to puppet.learnpuppet.comnnHave Fun!n" } } ! ! it { should contain_file('motd').with_content( %{Welcome to puppet.learnpuppet.com ! Have Fun! }) } end 42
  41. 41. reading tests $ grep -ie describe -e context spec/classes/init_spec.rb describe 'motd' do context 'with default values for all parameters' do describe 'with motd_file parameter specified' do context 'as a valid path' do context 'as an invalid path' do describe 'with motd_content parameter specified' do 43
  42. 42. Exercise Test all params • All attributes of file resource should be configurable. • Write tests first. • Then add code to the module. 44
  43. 43. four digit mode describe 'with motd_mode specified' do context 'as a valid four digit entry' do let(:params) { { :mode => '0755' } } ! it { should contain_file('motd').with({ 'mode' => '0755', }) } end ! context 'as an invalid three digit entry' do let(:params) { { :mode => '755' } } ! it 'should fail' do expect { should contain_class('motd') }.to raise_error(Puppet::Error,/^motd::mode must be a four digit string./) end end end 45
  44. 44. for loops ['666','66666','invalid',true].each do |mode| context "as invalid value #{mode}" do let(:params) { { :motd_mode => mode } } ! it 'should fail' do expect { should contain_class('motd') }.to raise_error(Puppet::Error,/^motd::mode must be a four digit string./) end end end 46
  45. 45. Exercise Validate mode • Validate mode with validate_re() https://github.com/puppetlabs/puppetlabs-stdlib/tree/3.2.0#validate_re • Test your regex at http://rubular.com/ 47
  46. 46. resource relationships# package it { should contain_package('ntp_package').with({ ... }) } ! # file it { should contain_file('ntp_config').with({ ... 'require' => 'Package[ntp]', }) } ! # service it { should contain_service('ntp_service').with({ ... 'subscribe' => 'File[ntp_config]', }) } 48
  47. 47. file content # check for a specific line ! it { should contain_file('ntp_conf').with_content(/^tinker panic 0$/) } 49
  48. 48. file content # what if the whole line is optional? # in this case we test that it is not present ! it { should_not contain_file('ntp_conf').with_content(/^tinker panic 0$/) } 50
  49. 49. Exercise ntp module • Use the last few slides to guide you on a module for NTP • Do the minimum amount of work to get the tests to pass. • Copy /etc/ntp.conf to your module as a starting place 51
  50. 50. specify facts context 'with default values for parameters on EL 6' do let(:facts) do { :osfamily => 'RedHat', :lsbmajdistrelease => '6', } end end 52
  51. 51. Exercise add OS to ntp • Add support for another OS.This OS should have at least a different name for the package or service. 53
  52. 52. 54 GitHub HowTo
  53. 53. Travis-ci.org • Free! • Matrix testing • Integrates with GitHub • Tests every pull request automatically • Free! 55
  54. 54. .travis.yml--- env: - PUPPET_VERSION=3.3.2 - PUPPET_VERSION=3.4.2 notifications: email: false rvm: - 1.8.7 - 1.9.3 - 2.0.0 language: ruby before_script: "gem install --no-ri --no-rdoc bundler" script: 'bundle exec rake validate && bundle exec rake lint && SPEC_OPTS="--format documentation" bundle exec rake spec' gemfile: Gemfile 56
  55. 55. 57 Integrate with Travis
  56. 56. Test functions # lib/puppet/parser/functions/yell.rb module Puppet::Parser::Functions newfunction(:yell, :type => :rvalue, :doc => <<-EOS Takes one argument, a string to be capitalized. Returns the string in all caps. EOS ) do |args| raise(Puppet::ParseError, "yell(): Wrong number of arguments " + "given (#{args.size} for 1)") if args.size != 1 args[0].upcase end end 58
  57. 57. Test functions # spec/functions/yell_spec.rb require 'spec_helper' describe 'yell' do it 'should run with correct number of arguments (1)' do should run.with_params('hello world').and_return('HELLO WORLD') end ! it 'should fail with no arguments' do should run.with_params().and_raise_error(Puppet::ParseError) end ! it 'should fail with more than one argument (2)' do should run.with_params('too','many').and_raise_error(Puppet::ParseError) end end 59
  58. 58. Defines# spec/defines/mkdir_p_spec.rb require 'spec_helper' describe 'common::mkdir_p' do context 'should create new directory' do let(:title) { '/some/dir/structure' } ! it { should contain_exec('mkdir_p-/some/dir/structure').with({ 'command' => 'mkdir -p /some/dir/structure', 'unless' => 'test -d /some/dir/structure', }) } end ! context 'should fail with a path that is not absolute' do let(:title) { 'not/a/valid/absolute/path' } ! it do expect { should contain_exec('mkdir_p-not/a/valid/absolute/path').with({ 'command' => 'mkdir -p not/a/valid/absolute/path', 'unless' => 'test -d not/a/valid/absolute/path', }) }.to raise_error(Puppet::Error) end end end 60
  59. 59. Exercise Defines • Create a define,‘say’, that takes a param,‘msg’ or if msg is not sent, use the title and pass that to a notify{} resource. • Write tests first, then write the define. • Bonus to create your own function to run on the msg, such as making it all lower case or l33t sp34k. 61
  60. 60. Hashes • https://github.com/ghoneycutt/puppet-module-vim/blob/master/ spec/classes/init_spec.rb 62
  61. 61. Exercise refactor ntp • Refactor ntp module to use a hash to specify differences between OS’s 63
  62. 62. TDD with Puppet ! LOADays 2014-04-06 Antwerp, BE! ! Garrett Honeycutt @learnpuppet gh@learnpuppet.com http://learnpuppet.com

×