Puppet Functions is simply the ability to write functions in Puppet.
FunctionDefinition
: 'function' name = NAME parameters = ParameterList? return_type = ReturnType? '{' statements += statements '}'
;
ParameterList
: '(' parameters += Parameter ')'
;
Parameter
: type=Expression<Type>? captures_rest='*'? name=QualifiedName ('=' value = Expression)?
;
ReturnType
: '>>' Expression<Type>
;
The Parameter
definition is the same as for Lambda.
- Only
FunctionDefinition
andLambda
can usecaptures_rest
in theirParameterList
since they use arguments passed 'by position'. - A
captures_rest
parameter (if used), must be placed last in the list - A parameter with a default value can not be placed after one that that does not have one.
- A default expression may refer to parameters defined in a parameter that is to the left of it. (once PUP-1985 is implemented - undefined otherwise)
- The default expressions are evaluated in the function's closure scope (i.e. global scope), with
the exception of parameters defined in the function list, such that:
- it is an error to refer to a (non '::' anchored) variable name that appears as a parameter to the right, even if it could be resolved to a globally available value.
- A
captures_rest
parameter will always receive an Array. - A
captures_rest
parameter's default can be given as a single value, or anArray
. If a single value is given, it is automatically wrapped in anArray
. - The type of the
captures_rest
parameter is either a non-Array type which becomes the element type of each captured element, or an Array Type that may cap the number or entries as well as specifying the element type. As a consequence, to capture a sequence ofArray[T]
, this must be specified asArray[Array[T]]
. - Since 4.7.0 it is possible to specify the expected return type of the function. When it is specified an automatic type assertion will be made against the value produced by the function.
Discussion:
The above rules for captures_rest
are motivated by the thought that the most common use is
not to pass multiple arrays, and it is more convenient to write foo(String *$rest)
then to have to write foo(Array[String] *$rest)
, since the *
already implies Array
. The consequence is when using that shorthand notation is that an array of arrays must be written Array[Array[T]]
- which is expected to be far more uncommon, than either capping the list
(e.g. foo(Array[String,1,10] *$rest)
, or passing a variable number of arrays.
See Parameter Scope for more information about scoping rules an variable access in a default value expression.
Functions written in the Puppet Language does not support lambdas / code blocks. While it is
possible to call a puppet function with a lambda, and possibly also type a parameter as accepting a Callable
parameter, this can not be put to any practical use since there is no support for calling the given block. It expected that this will be supported in a later version of this specification. Until then, the behavior of calling a puppet function with a code block is undefined.
Namespaced Puppet Functions are auto loaded from modules and the environment when located under /functions or /functions respectively.
Auto loaded puppet functions should always be namespaced; in a module, it's required to use the module name. In an environment, it's highly recommended to use the special name environment
(i.e. not the name of the environment since that typically changes as code is being developed, tested, put into production and then maintained, etc.).
The name of the .pp file must match the simple (non namespaced name part) of the function. Thus, a function 'testmodule::min' in module 'testmodule' is located like this:
testmodule
|- functions
| min.pp
With the following contents in min.pp
(note use of full namespace):
function testmodule::min($a, $b) {
# ...
}
And a function 'environment::min' in a 'production' environment like this:
production
|- functions
|- environment
| min.pp
With the following contents in min.pp
(note use of namespace 'environment'):
function environment::min($a, $b) {
# ...
}
Nested name spaces are allowed in both modules and the environment - e.g. a function 'testmodule::math::min' would be located like this:
testmodule
|- functions
|- math
| min.pp
With the following contents in min.pp
(note use of full namespace):
function testmodule::math::min($a, $b) {
# ...
}
And a a function 'environment::math::min' would be located like this:
production
|- functions
|- environment
|- math
| min.pp
With the following contents in min.pp
(note use of full namespace):
function environment::math::min($a, $b) {
# ...
}
In an environment, it is only possible to declare functions in the global namespace (not recommended) or in the special namespace 'environment'. Functions added in other namespaces will not be found. E.g. declaring function 'someother::min' like this will not make it visible to the auto loader:
production
|- functions
|- someother
| min.pp
Rules:
- Loading is performed by mapping the fully qualified function name to a 4.x Ruby function path, and a puppet function path, then:
- if the ruby path exist this 4.x Ruby function is loaded (and search stops)
- if the puppet path exists this Puppet function is loaded (and search stops)
- last, an attempt is made to load a 3.x Ruby function
- Files containing an auto loaded function may only contain a single function (or an error is raised and evaluation stops).
Note:
Function loading only searches a set of distinct paths based on the fully qualified name of the function. Contrast this with other kinds of automatic loading of classes and user defined resource types where a search is made using a widening of the namespace, and finally reaching a modules
manifests/init.pp
.Specifically: Autoloading a function from a module does not trigger loading of the module's
manifests/init.pp
(nor is such initialization required to call a function from a module). If an author of a module provides functions that require that the module'smanifests/init.pp
is loaded, the function should include the module's class, or require that the caller first includes the module's class).Specifically: a file
init.pp
under thefunctions
directory of a module or the environment does not have any special rules associated with it. If that file exists it is supposed to contain a function named<module>::init
. Contrast this withmanifests/init.pp
which represents the module it is in. There is no such concept for functions.
It is possible to define a Puppet Function in any manifest. Such functions will come into existence when the manifest in question is loaded for some reason other than calling the function (e.g. from 'manifests/site.pp' or when including a class).
The following restrictions/rules/conventions apply on naming non-auto-loaded functions:
- A function defined in a module must be qualified with the module's namespace
- A function defined in an environment's main manifest should begin with the special namespace 'environment'
- except when patching of a function is required - then the name may shadow other functions defined in the same environment, or the modules in this environment. Functions provided by the puppet runtime cannot be shadowed. A shadowed function cannot be called.
Note that the term "environment's main manifest" means logic loaded from the command line (apply -e
), the Puppet setting code
, the code loaded from the setting manifest
(e.g. the manifests/site.pp
file, or a directory of manifests).
The use cases for using functions defined in manifests are:
- Defining several helper functions that are used locally in a class/user defined type. (Although not yet provided in the language, these functions would typically be made
private
to the module). - For patching:
- To define a single word (non name-spaced) function (not recommended in general, useful for integrating code that needs such a function and where the original function's implementation is flawed/unwanted)
- To override/shadow functions in modules that are flawed/unwanted.
- To experiment during development
Note: In the current implementation of Puppet 4.3.x the naming restrictions on non-auto-loaded functions are not enforced. They are expected to be enforced in some future release.