WRITING & SHARING
GREAT MODULES
Cody Herriges, Puppet Labs
Twitter: @odyrf | Freenode: odyi
WHO IS THIS GUY?
Destiny was written in the 6th grade.
Professional Services @puppetlabs, ~2 years
Operations Engineer @puppetlabs, ~1 year
Integration Engineer @puppetlabs, ~1 month
THINGS I DO
Old public modules (The PS era)
puppetlabs-java_ks: java keystore management
puppetlabs-corosync: build pacemaker clusters
Less old public modules (The Operations era)
puppetlabs-apacheds: stands up Apache Directory
Server
puppetlabs-ldap_entry: manipulates an ldap server
puppetlabs-stunnel: set up SSL tunnels
Lately
puppet-openstack: build your openstack
LET’S TALK ABOUT
MODULES
BEST PRACTICES
Traditional development: 50+ years to mature
Modern config. mgmt: 15 years, max
Best practices are evolving
SO WHERE DO WE START?
Separate your logic and configuration
Know your interface
Use semantic versioning
Reuse everything
Leverage the community
DATA/LOGIC
SEPARATION
SEPARATE LOGIC FROM DATA
Logic != Data
Example: configuring a service on different platforms
Shouldn’t have to update every file in a module
PACKAGE/FILE/SERVICE
Humble beginnings for many modules
class mysql::server {
package { 'mysql-server':
ensure => present,
}
file { '/etc/mysql/my.cnf':
ensure => present,
content => template('mysql/server/my.cnf.erb'),
require => Package['mysql-server'],
}
service { 'mysqld':
ensure => running,
enable => true,
subscribe => File['/etc/mysql/my.conf'],
}
}
PROBLEMS WITH PACKAGE/FILE/SERVICE
Nothing inherently wrong
Overly simple
Very static
Generally requires overhaul for different platforms
RUDIMENTARY DATA/LOGIC SEPARATION
class mysql::server {
include mysql::params
package { 'mysql-server':
name => $mysql::params::server_package,
ensure => present,
}
file { 'my.cnf':
path => $mysql::params::server_config,
ensure => present,
source => 'puppet:///modules/mysql/my.cnf',
require => Package['mysql-server'],
}
service { 'mysql-server':
name => $mysql::params::server_service,
ensure => running,
HARDCODING TUNABLE VALUES
Want to prevent people from reusing your modules?
Hardcode everything!
BAD PARAMS USE
Params class = good
Why is this bad?
Site specific defaults?
INSECURE DEFAULTS‽
class mysql::params {
$allow_hosts = '172.16.0.1/24'
$root_password = 'changeme'
$root_user = 'root'
}
GOOD PARAMS USE
Force user to supply data
Fail fast
class mysql::params(
$allow_hosts, # Force the module user to fill this out
$root_password, # Fail fast rather than potentially use bad data
$root_user = 'root' # Sane default
) {
...
}
DATA BINDING
DATA BINDING
New in Puppet 3: data binding
Provides a method for configuring modules
USING DATA BINDING
Define data in a data store
file
database
web service
Automatically load data in the relevant manifests
It is hierarchical
USING DATA BINDING
class mysql::params(
$allow_hosts,
$database_password,
$database_user = 'root'
) {
...
}
# $datadir/common.yaml
---
mysql::params::allow_hosts: '10.126.8.0/24'
# $datadir/qa.mysite.local.yaml
---
mysql::params::allow_hosts: '10.134.8.0/24'
USING MODULES AS
INTERFACES
MODULES AS INTERFACES
Puppet simplifies management of services
Defines how people interact with that service
Puppet modules define an interface for that service
Creates two challenges
What options are supported?
What options should users configure?
BE OPINIONATED
Cannot make every option tunable
You’ll go insane
Require mandatory data
Add parameters for frequently changed data
Offer an ‘override’ option
BUT OTHER OPINIONS ARE NICE TOO
You can’t always support every option
Allow people to directly insert their own configuration
OVERRIDE EXAMPLE: PARTIAL TEMPLATES
Module provides template fragments
User assembles these into a full config
CREATING A PARTIAL TEMPLATE
<%# nginx/templates/vhost/_listen.conf.erb %>
<%# Configuration fragment for listening on IPv4 and IPv6 with SSL %>
<% unless @sslonly -%>
listen <%= port %>;
<% if scope.lookupvar('::ipaddress6') -%>
listen [::]:<%= port %>;
<% end -%>
<% end -%>
<% if ssl -%>
listen <%= ssl_port %> ssl;
<% if scope.lookupvar('::ipaddress6') -%>
listen [::]:<%= ssl_port %> ssl;
<% end -%>
<% end -%>
USING PARTIAL TEMPLATES
Example: my_nginx_app/templates/nginx-
vhost.conf.erb
server {
<%= scope.function_template(['nginx/vhost/_listen.conf.erb']) %>
root /usr/share/empty;
location / {
proxy_pass <%= @proto %>://workers;
proxy_redirect off;
proxy_next_upstream error timeout invalid_header http_500 http_503;
proxy_connect_timeout 5;
}
}
SEMVER
WITHOUT SEMANTIC VERSIONING
A cautionary tale of versioning gone bad
1.0.0 Initial release for managing cacti
1.1.1 Change serverparam to servername
1.1.2 Move params from cacti::data to cacti::params
1.2.0 Updated README
1.2.1 Drops support for CentOS 5
1.3.0 This module now manages munin
2.0.0 I can update versions whenever I want?
10.51.100 THIS IS AWESOME!
-4.number.999999999999 I’VE CREATED A MONSTER
UPGRADING SHOULD BE BORING
API breakage means upgrading is dangerous
Nobody wants to upgrade if it means uncertainty
Semantic versioning helps mitigate this
WHAT IS SEMVER?
Version strings should have meaning
Releases match the format x.y.z
Values indicate what’s changed in that version
MAJOR RELEASES
Example: x.0.0
Backwards incompatible changes
Changing class names
Changing parameter names
Dropping platform support
MINOR RELEASES
Example: x.y.0
Backwards compatible features
Adding support for new platforms
Adding parameters
Adding features
PATCH RELEASES
Example: x.y.z
Bugfixes
Documentation
Tests
Anything that can’t be called a feature
SEMVER AS A CONTRACT
If you use SemVer, you’re making an agreement to avoid
making breaking changes
What is a breaking change?
What’s public?
What’s private?
WHAT IS PUBLIC?
Publicly exposed classes
Class parameters
The final behavior of your class
WHAT IS PRIVATE?
The actual resources used in your classes and defines
As long as they result in the same functionality
Classes that are documented as private
If you document that a class is private, people shouldn’t
use it
SAFETY IN SEMVER
SemVer takes the risk out of upgrading
You can understand the implications of upgrading right
away
How Puppet is doing it
3.1.0: Better support for Ruby code loading
3.1.1: Security fixes
3.2.0: External CA support, types & providers for
OpenWRT
4.0.0: Tachyon based transport layer
MAKE OTHER PEOPLE DO
YOUR WORK
AKA
REUSE MODULES
REUSE MODULES
Writing good code is hard.
Make other people do your work.
Being upstream is hard.
DISCOVERY VIA THE FORGE
Puppet Forge has close to 1200 modules
Provides a single point to discover and install modules
Easy access to documentation
README
Release notes
Auto generated Type & provider documentation
GET DEPENDENCIES FROM THE FORGE
root@example:~# puppet module install puppetlabs/mysql
Notice: Preparing to install into /etc/puppet/modules ...
Notice: Downloading from https://forge.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/etc/puppet/modules
└─┬ puppetlabs-mysql (v0.6.1)
└── puppetlabs-stdlib (v4.1.0)
COLLABORATE ON EXISTING MODULES
Lots of good modules are out there
Encourage people to publish on the Forge
Help improve existing modules
Only you can prevent ecosystem fragmentation
SMALL CONTRIBUTIONS HELP
Documentation
Bug fixes
Issue reports
ESTABLISH A
COMMUNITY
SURVIVING SUCCESS
Your module is a hit!
Prepare for a deluge of bug reports and feature requests
POPULARITY = MORE WORK
Things users are good at:
Finding bugs
Filing feature requests
Requesting things like “documentation”
Finding more bugs
BUILD YOUR COMMUNITY
Bug reports = people care
Show people how to help
Ask for pull requests
Guide people through the contribution process
Find people to give commit rights to
END
QUESTIONS?

Writing & Sharing Great Modules - Puppet Camp Boston

  • 1.
    WRITING & SHARING GREATMODULES Cody Herriges, Puppet Labs Twitter: @odyrf | Freenode: odyi
  • 2.
    WHO IS THISGUY? Destiny was written in the 6th grade. Professional Services @puppetlabs, ~2 years Operations Engineer @puppetlabs, ~1 year Integration Engineer @puppetlabs, ~1 month
  • 3.
    THINGS I DO Oldpublic modules (The PS era) puppetlabs-java_ks: java keystore management puppetlabs-corosync: build pacemaker clusters Less old public modules (The Operations era) puppetlabs-apacheds: stands up Apache Directory Server puppetlabs-ldap_entry: manipulates an ldap server puppetlabs-stunnel: set up SSL tunnels Lately puppet-openstack: build your openstack
  • 4.
  • 5.
    BEST PRACTICES Traditional development:50+ years to mature Modern config. mgmt: 15 years, max Best practices are evolving
  • 6.
    SO WHERE DOWE START? Separate your logic and configuration Know your interface Use semantic versioning Reuse everything Leverage the community
  • 7.
  • 8.
    SEPARATE LOGIC FROMDATA Logic != Data Example: configuring a service on different platforms Shouldn’t have to update every file in a module
  • 9.
    PACKAGE/FILE/SERVICE Humble beginnings formany modules class mysql::server { package { 'mysql-server': ensure => present, } file { '/etc/mysql/my.cnf': ensure => present, content => template('mysql/server/my.cnf.erb'), require => Package['mysql-server'], } service { 'mysqld': ensure => running, enable => true, subscribe => File['/etc/mysql/my.conf'], } }
  • 10.
    PROBLEMS WITH PACKAGE/FILE/SERVICE Nothinginherently wrong Overly simple Very static Generally requires overhaul for different platforms
  • 11.
    RUDIMENTARY DATA/LOGIC SEPARATION classmysql::server { include mysql::params package { 'mysql-server': name => $mysql::params::server_package, ensure => present, } file { 'my.cnf': path => $mysql::params::server_config, ensure => present, source => 'puppet:///modules/mysql/my.cnf', require => Package['mysql-server'], } service { 'mysql-server': name => $mysql::params::server_service, ensure => running,
  • 12.
    HARDCODING TUNABLE VALUES Wantto prevent people from reusing your modules? Hardcode everything!
  • 13.
    BAD PARAMS USE Paramsclass = good Why is this bad? Site specific defaults? INSECURE DEFAULTS‽ class mysql::params { $allow_hosts = '172.16.0.1/24' $root_password = 'changeme' $root_user = 'root' }
  • 14.
    GOOD PARAMS USE Forceuser to supply data Fail fast class mysql::params( $allow_hosts, # Force the module user to fill this out $root_password, # Fail fast rather than potentially use bad data $root_user = 'root' # Sane default ) { ... }
  • 15.
  • 16.
    DATA BINDING New inPuppet 3: data binding Provides a method for configuring modules
  • 17.
    USING DATA BINDING Definedata in a data store file database web service Automatically load data in the relevant manifests It is hierarchical
  • 18.
    USING DATA BINDING classmysql::params( $allow_hosts, $database_password, $database_user = 'root' ) { ... } # $datadir/common.yaml --- mysql::params::allow_hosts: '10.126.8.0/24' # $datadir/qa.mysite.local.yaml --- mysql::params::allow_hosts: '10.134.8.0/24'
  • 19.
  • 20.
    MODULES AS INTERFACES Puppetsimplifies management of services Defines how people interact with that service Puppet modules define an interface for that service Creates two challenges What options are supported? What options should users configure?
  • 21.
    BE OPINIONATED Cannot makeevery option tunable You’ll go insane Require mandatory data Add parameters for frequently changed data Offer an ‘override’ option
  • 22.
    BUT OTHER OPINIONSARE NICE TOO You can’t always support every option Allow people to directly insert their own configuration
  • 23.
    OVERRIDE EXAMPLE: PARTIALTEMPLATES Module provides template fragments User assembles these into a full config
  • 24.
    CREATING A PARTIALTEMPLATE <%# nginx/templates/vhost/_listen.conf.erb %> <%# Configuration fragment for listening on IPv4 and IPv6 with SSL %> <% unless @sslonly -%> listen <%= port %>; <% if scope.lookupvar('::ipaddress6') -%> listen [::]:<%= port %>; <% end -%> <% end -%> <% if ssl -%> listen <%= ssl_port %> ssl; <% if scope.lookupvar('::ipaddress6') -%> listen [::]:<%= ssl_port %> ssl; <% end -%> <% end -%>
  • 25.
    USING PARTIAL TEMPLATES Example:my_nginx_app/templates/nginx- vhost.conf.erb server { <%= scope.function_template(['nginx/vhost/_listen.conf.erb']) %> root /usr/share/empty; location / { proxy_pass <%= @proto %>://workers; proxy_redirect off; proxy_next_upstream error timeout invalid_header http_500 http_503; proxy_connect_timeout 5; } }
  • 26.
  • 27.
    WITHOUT SEMANTIC VERSIONING Acautionary tale of versioning gone bad 1.0.0 Initial release for managing cacti 1.1.1 Change serverparam to servername 1.1.2 Move params from cacti::data to cacti::params 1.2.0 Updated README 1.2.1 Drops support for CentOS 5 1.3.0 This module now manages munin 2.0.0 I can update versions whenever I want? 10.51.100 THIS IS AWESOME! -4.number.999999999999 I’VE CREATED A MONSTER
  • 28.
    UPGRADING SHOULD BEBORING API breakage means upgrading is dangerous Nobody wants to upgrade if it means uncertainty Semantic versioning helps mitigate this
  • 29.
    WHAT IS SEMVER? Versionstrings should have meaning Releases match the format x.y.z Values indicate what’s changed in that version
  • 30.
    MAJOR RELEASES Example: x.0.0 Backwardsincompatible changes Changing class names Changing parameter names Dropping platform support
  • 31.
    MINOR RELEASES Example: x.y.0 Backwardscompatible features Adding support for new platforms Adding parameters Adding features
  • 32.
  • 33.
    SEMVER AS ACONTRACT If you use SemVer, you’re making an agreement to avoid making breaking changes What is a breaking change? What’s public? What’s private?
  • 34.
    WHAT IS PUBLIC? Publiclyexposed classes Class parameters The final behavior of your class
  • 35.
    WHAT IS PRIVATE? Theactual resources used in your classes and defines As long as they result in the same functionality Classes that are documented as private If you document that a class is private, people shouldn’t use it
  • 36.
    SAFETY IN SEMVER SemVertakes the risk out of upgrading You can understand the implications of upgrading right away How Puppet is doing it 3.1.0: Better support for Ruby code loading 3.1.1: Security fixes 3.2.0: External CA support, types & providers for OpenWRT 4.0.0: Tachyon based transport layer
  • 37.
    MAKE OTHER PEOPLEDO YOUR WORK
  • 38.
  • 39.
  • 40.
    REUSE MODULES Writing goodcode is hard. Make other people do your work. Being upstream is hard.
  • 41.
    DISCOVERY VIA THEFORGE Puppet Forge has close to 1200 modules Provides a single point to discover and install modules Easy access to documentation README Release notes Auto generated Type & provider documentation
  • 42.
    GET DEPENDENCIES FROMTHE FORGE root@example:~# puppet module install puppetlabs/mysql Notice: Preparing to install into /etc/puppet/modules ... Notice: Downloading from https://forge.puppetlabs.com ... Notice: Installing -- do not interrupt ... /etc/puppet/modules └─┬ puppetlabs-mysql (v0.6.1) └── puppetlabs-stdlib (v4.1.0)
  • 43.
    COLLABORATE ON EXISTINGMODULES Lots of good modules are out there Encourage people to publish on the Forge Help improve existing modules Only you can prevent ecosystem fragmentation
  • 44.
  • 45.
  • 46.
    SURVIVING SUCCESS Your moduleis a hit! Prepare for a deluge of bug reports and feature requests
  • 47.
    POPULARITY = MOREWORK Things users are good at: Finding bugs Filing feature requests Requesting things like “documentation” Finding more bugs
  • 48.
    BUILD YOUR COMMUNITY Bugreports = people care Show people how to help Ask for pull requests Guide people through the contribution process Find people to give commit rights to
  • 49.