create module, xymon, puppet module

Creating first Puppet Module

Puppet modules are the fundamental building block of puppet and are used for abstracting away the differences between different platforms. A good module for some software should not define how you want the software, but provide an API so that the software can be used on multiple platforms without needing to know the intricacies of that platform. An important part of writing reusable modules is that other people can easily understand you code and that the module can be used by different companies. Puppetlabs has published a Puppetlab Style Guide which is based on best practices in the Puppet community.

Check what the module must do

In the following example we are going to create the xymon-client formenly know as BigBrother cliënt module for checking system services. The bb-hosts and bb-bbexttab files are very imported for creating this module:

[root@puppetclient etc]# cat /opt/bb/etc/bb-hosts
# bb-hosts is  managed by puppet
192.168.178.130 xymon.monitor.com # BBDISPLAY BBNET BBPAGER
192.168.178.131 puppetclient
[root@puppetclient etc]# cat /opt/bb/etc/bb-bbexttab
localhost : : bb-ipfilt.sh
localhost : : bb-postqueue.sh
localhost : : bb-selinux.sh

In the /opt/bb/ext directory we have created our own scripts for system checking.

[root@puppetclient etc]# ls -l /opt/bb/ext/
total 212
-rwxr-xr-x. 1 bb bin  2993 Feb 22 09:00 bb-ipfilt.sh
-rwxr-xr-x. 1 bb bin  2725 Feb 22 09:00 bb-postqueue.sh
-rwxr-xr-x. 1 bb bin  2725 Feb 22 09:00 bb-selinux.sh

When xymon-client is started, it will check its local hostname:

 192.168.178.131 puppetclient

Sends the system checks to xymon server:

 192.168.178.130 xymon.monitor.com

It will also checks which services for this server must be checked (bb-bbexttab). All this checks are created in the /opt/bb/ext folder. In this module we are also using the concat module.The concat module construct files from multiple fragments.

Create a module structure

Before we going to create our module it is good to read the Beginner's Guide to Modules. After reading this we have first to create a module structure. This can be done by doing the following.

puppet module generate puppet-xymon

[root@puppetmaster ~]# puppet module generate dennis-bb
We need to create a metadata.json file for this module.  Please    answer the
following questions; if the question is not applicable to this   module, feel free
to leave it blank.
Puppet uses Semantic Versioning (semver.org) to version modules.
What version is this module?  [0.1.0]
-->
Who wrote this module?  [dennis]
-->
What license does this module code fall under?  [Apache-2.0]
-->
How would you describe this module in a single sentence?
--> Bb client module
Where is this module's source code repository?
--> My repo
Where can others go to learn more about this module?
--> Create a basis module
Where can others go to file issues about this module?
--> Mail me
----------------------------------------
{
"name": "dennis-bb",
"version": "0.1.0",
"author": "dennis",
"summary": "Xymon client module",
"license": "Apache-2.0",
"source": "My repo",
"project_page": "Create a basis module",
"issues_url": "Mail me",
"dependencies": [
{"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"}
 ],
  "data_provider": null
}
----------------------------------------
About to generate this metadata; continue? [n/Y]
--> y
Notice: Generating module at /root/bb...
Notice: Populating templates...
Finished; module generated in xymon.
bb/Gemfile
bb/Rakefile
bb/examples
bb/examples/init.pp
bb/manifests
bb/manifests/init.pp
bb/spec
bb/spec/classes
bb/spec/classes/init_spec.rb
bb/spec/spec_helper.rb
bb/README.md
bb/metadata.json

Customise the module

Now we have a basic module structure which we can use to create our working XYMON module. The main file and entry point to the module is bb/init.pp and if we open that up we will see a lot of comments and a empty xmon class construct. This class will later provide the API to the xymon module but for now we will leave it as it is.

 class bb {
 }

The basic pattern of most modules on Linux servers involves 3 components:

Software installation:

This can be anything from RPM, DEB package management through Ruby Gems through to a Zip or TAR file.

Configure the software:

This is usually through a configuration file but may involve creating databases or directories with the correct permissions.

Start the services

This is usually through whatever service management system is being used on the system. This may be things like systemd, chkconfig, supervisord. This step may not be needed if it not software that runs as a service.

To make our code easy to understand we split these three jobs into separate classes and keep them in their own files.

install.pp: class modulename::install
config.pp: class modulename::config
service.pp: class modulename::service

We create a class which contains the default parameters for our module:

params.pp: class modulename::params

In here we will create a defined resource type,

Defined resource types (also called defined types or defines) are blocks of Puppet code that can be evaluated multiple times with different parameters. Once defined, they act like a new resource type: you can cause the block to be evaluated by declaring a resource of that new resource type.

Defines can be used as simple macros or as a lightweight way to develop fairly sophisticated resource types.

