Iteration and loops

Use iteration and loops to write more succinct code, and use data more effectively.

Iteration functions

Instead of using loop keywords, the Puppet language uses iterative functions that accept blocks of code called lambdas.

Tip: Iteration functions take an array or a hash as their main argument, and iterate over its values.
Iterative functions accept a block of code and run it in a specific way:
  • each - Repeats a block of code a number of times, using a collection of values to provide different parameters each time.

  • slice - Repeats a block of code a number of times, using groups of values from a collection as parameters.

  • filter - Uses a block of code to transform a data structure by removing non-matching elements.

  • map - Uses a block of code to transform every value in a data structure.

  • reduce - Uses a block of code to create a new value, or data structure, by combining values from a provided data structure.

  • with - Evaluates a block of code one time, isolating it in its own local scope. It doesn’t iterate, but has a family resemblance to the iteration functions.

See the slice and reduce documentation for information on how these functions handle parameters differently.

The each, filter, and map functions accept a lambda with either one or two parameters. Depending on the number of parameters, and the type of data structure you’re iterating over, the values passed into a lambda vary:
Collection type Single parameter Two parameters
Array <VALUE> <INDEX>, <VALUE>
Hash [<KEY>, <VALUE>] (two-element array) <KEY>, <VALUE>

Arrays:

This example:
['a','b','c'].each |Integer $index, String $value| { notice("${index} = ${value}") }
Results in:
Notice: Scope(Class[main]): 0 = a
Notice: Scope(Class[main]): 1 = b
Notice: Scope(Class[main]): 2 = c

2D Arrays:

This example:
$a = [['1', '2'], ['3', '4']]

$a.each |$array| {
  $array.each |$int| {
    notice($int)
  }
}
Results in:
Notice: Scope(Class[main]): 1
Notice: Scope(Class[main]): 2
Notice: Scope(Class[main]): 3
Notice: Scope(Class[main]): 4

Hashes:

This example:
$pets = {
  'pet1' => 'dog',
  'pet2' => 'cat',
  'pet3' => 'goldfish'}

$pets.each |$key, $value| {notice($value)}
Results in:
Notice: Scope(Class[main]): dog
Notice: Scope(Class[main]): cat
Notice: Scope(Class[main]): goldfish

Hashes preserve the order in which their keys and values were written. When iterating over a hash’s members, the loops occur in the order that they are written. When interpolating a hash into a string, the resulting string is also constructed in the same order.

Declaring resources

The focus of the Puppet language is declaring resources, so most people want to use iteration to declare many similar resources at the same time. In this example, there is an array of command names to be used in each symlink’s path and target. The each function makes this succinct.
$binaries = ['facter', 'hiera', 'mco', 'puppet', 'puppetserver']

$binaries.each |String $binary| {
  file {"/usr/bin/${binary}":
    ensure => link,
    target => "/opt/puppetlabs/bin/${binary}",
  }
}

Iteration with defined resource types

In previous versions of Puppet, iteration functions did not exist and lambdas weren’t supported. By writing defined resource types and using arrays as resource titles you could achieve a clunkier form of iteration.

Similar to the declaring resources example, include an unique defined resource type in the symlink.pp file:
define puppet::binary::symlink ($binary = $title) {
  file {"/usr/bin/${binary}":
    ensure => link,
    target => "/opt/puppetlabs/bin/${binary}",
  }
}
Use the defined type for the iteration somewhere ele in your manifest file:
$binaries = ['facter', 'hiera', 'mco', 'puppet', 'puppetserver']

puppet::binary::symlink { $binaries: }
The main problems with this approach are:
  • The block of code doing the work was separated from the place where you used it, which makes a simple task complicated.

  • Every type of thing to iterate over would require its own one-off defined type.

The current Puppet style of iteration is much improved, but you might encounter code that uses this old style, and might have to use it to target older versions of Puppet.

Using iteration to transform data

To transform data into more useful forms, use iteration. For example:

This returns [1,3]:
$filtered_array = [1,20,3].filter |$value| { $value < 10 }
This returns 6:
$sum = reduce([1,2,3]) |$result, $value|  { $result + $value }
This returns {"key1"=>"first value", "key2"=>"second value", "key3"=>"third value"}:
$hash_as_array = ['key1', 'first value',
                 'key2', 'second value',
                 'key3', 'third value']

$real_hash = $hash_as_array.slice(2).reduce( {} ) |Hash $memo, Array $pair| {
  $memo + $pair
}

Breaking out of the a loop

You can break out of a loop or skip the next iteration using the break() or next() functions respectively.