Vous êtes sur la page 1sur 44

Basic Puppet Module Design

Jeremy Kitchen
Systems Engineer at NationBuilder
What is a module?
Package
Config
Service
Module Contents

foo/
manifests/
defaults.pp
init.pp
install.pp
config.pp
service.pp
templates/
foo.conf.erb
manifests/defaults.pp
class foo::defaults {
case $osfamily {
'Debian': {
$package_name = 'foo-ruby'
$service_name = 'foo'
$config_path = '/etc/foo/foo.conf'
$log_file = '/var/log/foo/foo.log'
$storage_path = '/var/lib/foo'
}
'RedHat': {
$package_name = 'ruby-foo'
$service_name = 'foo'
$config_path = '/etc/foo.conf'
$log_file = '/var/log/foo.log'
$storage_path = '/var/lib/foo'
}
default: {
fail("${osfamily} not currently supported by this module")
}
}
}
manifests/init.pp
class foo (
$package_ensure = installed,
$listen = '127.0.0.1',
$port = '1234',
$verbose = false,
) inherits foo::defaults {
if (!is_ip_address($listen)) {
fail('listen parameter needs to be an ip address')
}
$verbose_bool = str2bool($verbose)

include foo::install
include foo::config
include foo::service

Class['foo::install'] -> Class['foo::config'] ~> Class['foo::service']

anchor {
'foo::begin':
before => Class['foo::install','foo::config'],
notify => Class['foo::service'];
'foo::end':
require => Class['foo::service'];
}
}
manifests/init.pp

