Skip to content

Puppet

Puppet🔗

=("[Documentation](" + this.docs + ")")

General

  • Desired State Configuration (DSC)
  • Idempotency

Puppet language🔗

Resources🔗

puppet_resource_structure.png

Puppet URI: puppet://<server>/<mount point>/<path> (better syntax for hosted files available)

Resource defaults

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# capital letter
File {
  owner => 'root',
  group => 'root',
  mode  => '0644',
}
# or better in some ways (e.g. scope)
file {
  default:
    owner  => 'root',
    group  => 'root',
    mode   => '0644',
    ensure => file
  ;
  '/etc/motd':
    source => 'puppet:///modules/defaults/motd'
  ;
}

Dependencies🔗

There are mutiple ways to declare dependencies between resources to ensure they are run in a certain order. With newer version Puppet should also adhere to the order in which resources are stated in the manifest. This can save code repetition if many resources depend on one resource or the other way around.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
## direct sequential dependency
file { 'resource1': }
~> exec { 'resource2': }
## from earlier resource to later
file { 'resource1':
  notify => [Exec['resource2'], Service['resource3']],
}
exec { 'resource2': }
service { 'resource3': }
## from later resource to earlier
file { 'resource1': }
service { 'resource2':
  require => File['resource2'],
}

File🔗

file sources

1
2
3
4
file { '/etc/motd':
  ensure => file,
  source => 'puppet:///modules/modulname/directory/motd.txt'
}

Classes🔗

Simple abstraction, really a way of naming a block of code to be used elsewhere.

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# class definition
class motd {
  file { 'Message of the day':
    path    => '/etc/motd',
    ensure  => file,
    content => 'Keep calm and carry on.',
  }
}

# class declaration/instance
class { 'motd':
  ensure => present,
}

Often classes are scoped and they can have (type-enforced) parameters, e.g.

1
2
3
4
5
6
7
8
9
class profile::motd (
  String $message = 'Default message',
) inherits profile::params {
  file { 'motd':
    path    => '/etc/motd',
    ensure  => file,
    content => $message,
  }
}

Inheritance in Puppet is messy and only parameters should be set like this.

Variables🔗

  • immutable, no re-assignment

Syntax example

1
2
3
4
5
6
7
8
$some_name = 'Bob' # single quotes as muhc as possible
$some_greeting = "Hello ${some_name}" # variable for string interpolation
# selector: variable assignment
$default_editor = $facts['os']['family'] ? {
  'Linux'   => 'vim',
  'Windows' => 'notepad',
  default   => 'nano',
}

Logic and Comparators🔗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
if $some_boolean {

} else {

}
# inverted if
unless $another_boolean {

} else {

}
# selector: match multiple values
case $some_name {
  'bob': { include basic }
  'carol', 'ben': { include expert }
  'sue': { include regular }
}

Iterators🔗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# each: array/hash values to variable(s)
['carol', 'bob', 'alice'].each |$username| {
    user { $username:
    home       => "/var/www/${username}",
    managehome => true,
  }
}
{
  'bob' => '/home/bob_burger',
  'alice' => '/var/rabbit_hole',
  'tina' => '/srv/justice',
}.each |$username, $homedir| {
  # ...
}
# see also map and filter

Functions🔗

Examples

1
2
3
4
5
6
7
8
9
# template functions
file { '/etc/motd':
  content => epp('motd/message.epp', {message => 'Welcome!'})
}
# lookup parameters from Hiera
$userlist = lookup('profile::admin_users::users')
user { $userlist:
  ensure => present,
}

Logging🔗

Examples: https://stackoverflow.com/a/16671685
Notify resource

1
notify { "My message: variable = $variable": }
Different Puppet log level functions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
node default {
  notice("try to run this script with -v and -d to see difference between log levels")
  notice("function documentation is available here: http://docs.puppetlabs.com/references/latest/function.html")
  notice("--------------------------------------------------------------------------")

  debug("this is debug. visible only with -d or --debug")
  info("this is info. visible only with -v or --verbose or -d or --debug")
  alert("this is alert. always visible")
  crit("this is crit. always visible")
  emerg("this is emerg. always visible")
  err("this is err. always visible")
  warning("and this is warning. always visible")
  notice("this is notice. always visible")
  #fail will break execution
  fail("this is fail. always visible. fail will break execution process")

}


different log levels colors

Strings🔗

Operations can be performed on strings using Puppet’s builtin functionalities. For example

  • strip: trim whitespace left and right (including newline characters)
  • l/rstrip: trim whitespace left or right

