Older Puppet versions supported a small set of data types only: Bool, String, Array, and Hash. The Puppet DSL had almost no functionality to check for consistent variable types. Consider the following scenario.
A parameterized class enables other users of your code base to change the behavior and output of the class:
This class definition checks whether the server parameter has been set to true. However, in this example, the class was not protected from wrong data usage:
In this class declaration, the server parameter has been given a string instead of a bool value. Since the false string is not empty, the if $server condition actually passes. This is not what the user will expect.
Within Puppet 3, it was recommended to add parameter validation using several functions from the stdlib module:
With one parameter only, this seems to be a good way. But what if you have many parameters? How do we deal with complex data types like hashes?
This is where the type system comes into play. The type system knows about many generic data types and follows patterns that are also used in many other modern programming languages.
Puppet differentiates between core data types and abstract data types. Core data types are the "real" data types, the ones which are mostly used in Puppet Code:
In the given example, the server parameter should be checked to always contain a bool value. The code can be simplified to the following pattern:
If the parameter is not given a Boolean value, Puppet will throw an error, explaining which parameter has a non-matching data type:
The error displayed is as follows:
The Numeric, Float, and Integer data types have some more interesting aspects when it comes to variables and their type.
Puppet will automatically recognize Integers, consisting of numbers (either with or without the minus sign) and not having a decimal point.
Floats are recognized by the decimal point. When doing arithmetic algebra on a combination of an Integer and a Float, the result will always be a Float.
Floats between -1 and 1 must be written with a leading 0 digit prior to the decimal point; otherwise, Puppet will throw an error.
Besides this, Puppet has support for the decimal, octal, and hexadecimal notation, as known from C-like languages:
A nonzero decimal must not start with a 0
Octal values must start with a leading 0
Hexadecimal values have 0x as the prefix
Puppet will automatically convert numbers into strings during the interpolation: ("Value of number: ${number}").
Note
Puppet will not convert strings to numbers. To make this happen, you can simply add 0 to a string to convert:
The Default data type is a little special. It does not directly refer to a data type, but can be used in selectors and case statements:
Abstract data types are constructs that are useful for a more sophisticated or permissive Type checking:
Scalar
Collection
Variant
Data
Pattern
Enum
Tuple
Struct
Optional
Catalogentry
Type
Any
Callable
Assume that a parameter will only accept strings from a limited set. Only checking for being of type String is not sufficient. In this scenario, the Enum type is useful, for which a list of valid values are specified:
If the listen parameter is not set to one of the listed elements, Puppet will throw an error:
The following error is displayed:
Sometimes, it is difficult to use specific data types, because the parameter might be set to an undef value. Think of a userlist parameter that might be empty (undef) or set to an arbitrary array of strings.
This is what the Optional type is for:
Again, using a wrong data type will lead to a Puppet error:
The error displayed is as follows:
In the previous example, we used a data type composition. This means that data types can have more information for type checking.
Let's assume that we want to set the ssh service port in our class. Normally, ssh should run on a privileged port between 1 and 1023. In this case, we can restrict the Integer Data Type to only allow numbers between 1 and 1023 by passing additional information:
As always, providing a wrong parameter will lead to an error:
The preceding line of code gives the following error:
Complex hashes that use multiple data types are very complicated to describe using the new type system.
When using the Hash type, it is only possible to check for a hash in general, or for a hash with keys of a specific type. You can optionally verify the minimum and maximum number of elements in the hash.
The following example provides a working hash type check:
Notably, the home entry for user jones is missing the leading slash:
Running the preceding code, gives us the following output:
With the preceding notation, the data type is valid. Yet there are errors inside the Hash map.
Checking content of Arrays or Hashes requires the use of another abstract data type: Tuple (used for Arrays) or Struct (used for Hashes).
However, the Struct data type will work only when the key names are from a known limited set, which is not the case in the given example.
In this special case, we have two possibilities:
Extend the hash data type to know about the hash internal structure
Wrap the type data into a define, which makes use of all keys using the key function (from stdlib)
The first solution is as follows:
However, the error message is hard to understand when the data types are not matching:
The second solution gives a smarter hint on which data might be wrong:
This defined type is then employed from within the users class:
With the wrong submitted data in the hash, you will receive the following error message:
The error message is pointing to the home parameter of the user jones, which is given in the hash.
The correct hash is as follows:
The preceding code produces the expected result as follows:
The preceding manifest uses the each function, another part of the Puppet 4 language. The next section explores it in greater detail.