class foo (
$package_ensure = installed,
$listen = '127.0.0.1',
$port = '1234',
$verbose = false,
) inherits foo::defaults {
manifests/init.pp

if (!is_ip_address($listen)) {
fail('listen parameter needs to be an ip address')
}
$verbose_bool = str2bool($verbose)
manifests/init.pp

include foo::install
include foo::config
include foo::service

Class['foo::install'] -> Class['foo::config'] ~>


Class['foo::service']
manifests/init.pp

anchor {
'foo::begin':
before => Class['foo::install','foo::config'],
notify => Class['foo::service'];
'foo::end':
require => Class['foo::service'];
}
containment example

include site::mounts
include site::webapp
include mysql
include apache

Class['site::mounts'] -> Class['mysql']


Class['site::mounts'] -> Class['apache']
Class['mysql'] -> Class['apache']
Class['mysql'] -> Class['site::webapp']
manifests/init.pp

anchor {
'foo::begin':
before => Class['foo::install','foo::config'],
notify => Class['foo::service'];
'foo::end':
require => Class['foo::service'];
}
what about contain?
manifests/init.pp
class foo (
$package_ensure = installed,
$listen = '127.0.0.1',
$port = '1234',
$verbose = false,
) inherits foo::defaults {
if (!is_ip_address($listen)) {
fail('listen parameter needs to be an ip address')
}
$verbose_bool = str2bool($verbose)

include foo::install
include foo::config
include foo::service

Class['foo::install'] -> Class['foo::config'] ~> Class['foo::service']

anchor {
'foo::begin':
before => Class['foo::install','foo::config'],
notify => Class['foo::service'];
'foo::end':
require => Class['foo::service'];
}
}
manifests/install.pp

class foo::install inherits foo {


package { $package_name:
ensure => $package_ensure,
}
}
manifests/config.pp

class foo::config inherits foo {


file {
$config_path:
content => template('foo/foo.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644';
}
}
Module Contents

class foo::service inherits foo {


service { $service_name:
ensure => running,
enable => true,
}
}
templates/foo.conf.erb

[main]
listen = <%= @listen %>
port = <%= @port %>
verbose = <%= @verbose_bool ? 'yes' : 'no' %>
log_file = <%= @log_file %>
storage_dir = <%= @storage_dir %>
Module Contents

foo/
manifests/
defaults.pp
init.pp
install.pp
config.pp
service.pp
templates/
foo.conf.erb
example

# profile/manifests/server.pp
class profile::server {
class { 'foo':
port => 3389,
verbose => true,
}
}
example

# hiera/global.yaml
foo::port: 3389
foo::verbose: true

# profile/manifests/server.pp
class profile::server {
include ::foo
}
conf.d
Module Contents

foo/
manifests/
defaults.pp
init.pp
install.pp
config.pp
service.pp
plugin.pp
templates/
foo.conf.erb
plugin.conf.erb
manifests/defaults.pp

class foo::defaults {
case $osfamily {
'Debian': {
$package_name = 'foo-ruby'
$service_name = 'foo'
$config_path = '/etc/foo/foo.conf'
$log_file = '/var/log/foo/foo.log'
$storage_path = '/var/lib/foo'
$conf_d_path = '/etc/foo/conf.d'
$plugin_path = '/usr/share/foo/plugins'
}
'Redhat': {
$package_name = 'ruby-foo'
$service_name = 'foo'
$config_path = '/etc/foo.conf'
$log_file = '/var/log/foo.log'
$storage_path = '/var/lib/foo'
$conf_d_path = '/etc/foo.d/'
$plugin_path = '/usr/lib/foo/plugins'
}
}
}
manifests/config.pp
class foo::config inherits foo {
file {
$config_path:
content => template('foo/foo.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644';
$conf_d_path:
ensure => directory,
purge => true,
recurse => true,
owner => 'root',
group => 'root',
mode => '0755';
}
}
templates/foo.conf.erb

[main]
listen = <%= @listen %>
port = <%= @port %>
verbose = <%= @verbose_bool ? 'yes' : 'no' %>
log_file = <%= @log_file %>
storage_dir = <%= @storage_dir %>
@include <%= @conf_d_path %>/*.conf
manifests/plugin.pp
define foo::plugin (
$type,
$path = "${foo::defaults::plugin_path}/${name}.rb",
$verbose = undef,
) {
include foo::defaults

validate_absolute_path($path)
$verbose_bool = str2bool($verbose)

file { "${foo::defaults::conf_d_path}/${name}.conf":
content => template('foo/plugin.conf.erb'),
owner => 'root',
group => 'root',
mode => '0755',
notify => Class['foo::service'],
}
}
templates/plugin.conf.erb

[plugin <%= @name %>]


type = <%= @type %>
path = <%= @path %>

<%- if !@verbose.nil? -%>


verbose = <%= @verbose_bool ? 'yes' : 'no' %>
<%- end -%>
example

include foo

foo::plugin {
'webapp':
type => 'passenger';
'db':
type => 'mysql',
verbose => true,
path => '/usr/local/share/custom_mysql.rb';
}
role/profile example
class profile::app {
include foo
foo::plugin {
'webapp':
type => 'passenger';
}
}

class profile::db {
include foo
foo::plugin {
'db':
type => 'mysql';
}
}

class role::server {
include profile::app
include profile::db
}
no conf.d?
manifests/config.pp

class foo::config inherits foo {


concat {
$config_path:
owner => 'root',
group => 'root',
mode => '0644';
}

concat::fragment { 'foo_conf_header':
content => template('foo/foo.conf.erb'),
target => $config_path,
order => '00_header',
}
}
manifests/plugin.pp
define foo::plugin (
$type,
$path = "${foo::defaults::plugin_path}/$
{name}.rb",
$verbose = undef,
) {
include foo::defaults

validate_absolute_path($path)
$verbose_bool = str2bool($verbose)

concat { "foo_conf_plugin_${name}":
content => template('foo/plugin.conf.erb'),
target => $conf_file,
order => "10_plugin_${name}",
}
}
what about docs?
README.md

What it manages

Top level class parameters

Defined types and their parameters

Optional dependencies

Common usage examples


Testing
spec/classes/foo_spec.rb

describe 'foo' do
context 'default parameters' do
let (:params) {{ }}
it { should compile.with_all_deps }
it { should contain_class('foo::defaults') }
it { should contain_class('foo::install') }
it { should contain_class('foo::config') }
it { should contain_class('foo::service') }
end
end
spec/classes/foo_spec.rb
describe 'foo' do
let (:facts) {{
:osfamily => 'Redhat'
}}
context 'default parameters' do
it { should contain_package('ruby-foo') }
it { should contain_file('/etc/foo.conf').with(
:owner => 'root',
:group => 'root',
:mode => '0644',
)}
it { should contain_file('/etc/foo.d').with(
:owner => 'root',
:group => 'root',
:mode => '0755',
:purge => true,
:recurse => true,
)}
it { should contain_service('foo').with(
:ensure => 'running',
:enable => true,
)}
end
end
spec/classes/foo_spec.rb
describe 'foo' do
let (:facts) {{
:osfamily => 'Redhat'
}}
let (:config_file) { '/etc/foo.conf' }
context 'default parameters' do
it { should contain_file(config_file)
.with_content(/listen = 127\.0\.0\.1/)
}
it { should contain_file(config_file)
.with_content(/port = 1234/)
}
it { should contain_file(config_file)
.with_content(/verbose = no/)
}
it { should contain_file(config_file)
.with_content(%r{log_file = /var/log/foo.log})
}
it { should contain_file(config_file)
.with_content(%r{storage_dir = /var/lib/foo})
}
end
end
spec/classes/foo_spec.rb
describe 'foo' do
let (:facts) {{
:osfamily => 'Redhat'
}}
let (:config_file) { '/etc/foo.conf' }

context 'parameters set' do


let (:params) {{
:listen => '10.0.0.42',
:port => '4567',
}}
it { should contain_file(config_file).with_content(/listen = 10\.0\.0\.42/) }
it { should contain_file(config_file).with_content(/port = 4567/) }
end

context 'boolean values true' do


let (:params) {{
:verbose => true,
}}
it { should contain_file(config_file).with_content(/verbose = yes/) }
end

context 'boolean values fales' do


let (:params) {{
:verbose => true,
}}
it { should contain_file(config_file).with_content(/verbose = no/) }
end
end
Beaker?
Forge Tips

Limit dependencies

External resource dependencies (user, repo)

anchor pattern

use include foo vs class {foo: }


Resources
Example modules

puppetlabs/ntp: https://forge.puppetlabs.com/puppetlabs/ntp

pdxcat/collectd: https://forge.puppetlabs.com/pdxcat/collectd

Library modules

puppetlabs/stdlib: https://forge.puppetlabs.com/puppetlabs/stdlib

puppetlabs/concat: https://forge.puppetlabs.com/puppetlabs/concat

Testing

rspec-puppet: http://rspec-puppet.com

beaker: https://github.com/puppetlabs/beaker
Thanks!
kitchen@kitchen.io
github.com/kitchen
twitter.com/kitchen

NationBuilder.com
(were hiring!)

Vous aimerez peut-être aussi