The following changes occured in Puppet 4 and carried forward to Puppet 5.
Puppet 4/5 changes
Appending to and concatenating arrays
You can conca/home/test/puppet-beginners-guide-3/tenate arrays with the + operator or append them with the << operator. In the following example, we use the ternary operator to assign a specific package name to the $apache variable. We then append that value to an array using the << operator:
$apache = $::osfamily ? {
'Debian' => 'apache2',
'RedHat' => 'httpd'
}
$packages = ['memcached'] << $apache
package {$packages: ensure => installed}
If we have two arrays, we can use the + operator to concatenate the two arrays. In this example, we define an array of system administrators ($sysadmins) and another array of application owners ($appowners). We can then concatenate the array and use it as an argument to our allowed users:
$sysadmins = [ 'thomas','john','josko' ]
$appowners = [ 'mike', 'patty', 'erin' ]
$users = $sysadmins + $appowners
notice ($users)
When we apply this manifest, we see that the two arrays have been joined, as shown in the following command-line output:
t@cookbook:~$ puppet apply concat.pp
Notice: Scope(Class[main]): [thomas, john, josko, mike, patty, erin]
Merging hashes
If we have two hashes, we can merge them using the same + operator we used for arrays. Consider our $interfaces hash from a previous example; we can add another interface to the hash:
$iface = {'name' => 'eth0',
'ip' => '192.168.0.1',
'mac' => '52:54:00:4a:60:07' }
+ {'route' => '192.168.0.254'}
notice ($iface)
When we apply this manifest, we see that the route attribute has been merged into the hash (your results may differ; the order in which the hash prints is unpredictable), as follows:
t@cookbook:~$ puppet apply merge.pp
Notice: Scope(Class[main]): {name => eth0, ip => 192.168.0.1, mac => 52:54:00:4a:60:07, route => 192.168.0.254}
Using the sensitive type
It is often the case that you wish to store passwords or other credentials in Puppet. There are a few ways to do so with some level of security, such as eyaml or GPG. However, the unencrypted data may still be leaked via reports and logs. Starting in Puppet 4.6, a new sensitive type was created to address this problem. Data that is stored in a Sensitive type will not be leaked via reports or logs; when the value needs to be recorded it will be replaced with value redacted.
In the following example, we can see how we can output a password to a file but take advantage of the protections of the Sensitive type:
$secret = Sensitive('My Top Secret Password')
file {'/tmp/passwd':
content => "${secret.unwrap}\n",
}
notice($secret)
When we use puppet apply on this code, we see that the notice has the value redacted while the unwrapped value is stored in the file:
t@cookbook:~$ puppet apply sensitive.pp
Notice: Scope(Class[main]): Sensitive [value redacted]
Notice: Compiled catalog for cookbook.example.com in environment production in 0.01 seconds
Notice: /Stage[main]/Main/File[/tmp/passwd]/content: content changed '{md5}6d814ec03401f7954ed41306e8848a07' to '{md5}a4ca22adedac7912cbb7e53ccfba0a9d'
Notice: Applied catalog in 0.03 seconds
t@cookbook:~$ cat /tmp/passwd
My Top Secret Password
Lambda functions
Lambda functions are iterators applied to arrays or hashes. You iterate through the array or hash and apply an iterator function such as each, map, filter, reduce, or slice to each element of the array or key of the hash. Some of the lambda functions return a calculated array or value; others such as each only return the input array or hash.
Lambda functions such as map and reduce use temporary variables that are thrown away after the lambda has finished. Use of lambda functions is something best shown by example. In the next few sections, we will show an example usage of each of the lambda functions.
reduce
reduce is used to reduce the array to a single value. This can be used to calculate the maximum or minimum of the array, or in this case, the sum of the elements of the array:
$count = [1,2,3,4,5]
$sum = reduce($count) | $total, $i | { $total + $i }
notice("Sum is $sum")
This preceding code will compute the sum of the $count array and store it in the $sum variable, as follows:
t@cookbook:~$ puppet apply reduce.pp
Notice: Scope(Class[main]): Sum is 15
filter
filter is used to filter the array or hash based on a test within the lambda function. For instance, we filter our $count array as follows:
$count = [1,2,3,4,5]
$filter = filter ($count) | $i | { $i > 3 }
notice("Filtered array is $filter")
When we apply this manifest, we see that only elements 4 and 5 are in the result:
t@cookbook:~$ puppet apply filter.pp
Notice: Scope(Class[main]): Filtered array is [4, 5]
map
map is used to apply a function to each element of the array. For instance, if we wanted (for some unknown reason) to compute the square of all the elements of the array, we would use map as follows:
$count = [1,2,3,4,5]
$map = map ($count) | $i | { $i * $i }
notice("Square of array is ${map}")
The result of applying this manifest is a new array with every element of the original array squared (multiplied by itself), as shown in the following command-line output:
t@cookbook:~$ puppet apply map.pp
Notice: Scope(Class[main]): Square of array is [1, 4, 9, 16, 25]
slice
slice is useful when you have related values stored in the same array in a sequential order. For instance, if we had the destination and port information for a firewall in an array, we could split them up into pairs and perform operations on those pairs:
$firewall_rules = ['192.168.0.1','80',
'192.168.0.10','443']
slice ($firewall_rules,2) |$ip, $port| {
notice("Allow $ip on $port")
}
When applied, this manifest will produce the following notices:
t@cookbook:~$ puppet apply slice.pp
Notice: Scope(Class[main]): Allow 192.168.0.1 on 80
Notice: Scope(Class[main]): Allow 192.168.0.10 on 443
To make this a useful example, create a new firewall resource within the block of the slice instead of notice:
$firewall_rules = ['192.168.0.1','80',
'192.168.0.10','443']
slice ($firewall_rules,2) |$ip, $port| {
firewall { "$port from $ip":
dport => $port,
source => "$ip",
action => 'accept',
}
}
each
each is used to iterate over the elements of the array but lacks the ability to capture the results like the other functions. each is the simplest case where you simply wish to do something with each element of the array, as shown in the following code snippet:
each ($count) |$c| { notice($c) }
As expected, this executes the notice for each element of the $count array, as follows:
t@cookbook:~$ puppet apply each.pp
Notice: Scope(Class[main]): 1
Notice: Scope(Class[main]): 2
Functions in Puppet language
Starting from Puppet 4, you can write functions in the Puppet language instead of Ruby. It is believed at some point that Puppet will move away from using Ruby in the backend, so any new functions you write should use the Puppet language if possible. The interesting thing about writing functions in the Puppet language is that there is no return statement; Puppet will return the last expression from a function as the return value. It is still possible to do more advanced programming while remaining within Puppet language. In the following example, we will use a rough estimation algorithm to calculate the square root of a number.
The first function is used to determine if we are close to the square root:
function close(Integer $number, Float $guess) {
if abs(($guess * $guess) - $number ) < 0.01 {
true
} else {
guessAgain($number,$guess)
}
}
This will either return true if $guess2 is within 0.01 of $number or it will use the next guessAgain function to return the next guess to the main function:
function guessAgain($number, $guess) {
($guess + ($number / $guess )) / 2.0
}
This function is used to make a correction to the current guess, to bring the estimation closer to the square root. The last function is where we do our recursion; recursion is where a function uses itself as part of the algorithm:
function sqrt_($number, $guess) {
if close($number,$guess) == true {
$guess
} else {
sqrt_($number, guessAgain($number, $guess))
}
}
In this function, if the close function returns true, we know we are close enough to the root and can return $guess to the calling function. If not, we call sqrt_ again with a new guess. The final function is just a wrapper of sqrt_ and is used so the caller need not set an initial guess:
function sqrt($number) {
sqrt_($number,1.0)
}
To see this in action, we'll need to call our sqrt function and use notify to print the values returned:
function sqrt_($number, $guess) {
if close($number,$guess) == true {
$guess
} else {
sqrt_($number, guessAgain($number, $guess))
}
}
Now when we run Puppet, we see the following output:
Notice: sqrt(4) = 2.000609756097561
Notice: sqrt(2) = 1.4166666666666665
Notice: sqrt(81) = 9.000011298790216