Driven Development Don’t Repeat Yourself (DRY) Boy Scout Rule Collective Code Ownership Test Driven Development (TDD) Clean Code Broken Window Software Entropy Unit Testing Separation Of Concerns (SOC) Egoless Programming Continuous Integration Data Separation Code Control Keep It Simple Stupid (KISS) Code Review You Ain’t Gonna Need It (YAGNI) Legacy Code Design Patterns Style Guide Lint Test Harness Dev Practices bingo card
if… “I can understand how it works” Example: use Puppet standard PFS (Package, File, Service) resources instead of 400 chars oneliner wrapped in an exec
git push In our context, it’s nice if… “you manage all your code in git” “you use r10k to deploy code” “you have a branching / env strategy” Code Control
send its facts 2. Catalog Puppet master uses facts, code and hiera data to compile a catalog to specify what is expected on the agent 4. Report The nodes sends back a report containing the catalog execution result 3. Apply changes Agent takes action to conform to the catalog Puppet execution lifecycle Puppet code
to compile a catalog to specify what is expected on the agent RSpec-Puppet interactions Puppet code Facts Puppet Catalog RSpec-Puppet Set inputs Verify assertions in catalog
'base_packages' do context 'default params' do let(:params) { {} } it { is_expected.to compile } it { is_expected.to contain_package('vim') } it { is_expected.to contain_package('curl') } end end base_packages default params should contain compile into a catalogue without dependency cycles should contain Package[vim] (FAILED - 1) should contain Package[curl] (FAILED - 2) Finished in 0.62943 seconds (files took 0.42828 seconds to load) 3 examples, 2 failures RSpec-Puppet Tests Puppet code RSpec-Puppet run output
contain compile into a catalogue without dependency cycles should contain Package[vim] should contain Package[curl] Finished in 0.82657 seconds (files took 0.41641 seconds to load) 3 examples, 0 failures RSpec-Puppet Tests Puppet code RSpec-Puppet run output describe 'base_packages' do context 'default params' do let(:params) { {} } it { is_expected.to compile } it { is_expected.to contain_package('vim') } it { is_expected.to contain_package('curl') } end end class base_packages () { package { ['vim', 'curl'] : ensure => present, } }
} it { is_expected.to compile } it { is_expected.to contain_package('vim') } it { is_expected.to contain_package('curl') } end context 'override params' do let(:params) { { 'packages' => ['wget'], } } it { is_expected.to compile } it { is_expected.to contain_package('wget') } it { is_expected.to_not contain_package('vim') } it { is_expected.to_not contain_package('curl') } end end TDD: add your new feature tests base_packages default params should contain compile into a catalogue without dependency cycles should contain Package[vim] should contain Package[curl] override params should contain compile into a catalogue without dependency cycles (FAILED - 1) should contain Package[wget] (FAILED - 2) should not contain Package[vim] (FAILED - 3) should not contain Package[curl] (FAILED - 4) Finished in 0.91724 seconds (files took 0.39541 seconds to load) 7 examples, 4 failures Puppet code RSpec-Puppet Tests RSpec-Puppet run output class base_packages () { package { ['vim', 'curl'] : ensure => present, } }
} it { is_expected.to compile } it { is_expected.to contain_package('vim') } it { is_expected.to contain_package('curl') } end context 'override params' do let(:params) { { 'packages' => ['wget'], } } it { is_expected.to compile } it { is_expected.to contain_package('wget') } it { is_expected.to_not contain_package('vim') } it { is_expected.to_not contain_package('curl') } end end TDD: make your tests go green (again) Puppet code RSpec-Puppet Tests RSpec-Puppet run output class base_packages ( $packages = ['vim', 'curl'], ) { package { $packages : ensure => present, } }
} it { is_expected.to compile } it { is_expected.to contain_package('vim') } it { is_expected.to contain_package('curl') } end context 'override params' do let(:params) { { 'packages' => ['wget'], } } it { is_expected.to compile } it { is_expected.to contain_package('wget') } it { is_expected.to_not contain_package('vim') } it { is_expected.to_not contain_package('curl') } end end TDD: make your tests go green (again) Puppet code RSpec-Puppet Tests RSpec-Puppet run output class base_packages ( $packages = ['vim', 'curl'], ) { package { $packages : ensure => present, } } base_packages default params should contain compile into a catalogue without dependency cycles should contain Package[vim] should contain Package[curl] override params should contain compile into a catalogue without dependency cycles should contain Package[wget] should not contain Package[vim] should not contain Package[curl] Finished in 0.93594 seconds (files took 0.46291 seconds to load) 7 examples, 0 failures
your time Part-level modules 20% of your time ntp ssh apache java tomcat role::myapp uses profile::hardening profile::baseline profile::prodified_tomcat yum sudo
1. Take a fresh vm 2. Launch Puppet code 3. Verify the service is up 4. Trash the vm You can do it … - manually - ad-hoc scripting - by leveraging other people work Let’s see test-kitchen & beaker!
ec2 puppet serverspec 1. Take a fresh vm 2. Launch Puppet code 3. Verify the service is up 4. Trash the vm 104 matches for “kitchen” in rubygems kitchen-docker, kitchen-gce, kitchen-pester ...
up High-level modules Nope Part-level modules YES ! $ netstat -lntp | grep -q :80 $ service httpd status | grep -q started describe port(80) do it { should be_listening } end describe service('httpd') do it { should be_running } end Bash (could be in bats) Serverspec
modules YES ! Part-level modules Nope $ test -f /opt/app/myapp.war $ curl -sf localhost | grep myapp describe file('/opt/app/myapp.war') do it { should exist } end describe command('curl -sf localhost') do its(:exit_status) { should eq 0 } its(:stdout) { should match /myapp/ } end Bash (could be in bats) Serverspec
can cover idempotency & upgrade paths ◉ You can implement basic multi-hosts topology But it comes at a price: ◉ Beaker comes with its own complexity ◉ Bootstrap is not cheap Make up your mind to see if it’s worth it in your context Beaker for the win?
probably not apply for production rollouts ◉ Testing anything that needs to be in the middle of a tightly coupled IS > Hunting bugs is easier when we isolate! > Feedback loop is a HUGE pain point ◉ “Do it yourself” scripts to the rescue! What’s left?
Tests Run RSpec Tests Dev Commit Module Change Trigger Job Run Static Analysis Run RSpec Tests Run Test- Kitchen Tests Tag Module X.Y on Continous Integration Server on Developer’s workstation Module ready Continue with Puppet High-level Repository Development Workflow to deploy module Puppet Part-level module Development Workflow
high-level tests Deploy change DEV Branch Manual validation? Promote code to Production PROD Branch on CI Module ready Change deployed Change Module tag Change Role / Profile or Hiera data Deploy change PROD Branch Puppet High-level module Development Workflow same as previous... ... Dev
Embrace TDD Craft your (dev) tools Start tests on day 1, feed them on a daily basis Take advantage of Puppet rich testing ecosystem WIN WIN WIN WIN WIN WIN