Writing custom functions in the Puppet language
You can write simple custom functions in the Puppet language, to transform data and construct values. A function can optionally take one or more parameters as arguments. A function returns a calculated value from its final expression.
Syntax of functions
function <MODULE NAME>::<NAME>(<PARAMETER LIST>) >> <RETURN TYPE> {
... body of function ...
final expression, which is the returned value of the function
}
The general form of a function written in Puppet language is:
-
The keyword
function
. -
The namespace of the function. This must match the name of the module the function is contained in.
-
The namespace separator, a double colon
::
-
The name of the function.
-
An optional parameter list, which consists of:
-
An opening parenthesis
(
-
A comma-separated list of parameters (for example,
String $myparam = "default value"
). Each parameter consists of:-
An optional data type, which restricts the allowed values for the parameter (defaults to
Any
). -
A variable name to represent the parameter, including the
$
prefix. -
An optional equals sign
=
and default value, which must match the data type, if one was specified.
-
-
An optional trailing comma after the last parameter.
-
A closing parenthesis
)
-
-
An optional return type, which consists of:
-
Two greater-than signs
>>
-
A data type that matches every value the function could return.
-
-
An opening curly brace
{
-
A block of Puppet code, ending with an expression whose value is returned.
-
A closing curly brace
}
function apache::bool2http(Variant[String, Boolean] $arg) >> String {
case $arg {
false, undef, /(?i:false)/ : { 'Off' }
true, /(?i:true)/ : { 'On' }
default : { "$arg" }
}
}
Order and optional parameters
Puppet passes arguments by parameter position. This means that the order of parameters is important. Parameter names do not affect the order in which they are passed.
If a parameter has a default value, then it’s optional to pass a value for it when you're calling the function. If the caller doesn’t pass in an argument for that parameter, the function uses the default value. However, because parameters are passed by position, when you write the function, you must list optional parameters after all required parameters. If you put a required parameter after an optional one, it causes an evaluation error.
Variables in default parameters values
If you reference a variable as a default value for a parameter, Puppet starts looking for that variable at top scope. For
example, if you use $fqdn
as a variable, but then call the
function from a class that overrides $fqdn
, the parameter’s
default value is the value from top scope, not the value from the class. You can reference
qualified variable names in a function default value, but compilation fails if that class
isn't declared by the time the function is called.
The extra arguments parameter
You can specify that a function's last parameter is an extra arguments parameter. The extra arguments parameter collects an unlimited number of extra arguments into an array. This is useful when you don’t know in advance how many arguments the caller provides.
To specify that the parameter must collect extra arguments, start its name with an asterisk
*
, for example *$others
.
The asterisk is valid only for the last parameter.
-
If the provided default is a non-array value, the real default is a single-element array containing that value.
-
If the provided default is an array, the real default is that array.
If there are no extra arguments and there is no default value, it's an empty array.
An extra arguments parameter can also have a data type. Puppet uses this data type to validate the elements of the array. That is, if you specify a data
type of String
, the real data type of the extra arguments
parameter is Array[String]
.
Return types
>>
and a data type to specify the type of the values the function returns.
For example, this function only returns
strings:function apache::bool2http(Variant[String, Boolean] $arg) >> String {
...
}
The return type serves two purposes: -
Documentation. Puppet Strings includes information about the return value of a function.
-
Insurance. If something goes wrong and your function returns the wrong type (such as
undef
when a string is expected), it fails early with an informative error instead of allowing compilation to continue with an incorrect value.
The function body
In the function body, put the code required to compute the return value you want, given the arguments passed in. Avoid declaring resources in the body of your function. If you want to create resources based on inputs, use defined types instead.
The final expression in the function body determines the value that the function returns
when called. Most conditional expressions in the Puppet
language have values that work in a similar way, so you can use an if
statement or a case
statement as the final
expression to give different values based on different numbers or types of inputs. In the
following example, the case statement serves as both the body of the function, and its final
expression.
function apache::bool2http(Variant[String, Boolean] $arg) >> String {
case $arg {
false, undef, /(?i:false)/ : { 'Off' }
true, /(?i:true)/ : { 'On' }
default : { "$arg" }
}
}
Locations
Store the functions you write in a module's functions
folder, which is a top-level directory (a sibling of manifests
and lib
). Define only one function per
file, and name the file to match the name of the function being defined. Puppet is automatically aware of functions in a valid module
and autoloads them by name.
Avoid storing functions in the main manifest. Functions in the main manifest override any function of the same name in all modules (except built-in functions).
Names
Give your function a name that clearly reveals what it does. For more information about names, including restrictions and reserved words, see Puppet naming conventions.
Related topics: Arrays, Classes, Data types, Conditional expressions, Defined types, Namespaces and autoloading, Variables.
Calling a function
A call to a custom function behaves the same as a call to any built-in Puppet function, and resolves to the function's returned value.
After a function is written and available in a module where the autoloader can find it, you can call that function, either from a Puppet manifest that lists the containing module as a dependency, or from your main manifest.
Any arguments you pass to the function are mapped to the parameters defined in the function’s definition. You must pass arguments for the mandatory parameters, but you can choose whether you want to pass in arguments for optional parameters.
Functions are autoloaded and are available to other modules unless those modules have
specified dependencies. If a module has a list of dependencies in its metadata.json
file, only custom functions from those specific dependencies are
loaded.
Related topics: namespaces and autoloading, module metadata, main manifest directory
Complex example of a function
postgresql
module into Puppet code. This function translates the IPv4 and IPv6 Access
Control Lists (ACLs) format into a resource suitable for create_resources
. In this case, the filename would be acls_to_resource_hash.pp
, and it would be saved in a folder named functions
in the top-level directory of the postgresql
module.function postgresql::acls_to_resource_hash(Array $acls, String $id, Integer $offset) {
$func_name = "postgresql::acls_to_resources_hash()"
# The final hash is constructed as an array of individual hashes
# (using the map function), the result of that
# gets merged at the end (using reduce).
#
$resources = $acls.map |$index, $acl| {
$parts = $acl.split('\s+')
unless $parts =~ Array[Data, 4] {
fail("${func_name}: acl line $index does not have enough parts")
}
# build each entry in the final hash
$resource = { "postgresql class generated rule ${id} ${index}" =>
# The first part is the same for all entries
{
'type' => $parts[0],
'database' => $parts[1],
'user' => $parts[2],
'order' => sprintf("'%03d'", $offset + $index)
}
# The rest depends on if first part is 'local',
# the length of the parts, and the value in $parts[4].
# Using a deep matching case expression is a good way
# to untangle if-then-else spaghetti.
#
# The conditional part is merged with the common part
# using '+' and the case expression results in a hash
#
+
case [$parts[0], $parts, $parts[4]] {
['local', Array[Data, 5], default] : {
{ 'auth_method' => $parts[3],
'auth_option' => $parts[4, -1].join(" ")
}
}
['local', default, default] : {
{ 'auth_method' => $parts[3] }
}
[default, Array[Data, 7], /^\d/] : {
{ 'address' => "${parts[3]} ${parts[4]}",
'auth_method' => $parts[5],
'auth_option' => $parts[6, -1].join(" ")
}
}
[default, default, /^\d/] : {
{ 'address' => "${parts[3]} ${parts[4]}",
'auth_method' => $parts[5]
}
}
[default, Array[Data, 6], default] : {
{ 'address' => $parts[3],
'auth_method' => $parts[4],
'auth_option' => $parts[5, -1].join(" ")
}
}
[default, default, default] : {
{ 'address' => $parts[3],
'auth_method' => $parts[4]
}
}
}
}
$resource
}
# Merge the individual resource hashes into one
$resources.reduce({}) |$result, $resource| { $result + $resource }
}