Writing and Publishing Puppet Modules
Colleen Murphy
HelloMe (then):
Student sysadmin at the IT department for Portland State University’s College of Engineering
aka The Computer Action Team (TheCAT)
github.com/pdxcat forge.puppetlabs.com/pdxcat
HelloMe (now):Module engineer at Puppet Labs
What is a puppet module?● An encapsulation of configuration for a
service● A structure containing an organized set of
puppet code and data● Analogous to a package, gem, python library● The place where your code goes
What should a module do?● Set up a service, such as:
○ ssh○ mysql○ apache○ sudo
● Extend puppet functionality. Examples:○ puppetlabs/stdlib○ puppetlabs/concat
The strategySet up the service… without puppet.
Then iterate.
Layout of a moduleyourmodule/
➔ manifests/ # where your puppet code goes➔ files/ # flat configuration files➔ templates/ # dynamic configuration files➔ lib/ # plugins: types and providers, functions,
| facts, etc➔ tests/ # smoke tests/example usage➔ spec/ # automated tests
Layout of a moduleyourmodule/
➔ manifests/ # where your puppet code goes➔ files/ # flat configuration files➔ templates/ # dynamic configuration files
Layout of a moduleyourmodule/
➔ manifests/ # where your puppet code goes➔ files/ # flat configuration files➔ templates/ # dynamic configuration files
➔ tests/ # smoke tests/example usage➔ spec/ # automated tests
Starting out$ puppet module generate cmurphy-ssh && mv cmurphy-ssh ssh
$ mkdir ssh/{files,templates}
Writing your first module# manifests/init.pp
class ssh {
}
Writing your first module# manifests/init.pp
class ssh {
package { 'openssh-server':
ensure => installed,
}
}
Writing your first module# manifests/init.pp
class ssh {
package { 'openssh-server':
ensure => installed,
}
file { '/etc/ssh/sshd_config':
source => "puppet:///modules/ssh/sshd_config",
require => Package['openssh-server'],
}
}
Writing your first module# ...
package { 'openssh-server':
ensure => installed,
}
file { '/etc/ssh/sshd_config':
source => "puppet:///modules/ssh/sshd_config",
require => Package['openssh-server'],
}
service { 'ssh':
ensure => running,
enable => true,
subscribe => File['/etc/ssh/sshd_config'],
}
}
Drop in a configuration file# files/sshd_config
# Managed by Puppet
# What ports, IPs and protocols we listen for
Port 22
Protocol 2
# Logging
SyslogFacility AUTH
LogLevel INFO
# Authentication:
LoginGraceTime 120
PermitRootLogin no
StrictModes yes
# ...
Writing your first module# manifests/init.pp
class ssh {
package { 'openssh-server':
ensure => installed,
}
file { '/etc/ssh/sshd_config':
source => "puppet:///modules/ssh/sshd_config",
require => Package['openssh-server'],
}
service { 'ssh':
ensure => running,
enable => true,
subscribe => File['/etc/ssh/sshd_config'],
}
}
Writing your first module# tests/init.pp
include ssh
# or
# /etc/puppet/manifests/site.pp
node default {
include ssh
}
Needs more portability!
No one should have to change your code or your files in order to use your module.
Template your module# templates/sshd_config.erb
# Managed by Puppet
# What ports, IPs and protocols we listen for
Port <%= @port %>
Protocol 2
# Logging
SyslogFacility <%= @syslog_facility %>
LogLevel <%= @log_level %>
# Authentication:
LoginGraceTime 120
PermitRootLogin <%= @permit_root_login %>
StrictModes yes
# ...
Template your module# manifests/init.pp
class ssh (
$port = 22,
$syslog_facility = 'AUTH',
$log_level = 'INFO',
$permit_root_login = 'no',
) {
# ...
file { '/etc/ssh/sshd_config':
content =>
template('ssh/sshd_config.erb'),
require => Package['openssh-server'],
} # ...
# tests/init.pp or site.pp
# ...
class { 'ssh':
permit_root_login => 'without-password',
}
Templating strategies# manifests/init.pp
class ssh (
$ports = [ 22 ],
$options = {}
) {
# ...
file { '/etc/ssh/sshd_config':
content =>
template('ssh/sshd_config.erb'),
require => Package['openssh-server'],
}
# ...
# Applying the class
class { 'ssh':
ports => [ 22, 2222 ],
options => {
'PermitRootLogin' => 'no',
}
}
Templating strategies# templates/sshd_config.erb
# Managed by Puppet
<% @ports.each do |port| %>
Port <%= port %>
<% end %>
<% @options.each do |k,v| %>
<%= k %> <%= v %>
<% end %>
Templating strategies● Take advantage of Include conf.d/* directives
file { '/etc/collectd.conf':
ensure => present,
content => 'Include "conf.d/*.conf"\n',
}
# …
define collectd::plugins::exec {
file { "${name}.load":
path => "${conf_dir}/${name}.conf",
content => template('collectd/exec.conf.erb'),
}
}
Beyond templates● puppetlabs/concat concat { '/etc/motd': }
concat::fragment { 'welcome':
target => '/etc/motd',
content => 'Welcome to Redhat',
order => '01',
}
concat::fragment { 'legal':
# …
}
Beyond templates● puppetlabs/inifile
ini_setting { 'puppetdbserver':
ensure => present,
section => 'main',
path => "${puppet_confdir}/puppetdb.conf",
setting => 'server',
value => $server,
}
ini_setting { 'puppetdbport':
# …
}
Beyond Templates● augeas
● domcleal/augeasproviders
augeas { 'sshd_config_permit_root_login':
context => '/files/etc/ssh/sshd_config',
changes => "set PermitRootLogin $permit_root_login",
require => File['/etc/ssh/sshd_config'],
}
sshd_config { "PermitRootLogin":
ensure => present,
value => $permit_root_login,
}
Smart Parameter Defaults# manifests/params.pp
class ssh::params {
case $::osfamily {
'Debian': {
$ssh_svc = 'ssh'
}
'RedHat': {
$ssh_svc = 'sshd'
}
default: {
fail("${::osfamily} is not supported.")
}
}
}
# manifests/init.pp
class ssh (
# ...
) {
include ssh::params
service { $ssh::params::ssh_svc:
ensure => running,
enable => true,
}
# ...
The Forge
Publishing your modulemetadata.json● module name● version (SemVer)● author● summary● source● issues URL● operating system support● dependencies
Publishing your moduleUse semantic versioning! semver.org
Major.Minor.Patch
Publishing your moduleChangelog## 2013-12-05 Release 0.10.0### Summary:
This release adds FreeBSD osfamily support and various other improvements to some mods.
### Features:
- Add suPHP_UserGroup directive to directory context- Add support for ScriptAliasMatch directives...
## 2013-09-06 Release 0.9.0### Summary:
...
Publishing your moduleREADME● docs.puppetlabs.com/puppet/3/reference/READMEtemplate.markdown
Publishing your modulelicense● choosealicense.com
Publishing your module$ cd ssh/
$ puppet module build .
$ ls pkg/
cmurphy-ssh-0.0.1 cmurphy-ssh-0.0.1.tar.gz
TestingWhy we test:● Testing gives us (some) assurance that our
code won’t break production systems● Contributors can run tests without having
the same infrastructure as you
Testing your module● Smoke testing
# puppet apply --noop tests/init.pp
Testing your module● Unit testing: rspec-puppet
○ rspec-puppet.com
$ bundle exec rake spec
Testing your module# spec/classes/init_spec.rb
require 'spec_helper'
describe 'collectd' do
let :facts do
{:osfamily => 'RedHat'}
end
it { should contain_package('collectd').with(
:ensure => 'installed'
)}
it { should contain_service('collectd').with(
:ensure => 'running'
)}
# ...
Testing your module● Acceptance testing: beaker-rspec
○ github.com/puppetlabs/beaker○ youtu.be/jEJmUQOlaDg
$ bundle exec rspec spec/acceptance
Testing your module# spec/acceptance/class_spec.rb
require 'spec_helper_acceptance'
case fact('osfamily')
# ...
describe 'ssh class' do
context 'default parameters' do
it 'should work with no errors' do
pp = "class { 'ssh': }"
apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => true)
end
describe service(servicename) do
it { should be_running }
end
# ...
Testing your module● Linting
$ bundle exec rake lint
Maintaining your moduleUpdate your code● fix bugs● add features● manage pull requests
Installing modulesSearch for modules on forge.puppetlabs.com or puppet module search ssh
Then install with puppet module install saz/ssh
Where now?Learn more at docs.puppetlabs.com/guides/module_guides/bgtm.html
Get help atAsk: ask.puppetlabs.comIRC: #puppet on freenodeMailing list: groups.google.com/group/puppet-users
Thanks!Find me:
Colleen Murphyfreenode: crinklegithub: cmurphy
twitter: @pdx_krinkle
Top Related