Most of these also work within variable substitution ${var}, such as ${var.strip}.

Data and Parameters: Facter and Hiera🔗

Hiera, data and Puppet code: your path to the right data decisions @Puppet blog

Facter🔗

Facter | Custom facts

Puppet uses Facter to collect various information on the system and provides them as “facts”.

Facter is Puppet’s cross-platform system profiling library. It discovers and reports per-node facts, which are available in your Puppet manifests as variables.

Can be used to display OS and system information. To show data as available in Puppet, use puppet facts, e.g. $facts['os']['release']['major'].

1
FACTER_timeout=0 puppet agent -t
  • determine value of fact and debug
1
puppet facts --verbose --debug <your_fact>

Remco’s Ruby tutorial including Puppet facts

Hiera🔗

Documentation | Hiera config
Hiera works like a pyramid and the higher up a data layer is, the higher its priority. At the top is usually node-level data, that can overwrite profile/role/lower data such as defaults.

  • The main file for this is the file hiera.yaml in the base of the repository.
  • Hierarchy between modules is usually set in a file common.yaml, e.g. data/99_config.yaml. The section profile::config: contains the different sets of modules to be used in our different profiles
    • base: common to all profiles
    • server: modules for servers
  • The order of modules within one of these sets is important, if there are dependencies! For example a module sshd might use data from the module mfa and has thus to come after/below it
  • on top of that we might have node-level files (<node>.yaml), e.g. in data/00_hosts/, role-assignments by IP-ranges in data/10_roles as well as external node classifiers (ENCs, see next section).

External Node Classifiers (ENCs)🔗

Puppet documentation on ENCs

ENCs allow us to assign roles to machines by building, room, hostname, …

Lookup🔗

Hiera data can be accesses through a “look-up”, using the lookup function. A hiera data element in moduleA.yaml with content

1
moduleA::config: value

for example can be accessed in module B through lookup('moduleA::config.value'), see the Puppet documentation.

Template files🔗

Documentation | EPP | ERB
In a template file (within hiera/yaml) there are several ways

  • yaml style: aliased_key: "%{lookup('other_key.\"dotted.subkey\"')}"
  • .erb (ruby template) style: scope['mfa::enable']
  • .epp (puppet template) style: $mfa.enabl

Mind when puppet does the variable substitution though. To use hiera data within a template that is stored in hiera data again one might have to use @var. Also there’s a zoo of different ways to achieve the same here. Sometimes the syntax is $array['key'], in other contexts it might be $array[key] without ticks, etc.

Examples🔗

Work notes LWP: Puppet

Config🔗

Change default environment (branch): edit /etc/puppetlabs/puppet/puppet.conf

1
2
3
4
5
6
[main]
server = fqdn.puppet.master
ca_server = fqdn.puppet.ca

[agent]
environment = my_branch

Documentation🔗

Documentation of self-written modules can be automatically created if certain formats for comments and descriptions are used, see Puppet Strings.

Using Puppet Strings it is possible to automatically create Markdown documentation: requires certain format for module headers, then run puppet strings generate --format markdown

Modules, Puppet Forge🔗

Testing🔗

  • linting and syntax
    • puppet-lint
    • rake syntax: syntax check Puppet manifests and templates
    • puppet parser validate
    • yamllint: syntax, validity and style check for YAML files
  • rake: puppet module testing
    • list commands with rake -T (has to have Rakefile)
    • will ensure correct ruby gem versions, similar to/using(?) bundle
  • unit and integration
    • rspec-puppet
      • install: gem install rspec-puppet puppetlabs_spec_helper rspec-puppet-facts
      • for modules/classes created with PDK all necessary files and a basic test file should exist
      • run in module base directory: rspec spec/dir/module_name.rb
      • should be run using rake spec as other puppet testing facilities
    • beaker: puppet tool running code on a VM (vagrant, Docker, etc.)
  • acceptance and manual testing:
    • module-specific scripts
      • usually in _module_/files/tests, see e.g. icaclient
      • tests can be run by executing /var/tests/run_tests
    • beaker: puppet’s own
  • Puppet Development Kit (PDK)
    • pdk new module creates a new module in the style of puppet forge modules, that means a lot of stuff we don’t need
    • pdk new class creates a new (empty) class as a template in manifests/init.pp and spec/classes/bla_spec.rb
    • creates a file .travis.yaml for building with CI service Travis
      • works only with Github public repositories in the free version
      • Travis will request permissions from Github and once set up provide a pipeline for testing similar to GitLab etc.