Skip to content

The Puppet Language

OpenVox runs the Puppet language — the same declarative DSL described throughout this course. This chapter covers its core building blocks.

Variables

Variable names begin with a dollar sign ($) and can contain alphanumeric characters plus the underscore (_). Variable names are case-sensitive.

Variables

Constants

Variables can only be assigned a value once in a given scope — in reality, Puppet "variables" are actually constants.

No reassignment

Data types

Data types determine the kind of data that a variable can contain. The Puppet language is strongly typed. Common data types include:

  • String
  • Integer
  • Float
  • Numeric
  • Boolean
  • Array
  • Hash
  • Enum
  • Variant
  • Optional

Data types

Syntax

TypeName[modifier] $variable = value

As class parameters, data types have the following syntax:

DataType $variable_name = optional_default_value,

For example:

class test (
  String        $world_var       = 'World',
  Array[String] $packages        = ['vim', 'git'],
  Boolean       $enjoyable_class = true,
) {}

Note

Capitalization, spacing, and quoting are all very important!

String

A String is a text fragment of any length.

class test ( String $x = 'Strings are Neat' ) {}

Strings can be single- or double-quoted. Double-quoted strings allow for variable interpolation:

String $my_dog = 'Fido'
String $motd   = "Hello, ${world_var}"

String type

Regexp

A Regexp is a Ruby (PCRE-style) regular expression.

class test (
  Regexp $b = /b/,
) {}

Regexp type

Pattern

Pattern restricts String values to only those that match the provided Regexp value.

class test (
  Regexp      $b           = /b/,
  Pattern[$b] $x           = 'bob',
  Pattern[/\Aalice\Z/] $y  = 'alice',
) {}

Pattern type

Numbers

An Integer is a whole number:

class test ( Integer $x = 1337 ) {}

A Float is a floating-point number:

class test ( Float $x = 3.14 ) {}

The Numeric type can be either an Integer or a Float:

class test (
  Numeric $x = 1234,
  Numeric $y = 1.234
) {}

Number types

Boolean

A Boolean is a logical true or false value.

class test (
  Boolean $in_california    = true,
  Boolean $terrible_weather = false,
) { }

Boolean type

Array

An Array is a list of values.

class test (
  Array         $array1 = ['a',1,'b',2],
  Array[String] $array2 = ['a','b','c','d']
) {}

Array type

Hash

A Hash is a key-value data map.

class test (
  Hash[String, Boolean] $feature_flags = {
    'logging'    => true,
    'metrics'    => true,
    'debug_mode' => false,
    'beta_ui'    => false,
  }
) {}

Hash type

Enum

An Enum matches one of a range of strings.

class test (
  Enum['one', 'two', 'three'] $x = 'one',
) {}

Enum type

Variant

A Variant matches one of any listed types.

class test (
  Variant[String, Boolean] $string = 'neat',
  Variant[String, Boolean] $bool   = false
) {}

Variant type

Undef

An Undef variable can contain only the value undef.

Undef type

Optional

Optional is a modifier that allows undef as a value.

class test (
  Optional[String] $string = undef,
) {}

This is exactly equivalent to:

class test (
  Variant[String, Undef] $string = undef,
) {}

Optional type

Sensitive

The Sensitive type is used for data that should not be printed in logs or reports. Other data types can be converted to Sensitive using Sensitive.new().

class test (
  Sensitive $password = Sensitive.new("Don't print me!"),
) {}

When a Sensitive variable is converted to a String, its value will always be Sensitive [value redacted].

Sensitive type

Other types

Quoting strings

Double quotes

