
Creating Puppet 4 functions
The Puppet 3 functions API has some limitations and is missing features. The new function API in Puppet 4 improves upon that substantially.
Some of the limitations of the old functions are as follows:
- The functions had no automatic type checking
- These functions had to have a unique name due to a flat namespace
- These functions were not private and hence could be used anywhere
- The documentation could not be retrieved without running the Ruby code
Running on Puppet 3 requires functions to be in a module in the lib/puppet/parser/functions
directory. Therefore, people referred to these functions as parser functions. But this name is misleading. Functions are unrelated to the Puppet parser.
In Puppet 4, functions have to be put into a module in path lib/puppet/functions
.
This is how you create a function that will return the hostname of the Puppet Master:
# modules/utils/lib/puppet/functions/resolver.rb require 'socket' Puppet::Functions.create_function(:resolver) do def resolver() Socket.gethostname end end
Using dispatch
adds type checking for attributes. Depending on desired functionality, one might have multiple dispatch
blocks (checking for different data types). Each dispatch
can refer to another defined Ruby method inside the function. This reference is possible by using the same names for dispatch
and the Ruby method.
The following example code should get additional functionality; depending on the type of argument, the function should either return the hostname of the local system, or use DNS to get the hostname from an IPv4 address or the ipaddress
for a given hostname:
require 'resolv' require 'socket' Puppet::Functions.create_function(:resolver) do dispatch :ip_param do param 'Pattern[/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/]', :ip end dispatch :fqdn_param do param 'Pattern[/^([a-z0-9\.].*$/]', :fdqn end dispatch :no_param do end def no_param Socket.gethostname end def ip_param(ip) Resolv.getname(ip) end def fqdn_param(fqdn) Resolv.getaddress(fqdn) end end
At the beginning of the file, we have to load some Ruby modules to allow the DNS name resolution and finding the local hostname.
The first two dispatch
sections check for the data type of the parameter value and set a unique symbol. The last dispatch
section does not check for data types, which matches when no parameter was given.
Each defined Ruby method uses the name of the according dispatch
and executes Ruby code depending on the parameter type.
Now the resolver function can be used from inside the Puppet manifest code in three different ways:
class resolver { $localname = resolver() notify { "Without argument resolver returns local hostname: ${localname}": } $remotename = resolver('puppetlabs.com') notify { "With argument puppetlabs.com: ${remotename}": } $remoteip = resolver('8.8.8.8') notify { "With argument 8.8.8.8: ${remoteip}": } }
When declaring this class, the following output will show up:
puppet apply -e 'include resolver' Notice: Compiled catalog for puppetmaster.example.net in environment production in 0.35 seconds ... Notice: Without argument resolver returns local hostname: puppetmaster Notice: With argument puppetlabs.com: 52.10.10.141 Notice: With argument 8.8.8.8: google-public-dns-a.google.com Notice: Applied catalog in 0.04 seconds
With Puppet 3 functions, it was impossible to have two functions of the same name. One always had to check whether duplicate functions appeared when making use of a new module.
The Puppet 4 functions now offer the possibility of using namespacing just like classes.
Let's migrate our function into the class namespace:
# modules/utils/lib/puppet/functions/resolver/resolve.rb require 'resolv' require 'socket' Puppet::Functions.create_function(:'resolver::resolve') do # the rest of the function is identical to the example given # above end
In the example, the code needs to be in resolver/lib/puppet/functions/resolver/resolve.rb
which corresponds to function name: 'resolver::resolve'
.
Functions with namespaces are invoked as usual:
class resolver { $localname = resolver::resolve() $remotename = resolver::resolve('puppetlabs.com') $remoteip = resolver::resolve('8.8.8.8') }