Handling 'null' value

41 views
Skip to first unread message

Guy St-Denis

unread,
May 20, 2015, 12:43:51 PM5/20/15
to help-c...@googlegroups.com
Hi,

I'm new to CFEngine and I'm trying to understand how to use it properly.

One problem that I am running into right now is how to handle the special 'null' value in a JSON object (and the data container that is generated from it).
See https://dev.cfengine.com/issues/7194 for a typical scenario...

I tried searching the documentation, this forum, the issue tracker, Diego's book, Google, the actual source code on GitHub... nothing jumped out at me. Sorry...

I would appreciate any suggestions or links to relevant material that could clear things up.

Cheers!
--
Guy

P.S. Dealing with 'null' in general with CFEngine is still mysterious... I have seen 'cf_null', but I haven't figured out how to use it reliably, or if it is even relevant with data containers. 

Ted Zlatanov

unread,
May 20, 2015, 2:07:28 PM5/20/15
to help-c...@googlegroups.com
On Wed, 20 May 2015 09:43:51 -0700 (PDT) Guy St-Denis <stde...@gmail.com> wrote:

GS> I'm new to CFEngine and I'm trying to understand how to use it properly.

GS> One problem that I am running into right now is how to handle the special '
GS> *null*' value in a JSON object (and the data container that is generated
GS> from it).
GS> See https://dev.cfengine.com/issues/7194 for a typical scenario...

CFEngine scalar variables (string, int, real) can't hold a null value.
It wouldn't work in any part of the policy language and the evaluator
has no idea what to do with such values. But Mustache templates can use
them and the underlying JSON objects have no problem with nulls.

Unfortunately changing the evaluator is risky so there has to be a
reason (why would we want to use nulls, and how are they useful?) and a
very clear spec (what should nulls do, and how will we deal with them in
legacy code?). Do you want to try answering those questions?

Ted

Guy St-Denis

unread,
May 20, 2015, 2:34:08 PM5/20/15
to help-c...@googlegroups.com
Thanks for replying.

To be clear, at this stage I am just trying to understand how CFEngine behaves; I am not suggesting that changes are required.

In issue #7194 that I submitted, there is some behavior that isn't well-defined (perhaps based on my current ignorance...) when the 'null' value is encountered.

So, how can I detect the 'null' value when it is encountered in a JSON object?

This seems terribly basic, so once again excuse me if I have missed the explanation somewhere... :-)
--
Guy



Ted Zlatanov

unread,
May 20, 2015, 3:41:54 PM5/20/15
to help-c...@googlegroups.com
On Wed, 20 May 2015 11:34:08 -0700 (PDT) Guy St-Denis <stde...@gmail.com> wrote:

GS> On Wednesday, May 20, 2015 at 2:07:28 PM UTC-4, Ted Zlatanov wrote:

>> CFEngine scalar variables (string, int, real) can't hold a null value.
>> It wouldn't work in any part of the policy language and the evaluator
>> has no idea what to do with such values. But Mustache templates can use
>> them and the underlying JSON objects have no problem with nulls.
>>
>> Unfortunately changing the evaluator is risky so there has to be a
>> reason (why would we want to use nulls, and how are they useful?) and a
>> very clear spec (what should nulls do, and how will we deal with them in
>> legacy code?). Do you want to try answering those questions?

GS> Thanks for replying.

GS> To be clear, at this stage I am just trying to understand how CFEngine
GS> behaves; I am not suggesting that changes are required.

Of course.

GS> In issue #7194 that I submitted, there is some behavior that isn't
GS> well-defined (perhaps based on my current ignorance...) when the 'null'
GS> value is encountered.

I agree :)

GS> So, how can I detect the 'null' value when it is encountered in a JSON
GS> object?

You can't currently. But my questions were:

* why do you want to detect null?
* how are they useful in the CFEngine context?
* what should they do?
* how to deal with them in legacy code?

The last question is more for experienced policy writers, but for the
first three, your perspective is definitely valuable.

GS> This seems terribly basic, so once again excuse me if I have missed the
GS> explanation somewhere... :-)

