64
TDD with Puppet LOADays 2014-04-06 Antwerp, BE Garrett Honeycutt @learnpuppet [email protected] http://learnpuppet.com

20140406 loa days-tdd-with_puppet_tutorial

Embed Size (px)

Citation preview

Page 1: 20140406 loa days-tdd-with_puppet_tutorial

TDD with Puppet !

LOADays 2014-04-06 Antwerp, BE!

!Garrett Honeycutt

@learnpuppet [email protected] http://learnpuppet.com

Page 2: 20140406 loa days-tdd-with_puppet_tutorial

# whoami

Page 3: 20140406 loa days-tdd-with_puppet_tutorial

Where are we going?

• Why test?

• What makes a good module

• Tools

• Setup VM

• Hack

• Travis-ci

• More Hacking

3

Page 4: 20140406 loa days-tdd-with_puppet_tutorial

LearnPuppet.com

• Training

• 3 day Intro course

• 2 day advanced course

• Consulting

• Auditing

4

Page 5: 20140406 loa days-tdd-with_puppet_tutorial

Why test?

5

• Confidence to change things

Page 6: 20140406 loa days-tdd-with_puppet_tutorial

Why test?

6

• Confidence to change things

• Know when you break something before deploying it

Page 7: 20140406 loa days-tdd-with_puppet_tutorial

Why test?

7

• Confidence to change things

• Know when you break something before deploying it

• Quickly test a matrix of Puppet and Ruby versions

Page 8: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 9: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 10: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 11: 20140406 loa days-tdd-with_puppet_tutorial

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.

Page 12: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 13: 20140406 loa days-tdd-with_puppet_tutorial

What to test?

• Each parameter

• Each resource

• Ensure that failure occurs when that’s expected

• Conditional logic

13

Page 14: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 15: 20140406 loa days-tdd-with_puppet_tutorial

Semver

• Explanation of semantic versioning - semver.org

15

Page 16: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 17: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 18: 20140406 loa days-tdd-with_puppet_tutorial

18

Page 19: 20140406 loa days-tdd-with_puppet_tutorial
Page 20: 20140406 loa days-tdd-with_puppet_tutorial
Page 21: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 22: 20140406 loa days-tdd-with_puppet_tutorial

Testing tools

• Only if you are not using the provided VM

$ 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

Page 23: 20140406 loa days-tdd-with_puppet_tutorial

RVM

http://www.rvm.io/

!

• Allows you to easily switch between multiple versions of Ruby

23

Page 24: 20140406 loa days-tdd-with_puppet_tutorial

Ruby Versions

• 1.8.7

• 1.9.3

• 2.0.0

• 2.1.0 (Coming with Puppet v3.5.0)

24

Page 25: 20140406 loa days-tdd-with_puppet_tutorial

rspec-puppet

http://rspec-puppet.com/

Thanks, Tim!

25

Page 26: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 27: 20140406 loa days-tdd-with_puppet_tutorial

Create a module

• generate motd module

$ puppet module generate forgename-motd

27

Page 28: 20140406 loa days-tdd-with_puppet_tutorial

Componentsghoneycutt-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

Page 29: 20140406 loa days-tdd-with_puppet_tutorial

Componentsghoneycutt-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

Page 30: 20140406 loa days-tdd-with_puppet_tutorial

.fixtures.yml

• List all of your dependencies from Modulefile

30

Page 31: 20140406 loa days-tdd-with_puppet_tutorial

Gemfile

• Used by Bundler

31

Page 32: 20140406 loa days-tdd-with_puppet_tutorial

.travis.yml

• Configure travis-ci.org

32

Page 33: 20140406 loa days-tdd-with_puppet_tutorial

spec_helper.rb

• Code that is run before your spec tests.

• Configures the spec testing environment.

33

Page 34: 20140406 loa days-tdd-with_puppet_tutorial

Rakefile

• Validate syntax

rake validate

!

• Validate style

rake lint

34

Page 35: 20140406 loa days-tdd-with_puppet_tutorial

Rakefile

• show all tasks

rake -T

35

Page 36: 20140406 loa days-tdd-with_puppet_tutorial

rake spec

• rake spec calls

• rake spec_prep

• rake spec_standalone

• rake spec_clean

36

Page 37: 20140406 loa days-tdd-with_puppet_tutorial

run tests

$ SPEC_OPTS="--format documentation" rake spec_standalone

37

Page 38: 20140406 loa days-tdd-with_puppet_tutorial

first test it {

should contain_file('motd').with({

'ensure' => 'file',

'path' => '/etc/motd',

'owner' => 'root',

'group' => 'root',

'mode' => '0644',

'content' => nil,

})

}

38

Page 39: 20140406 loa days-tdd-with_puppet_tutorial

run tests

• It fails! Now let’s fill in the code.

39

Page 40: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 41: 20140406 loa days-tdd-with_puppet_tutorial

testing params describe '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

Page 42: 20140406 loa days-tdd-with_puppet_tutorial

testing file content describe 'with content parameter specified' do let(:params) { { :content => "Welcome to puppet.learnpuppet.com\n\nHave Fun!\n" } } !! it { should contain_file('motd').with_content( %{Welcome to puppet.learnpuppet.com !Have Fun! }) } end

42

Page 43: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 44: 20140406 loa days-tdd-with_puppet_tutorial

Exercise Test all params

• All attributes of file resource should be configurable.

• Write tests first.

• Then add code to the module.

44

Page 45: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 46: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 47: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 48: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 49: 20140406 loa days-tdd-with_puppet_tutorial

file content

# check for a specific line !it { should contain_file('ntp_conf').with_content(/^tinker panic 0$/) }

49

Page 50: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 51: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 52: 20140406 loa days-tdd-with_puppet_tutorial

specify facts

context 'with default values for parameters on EL 6' do let(:facts) do { :osfamily => 'RedHat', :lsbmajdistrelease => '6', } end end

52

Page 53: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 54: 20140406 loa days-tdd-with_puppet_tutorial

54

GitHub HowTo

Page 55: 20140406 loa days-tdd-with_puppet_tutorial

Travis-ci.org

• Free!

• Matrix testing

• Integrates with GitHub

• Tests every pull request automatically

• Free!

55

Page 56: 20140406 loa days-tdd-with_puppet_tutorial

.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

Page 57: 20140406 loa days-tdd-with_puppet_tutorial

57

Integrate with Travis

Page 58: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 59: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 60: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 61: 20140406 loa days-tdd-with_puppet_tutorial

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

Page 62: 20140406 loa days-tdd-with_puppet_tutorial

Hashes

• https://github.com/ghoneycutt/puppet-module-vim/blob/master/spec/classes/init_spec.rb

62

Page 63: 20140406 loa days-tdd-with_puppet_tutorial

Exercise refactor ntp

• Refactor ntp module to use a hash to specify differences between OS’s

63

Page 64: 20140406 loa days-tdd-with_puppet_tutorial

TDD with Puppet !

LOADays 2014-04-06 Antwerp, BE!

!Garrett Honeycutt

@learnpuppet [email protected] http://learnpuppet.com