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.
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.
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>
|
['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
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.
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:
[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
}