3.6 was the first version with data containers. I don't think this issue
has come up since it was released. That's partly why I'm asking the
questions above, to understand why you're exploring the behavior with
nulls and how it can be changed or explained for the better.

Thanks
Ted

Guy St-Denis

unread,
May 20, 2015, 5:58:57 PM5/20/15
to help-c...@googlegroups.com
Ok, here are some answers :-)

* why do you want to detect null? 
'null', 'true' and 'false' are special values in JSON. If they can be used to convey information, we need to be able to detect them; otherwise, there is loss of information.  

* how are they useful in the CFEngine context? 
The strategy I am developing uses a JSON file to specify the configuration of a host. The use of 'null' in this file can indicate that a particular configuration task does not apply, so skip the task entirely.

* what should they do?  
At this point, I would just be happy to correctly and reliably detect them. Once detected, I can decide what should happen next. Upon further investigation, I found the 'nth' function (https://docs.cfengine.com/latest/reference-functions-nth.html) that appears to be good enough for my purposes right now. I have updated my issue report with more test cases using the 'nth' function in case that may be of use to someone (https://dev.cfengine.com/issues/7194).

I hope that helps. If not, let me know.

Thanks!
--
Guy

Aleksey Tsalolikhin

unread,
May 20, 2015, 7:44:06 PM5/20/15
to help-c...@googlegroups.com
On Wed, May 20, 2015 at 12:41 PM, Ted Zlatanov <t...@lifelogs.com> wrote:

3.6 was the first version with data containers. I don't think this issue
has come up since it was released.  

I ran into this issue -- I'm integrating with a tool that feeds CFEngine JSON data,
and the data had nulls. 

I asked the developer to modify the tool to always provide CFEngine 
sensible defaults (for example, if the JSON contained file metadata 
and the file mode was not specified, default to 644 instead of saying 
the mode is null) and he did so.

--
Need CFEngine training?  Email trai...@verticalsysadmin.com

Ted Zlatanov

unread,
May 20, 2015, 11:30:37 PM5/20/15
to help-c...@googlegroups.com
On Wed, 20 May 2015 14:58:57 -0700 (PDT) Guy St-Denis <stde...@gmail.com> wrote:

GS> * why do you want to detect null?
GS> 'null', 'true' and 'false' are special values in JSON. If they can be used
GS> to convey information, we need to be able to detect them; otherwise, there
GS> is loss of information.

Right, but there is nothing equivalent in CFEngine's data types (classes
are *not* booleans), so we need a reason to have this conversion. It
seems like detecting them can be useful as a way to communicate
knowledge, so perhaps it makes sense to simply recognize them for
classes:

classes:
"isnull" expression => datatype_isnull("container[path]");
"istrue" expression => datatype_istrue("container[path]");
"isfalse" expression => datatype_isfalse("container[path]");

This can get very complicated, though. Consider the rich confusion of
Javascript with its several types of truth (see
https://stackoverflow.com/questions/801032/why-is-null-an-object-and-whats-the-difference-between-null-and-undefined
http://www.smashingmagazine.com/2011/05/30/10-oddities-and-secrets-about-javascript/
and many other fun places). There has to be a practical use for this
information, not just abstract inspection of data values.

GS> * how are they useful in the CFEngine context?
GS> The strategy I am developing uses a JSON file to specify the configuration
GS> of a host. The use of 'null' in this file can indicate that a particular
GS> configuration task does not apply, so skip the task entirely.

OK, that makes sense, though usually I would use an empty array []
instead of null to denote "no actions apply." So when you iterate over
a null value, it should be skipped? Is that not the case right now?
Let's check:

#+begin_src cfengine3
bundle agent main
{
vars:
"items" data => '[ "x", "y", null ]';

reports:
"we have item $(items)";
}
#+end_src

#+begin_src text
% cf-agent -KI -f ./test_null.cf
R: we have item x
R: we have item y
#+end_src

So at least iteration works like you'd expect... can you show the
example that didn't?

