More about undef
In my earlier blogpost Let’s talk about undef I covered the data type Undef
itself. In this blog post I am going to cover what happens when you use undef
in puppet manifests and in Ruby.
Over time the Puppet Language undef
has been represented internally in different ways. Starting with Puppet 4 (and with future parser in Puppet 3) the compiler (i.e. the puppet language) represents undef
as the Ruby nil
value.
However, the resource API and the older so called 3.x function API required nil
to be transformed to other values for backwards compatibility.
Undef and Functions
Puppet has two function APIs, the so called 3x (old) and 4x (since Puppet 3 with future parser).
Here is how the transformations are done for a function call to function f()
in puppet and what the resulting Ruby values given to the function are in the 3x and 4x APIs:
puppet | 3x API < 6.0.0 | 3x API >= 6.0.0 | 4x API |
---|---|---|---|
f(undef) |
'' |
'' |
nil |
f([undef]) |
[:undef] |
[nil] |
[nil] |
f({undef => undef}) |
{:undef => :undef}] |
{nil => nil} |
{nil => nil} |
As you can see from the table above:
- In the 3x API (in all Puppet versions) a top level value of
undef
is translated to the empty string. - In versions before 6.0.0, an
undef
nested (at any depth) inside anArray
orHash
is translated to the RubySymbol
:undef
. - From Puppet 6.0.0 nested
undef
in the 3x API is handled the same was as for the 4x API - using the Ruby runtime valuenil
.
Returned values
Returning values from 4.x functions works as you expect; anything set to nil
is semantically the same as Puppet’s undef
. This is however royally screwed up when it comes to 3x functions! Since they are given the transformed undef
values (in the form of either empty string or symbol :undef
, they could return such values back to the compiler. And thus, the compiler may end up feeding values encoded that way to 4x functions - thus exposing the 4x functions to the 3x API encoding.
The compiler treats the :undef
symbol as if it was nil
in terms of type checking and it will also be serialized as if it was a nil
, but since there is no transformation going on for 4x functions they were exposed to this problem.
From Puppet 5.5.7 all returned values from 3x functions are subject to a transformation such that the :undef
symbol is transformed back to nil
.
Recommended Actions for 3x functions
For 3x functions that you maintain, the recommendation is to change them to use the 4x API since that makes it sane; you get nil
for Puppet’s undef
and you return nil
when you want to return undef
values - and it all works in harmony with Ruby. (And if you have special processing of :undef
you can remove that in favor or straight forward detection/removal of nil
in Ruby). Although it is an action you need to take, it is quite easy to change a 3x to 4x function.
If you for some reason cannot do that, and you want to maintain your function as a 3x function supporting both old and new versions you should treat both :undef
and nil
as being nil
- which means you may need to do operations twice. You also need to make sure you are not returning structures with :undef
in them if function is used with any puppet version >= Puppet 3 with future parser <= Puppet 5.5.7.
Undef and Resources
Giving values to resource is almost like giving values to functions, but not quite. A given top-level undef
in the resource API means “I want the default value” - that is, it acts as if you had not given a value at all!
# Given this definition:
define myresource($param = 'green') { }
# These two will have exactly the same effect on param1
# setting it to the value 'green' in both resources
myresource { 'title1': param1 => undef }
myresource { 'title2': }
If we change the default value expression to also be undef, we
will get the effect of not including a value at all for that
parameter. (That is, no null
value will appear in the JSON for the serialized catalog sent to the agent).
# Given this definition:
define myresource($param = undef) { }
# These two will have exactly the same effect on param1
# neither will have the param1 set at all
myresource { 'title1': param1 => undef }
myresource { 'title2': }
The (abbreviated) output in the catalog looks like this:
{
"type": "Myresource",
"title": "title1",
},
{
"type": "Myresource",
"title": "title2",
}
Getting Undef from Hiera
When Automatic Parameter Lookup (APL) is used for a class, it is possible to bind an undef
value (null
in JSON and YAML data files). When a value for a parameter looked up with APL results in undef
it will set the value of the parameter to undef
(in contrast to when giving it in a manifest since that means - “use the default”).
The rationale for this is that APL kicks in to get a default value and it is then not meaningful to also let it return a value that means “use the default”. Instead, the result of the default value expression for a class parameter only gets used if there was no value bound at all for that parameter in hiera.
class car($color = 'blue') { }
class { 'car': color => undef }
- If nothing was bound in hiera, this would result in the car’s color being
'blue'
(the default expression kicks in) - If
car::color
was bound to ‘green’ in hiera, the result would be a'green'
car. - If
car::color
was bound toundef
in hiera the result would be anundef
color.
Recommended actions for Resources
If you want to accept a parameter value of undef
then declare a default value expression of undef
. Then you can either get that default by not specifying a parameter value (or giving undef
, which is the same thing), or you can bind undef
in hiera and all gives you the same result.
Something like this:
class car(Optional[String] $color = undef) { }
Summary
I hope this has provided you with some of the (otherwise) hard to find details about how undef
actually works in different Puppet versions. (I am also a bit sad that something like this blog post is needed - but that is a different story).
Puppet 6.0.0 has an unfortunate bug that under certain conditions could replace :undef with empty space in nested structures. This is fixed in Puppet 6.0.1.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDelete