tdd with puppet tutorial presented at cascadia it conference 2014-03-07

64
TDD with Puppet Cascadia IT Conference 2014-03-08 Seattle, WA Garrett Honeycutt @learnpuppet [email protected] http://learnpuppet.com

Upload: garrett-honeycutt

Post on 30-Jun-2015

471 views

Category:

Technology


2 download

DESCRIPTION

Test Driven Development (TDD) with Puppet Tutorial that was given at Cascadia IT Conference in Seattle on 2014-03-07 by Garrett Honeycutt of LearnPuppet.com. Follow me at @learnpuppet

TRANSCRIPT

TDD with Puppet !

Cascadia IT Conference 2014-03-08 Seattle, WA!

!Garrett Honeycutt

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

# whoami

Where are we going?

• Why test?

• What makes a good module

• Tools

• Setup VM

• Hack

• Travis-ci

• More Hacking

���3

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 Puppet and Ruby versions

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

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

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

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.

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

What to test?

• Each parameter

• Each resource

• Ensure that failure occurs when that’s expected

• Conditional logic

���13

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

Semver

• Explanation of semantic versioning - semver.org

���15

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

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

Prep VM• http://bit.ly/1fdJtwy

• What interface is detected? (ifconfig -a)

• Configure it for DHCP (/etc/sysconfig/network-scripts/ifcfg-ethX)

• Restart the network (service network restart)

• Modify /etc/hosts and place your IP in there

• Validate (`ping puppet` should work)

• Restart apache (service httpd restart)

• # gem update -V rspec-core rspec puppetlabs_spec_helper rspec-puppet

���21

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

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

���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 print vardir`/puppet-module/skeleton/

• $ rsync -avp --exclude .git puppet-module-skeleton/ `puppet config print vardir`/puppet-module/skeleton/

���26

Create a module

• generate motd module

$ puppet module generate forgename-motd

���27

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

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

.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' => 'root',

'mode' => '0644',

'content' => nil,

})

}

���38

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 should should work as well as what should produce an error.

���40

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

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

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

Exercise Test all params

• All attributes of file resource should be configurable.

• Write tests first.

• Then add code to the module.

���44

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

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

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

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

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_file('ntp_conf').with_content(/^tinker panic 0$/) }

���50

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

specify facts

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

���52

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

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

���57

Integrate with Travis

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

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

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

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

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 !

Cascadia IT Conference 2014-03-08 Seattle, WA!

!Garrett Honeycutt

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