GS> * what should they do?
GS> At this point, I would just be happy to correctly and reliably detect them.
GS> Once detected, I can decide what should happen next. Upon further
GS> investigation, I found the 'nth' function
GS> (https://docs.cfengine.com/latest/reference-functions-nth.html) that
GS> appears to be *good enough* for my purposes right now. I have updated my
GS> issue report with more test cases using the 'nth' function in case that may
GS> be of use to someone (https://dev.cfengine.com/issues/7194).

Hmm, that's not ideal, though, as you explain in the comments.

Actually almost exactly what you're describing already existed and was
removed from 3.6 because we couldn't find a use for it. The datatype()
function could actually tell you if a JSON element was boolean, null, a
string, etc.

Check out core.git:

commit f251521594a02a39b8998c3478d9851d46030c9b
Author: Ted Zlatanov <t...@lifelogs.com>
Date: Thu Feb 6 09:57:23 2014 -0500

libpromises/evalfunction.c: disable datatype() pending discussion

The code is still in there, so re-enabling it is a matter of proving to
the developers that there's a practical utility to the function. From
the discussion so far, it seems that it would be useful.

On Wed, 20 May 2015 16:43:25 -0700 Aleksey Tsalolikhin <atsalo...@gmail.com> wrote:

AT> I ran into this issue -- I'm integrating with a tool that feeds
AT> CFEngine JSON data, and the data had nulls.

AT> I asked the developer to modify the tool to always provide CFEngine
AT> sensible defaults (for example, if the JSON contained file metadata
AT> and the file mode was not specified, default to 644 instead of saying
AT> the mode is null) and he did so.

Aha, so in your case null means "don't use this attribute." That can be
done with Mustache templates but not in policy currently. The datatype()
function plus some convenience tests like I suggested above might do the
trick. Do you agree?

If so, speak up in https://dev.cfengine.com/issues/7194 and here and let
the developers know. For what it's worth, I'm in favor of re-enabling
datatype(). Because it was already working fine before it was disabled,
maybe it could even make it into 3.7 despite the feature freeze (but the
convenience tests definitely wouldn't).

Ted

Guy St-Denis

unread,
May 21, 2015, 9:45:04 AM5/21/15
to help-c...@googlegroups.com
Right, but there is nothing equivalent in CFEngine's data types (classes 
are *not* booleans), so we need a reason to have this conversion.  It 
seems like detecting them can be useful as a way to communicate 
knowledge, so perhaps it makes sense to simply recognize them for 
classes: 

    classes: 
      "isnull" expression => datatype_isnull("container[path]"); 
      "istrue" expression => datatype_istrue("container[path]"); 
      "isfalse" expression => datatype_isfalse("container[path]"); 

This approach may not be necessary when there is a "CFEngine Way" workaround (e.g., empty list)... but I (as a neophyte) naively expected a technology that "parses JSON" to provide me with the tools to detect this information. 
 

OK, that makes sense, though usually I would use an empty array [] 
instead of null to denote "no actions apply."  So when you iterate over 
a null value, it should be skipped?  Is that not the case right now? 

In my case, I was attempting to do something like this:

{
 
"PACKAGE": [
   
{"name":"packageX", "version":"1.2", ..., "files": null, "special":null},
   {"name":"packageY", "version":"3.4", ..., "files": [{"src":"confY1.dat","dst":"/etc/Y/confY1.dat"}], "special":null },
   
{"name":"packageZ", "version":"5.6", ..., "files": [{"src":"confZ1.dat","dst":"/etc/Z/confZ1.dat"}, {"src":"confZ2.txt","dst":"/etc/Z/confZ2.txt"}], "special":"packageZ.cf" }
 
]
}

  • This configuration file has a "PACKAGE" section, which is a list of packages to install.
  • Each package configuration object has number of attributes: "name", "version", ..., "files" and "special".
  • "files" is a list of (source, destination) files to be copied from the HUB to the HOST.
  • "special" is the name of a file that contains additional setup instructions for this package
In my strategy, I was using null to indicate that there is no "files" or "special" step to carry out, bypassing entirely the call to either the handleFiles() and handleSpecial() sub-bundles.

<sidenote>
The fundamental problem may be due to my inexperience with "The CFEngine Way". 
But from a newbie's perspective, I have been bumping into many of these quirks that just impede productivity because they send me down paths of debugging non-intuitive behavior.
At this point, it's still very difficult for me to look at some CFEngine code and be able to predict the outcome based on the documentation alone... that will come with experience, I'm sure, but it can be discouraging for newcomers.

In a case like this, for example, I have encountered unexpected results:

"my_x" string => nth(my_container,"x");
"my_x" string => "$(my_container[x])";


I have demonstrated here https://dev.cfengine.com/issues/7194 that they currently are not.

If "The CFEngine Way" requires that I should use an empty list or array instead of 'null', then that fact should be documented clearly. 
When I see something that claims to support some format (e.g., JSON), I expect to be able to use the full feature set of that format.
</sidenote>

To conclude, I just want to stress that I am offering these comments as a datapoint from a newbie's perspective to help the CFEngine community; I hope no one takes offense. :-)
I recognize the hard work and effort it takes to make great technology and I appreciate everyone's time and dedication to this project.

Thanks!
--
Guy

Brian Bennett

unread,
May 21, 2015, 12:28:20 PM5/21/15
to help-c...@googlegroups.com
This, to me looks like a very serious bug.

If I'm getting json from an external source it may legitimately have null values in it, which is out of my control. Having left over values in a field from a previous record can have serious and dangerous consequences. This is akin to using uninitialized memory in C, and just being OK with whatever happened to be preexisting in that memory space.

In any case, this is unacceptable. I appreciate that this may be a difficult problem, that doesn't make it any less egregious of a bug.

As to what Cfengine *should* do in this case, I would expect the reasonable behavior to be the equivalent of a Cfengine uninitialized variable, e.g, $(json.path.that.is.null). It's not great, but it's something that we are already accustomed to dealing with. But what is NOT ok is having a value that looks valid, but isn't.

--
Brian Bennett
Looking for CFEngine training?
http://www.verticalsysadmin.com/
> --
> You received this message because you are subscribed to the Google Groups "help-cfengine" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to help-cfengin...@googlegroups.com.
> To post to this group, send email to help-c...@googlegroups.com.
> Visit this group at http://groups.google.com/group/help-cfengine.
> For more options, visit https://groups.google.com/d/optout.

Ted Zlatanov

unread,
May 28, 2015, 1:20:05 PM5/28/15
to help-c...@googlegroups.com
On Thu, 21 May 2015 09:28:14 -0700 Brian Bennett <brian....@verticalsysadmin.com> wrote:

BB> As to what Cfengine *should* do in this case, I would expect the
BB> reasonable behavior to be the equivalent of a Cfengine uninitialized
BB> variable, e.g, $(json.path.that.is.null). It's not great, but it's
BB> something that we are already accustomed to dealing with. But what is
BB> NOT ok is having a value that looks valid, but isn't.

My suggestion again is to make it an empty slist because the variable is
indeed initialized.

Ted

Marty Gehrke

unread,
Jun 2, 2015, 8:57:08 AM6/2/15
to help-c...@googlegroups.com
I am still trying to learn the more nuanced parts of CFEngine. Ted, can you explain in more detail what you mean when you say classes are NOT Booleans.

The documentation claims they are.

https://docs.cfengine.com/docs/master/guide-language-concepts.html
"Classes: CFEngine's boolean classifiers that describe context."


-----Original Message-----
From: help-c...@googlegroups.com [mailto:help-c...@googlegroups.com] On Behalf Of Ted Zlatanov
Sent: Wednesday, May 20, 2015 11:30 PM
To: help-c...@googlegroups.com
Subject: [help-cfengine] Re: Handling 'null' value

On Wed, 20 May 2015 14:58:57 -0700 (PDT) Guy St-Denis <stde...@gmail.com> wrote:

GS> * why do you want to detect null?
GS> 'null', 'true' and 'false' are special values in JSON. If they can
GS> be used to convey information, we need to be able to detect them;
GS> otherwise, there is loss of information.

Right, but there is nothing equivalent in CFEngine's data types (classes are *not* booleans), so we need a reason to have this conversion. It seems like detecting them can be useful as a way to communicate knowledge, so perhaps it makes sense to simply recognize them for
classes:

classes:
"isnull" expression => datatype_isnull("container[path]");
"istrue" expression => datatype_istrue("container[path]");
"isfalse" expression => datatype_isfalse("container[path]");

This can get very complicated, though. Consider the rich confusion of Javascript with its several types of truth (see https://stackoverflow.com/questions/801032/why-is-null-an-object-and-whats-the-difference-between-null-and-undefined
http://www.smashingmagazine.com/2011/05/30/10-oddities-and-secrets-about-javascript/
and many other fun places). There has to be a practical use for this information, not just abstract inspection of data values.

GS> * how are they useful in the CFEngine context?
GS> The strategy I am developing uses a JSON file to specify the
GS> configuration of a host. The use of 'null' in this file can indicate
GS> that a particular configuration task does not apply, so skip the task entirely.

OK, that makes sense, though usually I would use an empty array [] instead of null to denote "no actions apply." So when you iterate over a null value, it should be skipped? Is that not the case right now?
Let's check:

#+begin_src cfengine3
bundle agent main
{
vars:
"items" data => '[ "x", "y", null ]';

reports:
"we have item $(items)";
}
#+end_src

#+begin_src text
% cf-agent -KI -f ./test_null.cf
R: we have item x
R: we have item y
#+end_src

So at least iteration works like you'd expect... can you show the example that didn't?

GS> * what should they do?
GS> At this point, I would just be happy to correctly and reliably detect them.
GS> Once detected, I can decide what should happen next. Upon further
GS> investigation, I found the 'nth' function
GS> (https://docs.cfengine.com/latest/reference-functions-nth.html) that
GS> appears to be *good enough* for my purposes right now. I have
GS> updated my issue report with more test cases using the 'nth'
GS> function in case that may be of use to someone (https://dev.cfengine.com/issues/7194).

Hmm, that's not ideal, though, as you explain in the comments.

Actually almost exactly what you're describing already existed and was removed from 3.6 because we couldn't find a use for it. The datatype() function could actually tell you if a JSON element was boolean, null, a string, etc.

Check out core.git:

commit f251521594a02a39b8998c3478d9851d46030c9b
Author: Ted Zlatanov <t...@lifelogs.com>
Date: Thu Feb 6 09:57:23 2014 -0500

libpromises/evalfunction.c: disable datatype() pending discussion

The code is still in there, so re-enabling it is a matter of proving to the developers that there's a practical utility to the function. From the discussion so far, it seems that it would be useful.

On Wed, 20 May 2015 16:43:25 -0700 Aleksey Tsalolikhin <atsalo...@gmail.com> wrote:

AT> I ran into this issue -- I'm integrating with a tool that feeds
AT> CFEngine JSON data, and the data had nulls.

AT> I asked the developer to modify the tool to always provide CFEngine
AT> sensible defaults (for example, if the JSON contained file metadata
AT> and the file mode was not specified, default to 644 instead of
AT> saying the mode is null) and he did so.

Aha, so in your case null means "don't use this attribute." That can be done with Mustache templates but not in policy currently. The datatype() function plus some convenience tests like I suggested above might do the trick. Do you agree?

If so, speak up in https://dev.cfengine.com/issues/7194 and here and let the developers know. For what it's worth, I'm in favor of re-enabling datatype(). Because it was already working fine before it was disabled, maybe it could even make it into 3.7 despite the feature freeze (but the convenience tests definitely wouldn't).

Ted

Ted Zlatanov

unread,
Jun 2, 2015, 11:18:23 AM6/2/15
to help-c...@googlegroups.com
On Tue, 2 Jun 2015 12:57:04 +0000 Marty Gehrke <Martin...@twosigma.com> wrote:

MG> I am still trying to learn the more nuanced parts of CFEngine. Ted, can you explain in more detail what you mean when you say classes are NOT Booleans.
MG> The documentation claims they are.

MG> https://docs.cfengine.com/docs/master/guide-language-concepts.html
MG> "Classes: CFEngine's boolean classifiers that describe context."

I think the statement you quote means "boolean" in the sense of Boolean
algebra.

I meant classes are not "boolean" variables (in context, I was talking
about data types, and in CFEngine only variables have a data type).
Classes can only exist or not exist, they represent the presence or
absence of knowledge about the system.

Ted

Reply all
Reply to author
Forward
0 new messages