Lets start with the software installation step of our module in manifests/install.pp.

Content for manifests/install.pp

 # == Class bb::install
class bb::install inherits bb {

package { 'bbc':
ensure => present,
 }
}

In this class we inherit from xymon. This inheritance will be used later to inherit properties that the main xymon class configures. The second thing it does in define a Puppet type package named xymon and ensure it is installed. Puppet abstracts away the actual installation so that this will install xymon on systems with different package managers. For instance, on Debian it will use apt-get and on Redhat it will use yum install

Next we need to configure our xymon software. The config file for xymon is bb-hosts and bb-bbexttab in /opt/xymon/etc/

Content for manifests/config.pp

# == Class bb::config
class bb::config inherits bb
{
$bbexttab = '/opt/bb/etc/bb-bbexttab'
$bbtests.each |String $bbtest| {
bb::bbexttab { $bbtest:
bbtest => $bbtest,
 }
}

file { '/opt/bb/etc/bb-hosts':
ensure  => file,
owner   => 'bb',
group   => 'bin',
mode    => '0644',
content => epp('bb/bb-hosts.epp'),
notify  => Service['bb'],
}

concat { $bbexttab:
ensure => present,
group  => 'bin',
mode   => '0644',
owner  => 'bb',
notify => Service['bb'],
}

concat::fragment{ 'bbexttab_header':
target  => $bbexttab,
content => "#bb-bbexttab is managed by Puppet\n",
order   => '01'
 }
}

As with xymon::install we inherit from the main xymon class. Next we use the Puppet type file to manage the xymon config file setting it’s permissions and content which is created by a template

Templates go in the template directory of our module. They can contain variables and basic logic to construct the end file but for now we will just create one with no template logic and we can come back to it later.

Content for templates/bb-hosts.epp

<% if $bb::bigbrother =~ Array[Data] { -%>
<% $bb::bigbrother.flatten.each |$bigbrother| {-%>
<%= $bigbrother %> # BBDISPLAY BBNET BBPAGER
<% } -%>
<% } -%>
<%= $facts[ipaddress] %> <%= $facts[hostname] %>

As mentioned, we also created a defined resource type.

#Defined resource type
#bb::bbexttab { 'bb-ad':
#      bbtest => "bb-ad",
define bb::bbexttab (
$bbtest,
) {

if ! ($::osfamily in ['Debian', 'RedHat', 'Suse']) {
fail('bb::bbexttab does not support osfamily $::osfamily')
}

concat::fragment { $bbtest:
target  => '/opt/bb/etc/bb-bbexttab',
content => "localhost: : ${bbtest}.sh\n",
notify  => Service['bb'],
 }
}

Content for manifests/service.pp

# == Class bb::service
class bb::service inherits bb {

  service { 'bb':
    ensure     => running,
    enable     => true,
    hasstatus  => false,
    hasrestart => true,
  }
}

Once again we inherit from the main xymon class. This time we use the Puppet type service which is an abstraction of most major service control systems on Linux. We ensure that the service xymon is running and that it is enabled on boot. It also requires the package xymon because there is no point trying to start software that isn’t installed.

No we going to create the manifests/params.pp file with the following content:

# == Class bb::params
#
# This class is meant to be called from bb.
# It sets variables according to platform.
#
class bb::params
{
  $bigbrother   = [ '10.10.10.10 xymon.monitor.com' ]
  $bbtests  = [
    'vmstat-bf',
    'netstat-bf',
    'bb-memory'
  ]
}

Now we have our three subclasses defined we need to let the main xymon class know about them. To do this we need to include our subclasses in our main xymon class.

class bb (
$bigbrother  = $::bb::params::bigbrother,
$bbtests    = $::bb::params::bbtests,

) inherits bb::params {

if ! ($::osfamily in ['Debian', 'RedHat', 'Suse']) {
fail('bb::bbexttab does not support osfamily $::osfamily')
}

class { '::bb::install': }
class { '::bb::config': }
class { '::bb::service': }
}

In our example our profile will look like this ==>

class { 'bb':

  bigbrother  => [ 
    '192,168.178.131 puppetmaster',
    '192.168.178.131 puppetclient' ],
     bbtests => [
    'vmstat-bf',
    'netstat-bf',
    'bb-memory',
    'bb-ipfilt',
    'bb-postqueue' ],
}

if $facts['os']['selinux']['enforced'] == true {
  bb::bbexttab { 'bb-selinux':
    bbtest => "bb-selinux",
  }
}

if $facts['virtual'] == 'physical' {
  bb::bbexttab { 'hardware':
      bbtest => "hardware",
 }
  bb::bbexttab { 'multipath':
      bbtest => "multipath",
 }
}

if $facts['dmz'] == false {
  bb::bbexttab { 'bb-ad':
      bbtest => "bb-ad",
  }
 }
}