44
Basic Puppet Module Design Jeremy Kitchen Systems Engineer at NationBuilder

Puppet Camp LA 2015: Basic Puppet Module Design (Beginner)

Embed Size (px)

Citation preview

Basic Puppet Module DesignJeremy 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.ppclass 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.ppclass 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::mountsinclude site::webappinclude mysqlinclude 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.ppclass 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.ppclass profile::server { class { 'foo': port => 3389, verbose => true, }}

example

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

# profile/manifests/server.ppclass 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.ppclass 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.ppdefine 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 [email protected]? -%>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 exampleclass 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.ppdefine 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') } endend

spec/classes/foo_spec.rbdescribe '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, )} endend

spec/classes/foo_spec.rbdescribe '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}) } endend

spec/classes/foo_spec.rbdescribe '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/) } endend

Beaker?

Forge Tips

• Limit dependencies

• External resource dependencies (user, repo)

• anchor pattern

• use include foo vs class {‘foo’: }

ResourcesExample 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

[email protected] github.com/kitchen twitter.com/kitchen

NationBuilder.com (we’re hiring!)