Iteration and loops

Looping and iteration features help you write more succinct code, and use data more effectively.

Iteration functions

Iteration features are implemented as functions that accept blocks of code (lambdas). When a lambda requires extra information, pass it to a function that provides the information and to evaluate the code, possibly multiple times. This differs from some other languages where looping constructs are special keywords. In the Puppet code, they're functions.

Tip: Iteration functions take an array or a hash as their main argument, and iterate over its values.
These 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 once, isolating it in its own local scope. It doesn’t iterate, but has a family resemblance to the iteration functions.

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>
For 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
See the  slice  and  reduce  documentation for information on how these functions handle parameters differently.

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.

For information about the syntax of function calls, see the functions documentation, or the lambdas documentation for information about the syntax of code blocks that you pass to functions.

Declaring resources

The focus of the Puppet language is declaring resources, so most people want to use iteration to declare many similar resources at once. 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
}