Double quotes (") allow for strings that contain variable interpolation or escape sequences. For example, variable interpolation:

$combined_string = "Hello ${myclass::world_var}"

The following escape sequences are available in double-quoted strings:

Sequence Result
\\ Single backslash
\n Newline
\r Carriage return
\t Tab
\s Space
\$ Literal dollar sign (to prevent interpolation)
\uXXXX Unicode character number XXXX (a four-digit hex number)
\u{XXXXXX} Unicode character XXXXXX (a hex number between two and six digits)
\" Literal double quote
\' Literal single quote

According to the Puppet Language Style Guide, double quotes should only be used for strings that contain variable interpolation or escape sequences.

Single quotes

Single quotes (') denote literal strings, with minor exceptions. The only escape sequences available in single-quoted strings are:

Sequence Result
\\ Single backslash
\' Literal single quote

Facts

Facts are available in Puppet code via the $facts hash.

$ puppet apply -e 'notify { "${facts[os][name]}": }'
Notice: Compiled catalog for node1.test in environment production in 0.02 seconds
Notice: Fedora
Notice: /Stage[main]/Main/Notify[Fedora]/message: defined 'message' as 'Fedora'
Notice: Applied catalog in 0.02 seconds

The data in the $facts hash is the same data returned by the facter command.

Facts and built-in variables

Originally, facts were available via top-scope variables:

$ puppet apply -e 'notify { "${os[name]}": }'
Notice: Compiled catalog for node1.test in environment production in 0.02 seconds
Notice: Fedora
Notice: /Stage[main]/Main/Notify[Fedora]/message: defined 'message' as 'Fedora'
Notice: Applied catalog in 0.02 seconds

Warning

While top-scope fact variables still work and are in common use, they should be avoided in favor of the $facts hash.

Conditionals

The Puppet language supports four types of conditionals:

  • if
  • unless
  • case
  • Selectors

if

if conditionally executes blocks of code. It can be combined with elsif ("else if") and else to build more complex logic.

if $facts['is_virtual'] {
  # Our NTP module is not supported on virtual machines:
  warning('Tried to include class ntp on virtual machine; this node might be misclassified.')
}
elsif $facts['os']['name'] == 'Darwin' {
  warning('This NTP module does not yet work on our Mac laptops.')
}
else {
  # Normal node, include the class.
  include ntp
}

if statements

Try it yourself

Save the following in a file called test.pp and apply it with puppet apply test.pp:

if $facts['kernel'] == 'windows' {
  notify { 'How did this happen?': }
} else {
  notify { $facts['kernel']: }
}

unless

unless acts like a reversed if statement. It cannot contain elsif blocks, but it can include an else block.

unless $facts['kernel'] == 'windows' {
  notify { 'This is a Unix of some sort': }
}

unless statements

Try it yourself

Add the code above to a file and apply it with puppet apply.

case

Much like if, case executes a block of code based on a set of conditions.

case $facts['os']['name'] {
  'Solaris':           { include role::solaris } # Apply the solaris class
  'RedHat', 'CentOS':  { include role::redhat  } # Apply the redhat class
  /^(Debian|Ubuntu)$/: { include role::debian  } # Apply the debian class
  default:             { include role::generic } # Apply the generic class
}

case statements

Try it yourself

Add the following to a file and apply it with puppet apply:

case $facts['os']['name'] {
  'Solaris':           { notify { 'I am using Solaris!': } }
  'RedHat', 'CentOS':  { notify { 'I am using EL!': } }
  /^(Debian|Ubuntu)$/: { notify { 'I am using Debian/Ubuntu!': } }
  default:             { notify { 'This OS is probably hard to use...': } }
}

Selectors

Selectors are similar to a case statement, but they return a value instead of executing a code block.

$rootgroup = $facts['os']['family'] ? {
  'Solaris'          => 'wheel',
  /(Darwin|FreeBSD)/ => 'wheel',
  default            => 'root',
}

file { '/etc/passwd':
  ensure => file,
  owner  => 'root',
  group  => $rootgroup,
}

Selectors

Try it yourself

Add the following to a file and apply it with puppet apply as root:

$rootgroup = $facts['os']['family'] ? {
  'Solaris'          => 'wheel',
  /(Darwin|FreeBSD)/ => 'wheel',
  default            => 'root',
}

file { '/tmp/delete_me':
  ensure => file,
  owner  => 'root',
  group  => $rootgroup,
  mode   => '0640',
}

Functions

Functions perform an action or return a result. They typically take one or more parameters and can be used to modify values in a catalog. In a typical server/agent configuration, they are executed on the server.

Functions · Function reference

Functions can be called in either prefix style (where all arguments follow the function name) or chain style (where the first argument, followed by a ., precedes the function name):

name(arg1, arg2, ...)
arg1.name(arg2, ...)

For example, the following are equivalent:

include(lookup('classes'))
lookup('classes').include

Prefix calls · Chained calls

puppetlabs-stdlib

puppetlabs-stdlib is a standard library of resources for Puppet modules. It contains many popular functions, defined types, data types, and facts not included in the core Puppet language.

puppetlabs/stdlib on the Forge

YAML

A common way to represent data in Puppet modules is YAML, a data serialization format similar in many ways to JSON. YAML files used by OpenVox contain keys and values. The keys are always strings; the values can be any data type that exists in the Puppet language and can be represented in YAML. Keys and values are separated by a colon and one or more spaces (:).

YAML documents should always start with the following string:

---

Unlike JSON, YAML supports comments that start with #, but only (reliably) when the comment is the only thing on a line.

Strings

In YAML, strings are bare words, or can be quoted with ' or ":

key: value
"quoted key": 'quoted value'

Multi-line strings can be represented using quoted or unquoted strings with line breaks. Unquoted strings require that trailing lines are indented, but can have issues when : and # characters are used. This is the flow scalar format:

key: 'This is a long single-quoted
string value.'
key2: This is an even longer completely
  unquoted string value.

YAML also supports a block scalar format. Blocks begin with either > (to fold lines) or | (to keep line breaks), and an optional + (to keep trailing line breaks) or - (to drop the trailing line break):

key: >-
  This is a long
  string value.
  Linefeeds will be
  replaced with spaces,
  and the trailing
  linefeed will be chomped.
key2: |
  # This is a string that
  # will include linefeeds,
  # but will have the
  # indentation stripped.
  [myconfig]
  key = value

For a great way to visualize multi-line strings in YAML, see yaml-multiline.info.

Numeric values

Numeric values are unquoted decimal, hexadecimal, or octal integers, plus floating-point numbers:

decimal: 42
hex: 0x2A
octal: 052
float: 42.0

Booleans

Booleans are unquoted true or false values:

'Evaluates as true': true
'Evaluates as false': false

YAML allows many spellings (yes/no, on/off, and various capitalizations), but for maximum compatibility with Puppet code, true and false are the recommended values.

undef

In YAML, undef can be represented with ~ or null:

'nothing': null
'nada': ~

To OpenVox these technically mean "no value," which may mean that defaults from another data layer are used instead.

Arrays

Arrays are represented with indented lines beginning with -:

key:
  - 'array value 1'
  - 'array value 2'

Hashes

Hashes are represented with indented lines containing additional key/value pairs:

key:
  'hash key': 'hash value'
  'another key': 'another value'

Module data

Module data can be declared in two ways: Hiera data and functions.

Hiera data

Hiera in modules should be used for relatively static data, which might be determined by facts.

# ntp/hiera.yaml
---
version: 5
hierarchy:
  - name: "OS family"
    path: "os/%{facts.os.family}.yaml"

  - name: "common"
    path: "common.yaml"
# ntp/data/common.yaml
---
ntp::autoupdate: false
ntp::service_name: ntpd
# ntp/data/os/AIX.yaml
---
ntp::service_name: xntpd
# ntp/data/os/Debian.yaml
ntp::service_name: ntp

Functions

Functions can be used where logic is required to determine the values of data.

# ntp/functions/data.pp
function ntp::data() {
  $base_params = {
    'ntp::autoupdate'   => false,
    'ntp::service_name' => 'ntpd',
  }

  $os_params = case $facts['os']['family'] {
    'AIX': {
      { 'ntp::service_name' => 'xntpd' }
    }
    'Debian': {
      { 'ntp::service_name' => 'ntp' }
    }
    default: {
      {}
    }
  }

  # Merge the hashes and return a single hash.
  $base_params + $os_params
}