"Building Reusable Puppet Modules" by Jon Topper at Puppet Camp London 2013. Find a Puppet Camp near you: https://puppetlabs.com/community/puppet-camp/
2. Jon who?
★ 12 years professional Linux sysadmin experience
★ Principal Consultant, The Scale Factory
★ 4.5 years using Puppet (since 0.24)
★ 4 full-time staff + subcontractors
★ >20 customer infrastructures deployed
Tuesday, 2 April 13
3. Our Problem Domain
★ Multiple customers
★ Multiple Linux distributions
★ Multiple application environments
★ Adding new features over time
★ Requirement to support old deployments
★ Multiple individuals contributing
Tuesday, 2 April 13
4. Your Problem Domain?
★ Multiple teams in a large organisation
★ Forge contributions
Tuesday, 2 April 13
5. Our Approach
role_web role_db role_lb
defaults
yum firewall apache mysql varnish
firewall stdlib concat
Tuesday, 2 April 13
6. Reusable?
★ Use the same modules
★ In different combinations
★ To solve different problems
★ Without modification
Tuesday, 2 April 13
7. Single Responsibility
Install MySQL
Configure MySQL
Start MySQL ✔
Install MySQL
Configure MySQL
Install Apache
Install mod_php
Install Wordpress
Configure MySQL grants
Configure Wordpress ✘
Tuesday, 2 April 13
8. Low Coupling
★ Changes in highly coupled modules necessitate
changes in others
★ Limit coupling where possible
★ Examples of coupling:
★ Using variables from another class
★ Requiring specific resources from another class
★ Any other use of non-public interfaces
Tuesday, 2 April 13
9. # sf_apache/manifests/init.pp
class sf_apache::service {
service { “httpd”: ensure => running }
}
# sf_puppetmaster/manifests/init.pp
class sf_puppetmaster::config {
file { “/etc/httpd/conf.d/puppetmaster.conf”:
notify => Class[“sf_apache::service”],
}
}
✔
file { “/etc/httpd/conf.d/puppetmaster.conf”:
notify => Service[“httpd”],
}
✘
Tuesday, 2 April 13
10. Tell, Don’t Ask
class apache::config( $port = 80 ) {
...
} ✔
class apache::config {
case $hostname {
/devel/: { $port = 8080 }
default: { $port = 80 }
}
...
}
✘
Tuesday, 2 April 13
12. Parameters
★ Check user parameters for syntax
★ Choose sensible defaults
★ Document the module’s public interface
★ Write tests for that interface
Tuesday, 2 April 13
13. Open/Closed Principle
★ Modules should be open for extension
★ But closed for modification
★ Ensures backward compatibility
★ Tests prove that compatibility
Tuesday, 2 April 13
18. Smoke Testing
★ puppet-lint to enforce house style
★ puppet parser validate to check syntax
★ Evaluate ERB templates to check syntax
Tuesday, 2 April 13
19. Unit Testing: rspec-puppet
★ Rapid feedback testing
★ Can easily target multiple ruby/puppet version
combinations
★ Test your module’s behaviour - not Puppet’s
★ Aim for Condition Coverage across behaviour that
varies on parameter
★ Doesn’t guarantee that your module will work as
planned in the real world!
Tuesday, 2 April 13
20. Condition Coverage
class sf_firewall::params {
case $osfamily {
'RedHat': {
$persist_command = "/sbin/iptables-save > /etc/sysconfig/iptables"
}
'Debian': {
$persist_command = "/sbin/iptables-save > /etc/iptables/rules.v4"
}
}
Tuesday, 2 April 13
21. Condition Coverage
describe ‘sf_firewall’, :type => ‘class’ do
def self.test_standard_behaviour
...
end
context "on a RedHat OS" do
let :facts do {
:osfamily => 'RedHat'
}
end
it { should contain_exec('persist-firewall').with_command(
"/sbin/iptables-save > /etc/sysconfig/iptables"
)}
test_standard_behaviour
end
Tuesday, 2 April 13
22. Integration Testing: Cumberbatch
★ Based on Cucumber
★ Uses Vagrant to start virtual machines
★ Runs puppet manifests for real
★ Tests outcomes
★ Rolls back VM state and tries different manifests
★ Does it all again for a different OS version
★ Very slow!
Tuesday, 2 April 13
23. Integration Testing: Cumberbatch
Scenario: Basic install of Apache
Given there is a running VM called "server"
When I apply a puppet manifest containing:
"""
include cucumber_defaults
class { sf_apache:
'Port' => '80',
'Children' => '10'
}
"""
Then a second manifest application should do nothing
And there should be 11 processes called “httpd” running
And the Apache module “core_module” should be loaded
And the Apache module “rpaf_module” should be loaded
And a process called “httpd” should be listening on TCP
port 80
And a GET request to http://localhost/server-status/
should return an http status of 200
Tuesday, 2 April 13