Jira (FACT-3111) Facter 3 and 4 handle external facts with nil values differently

12 views
Skip to first unread message

Josh Cooper (Jira)

unread,
Mar 30, 2022, 7:17:02 PM3/30/22
to puppe...@googlegroups.com
Josh Cooper updated an issue
 
Facter / Bug FACT-3111
Facter 3 and 4 handle external facts with nil values differently
Change By: Josh Cooper
Summary: Facter 3 and 4 handle external facts with value nil values differently
Add Comment Add Comment
 
This message was sent by Atlassian Jira (v8.20.2#820002-sha1:829506d)
Atlassian logo

Josh Cooper (Jira)

unread,
Mar 30, 2022, 7:24:02 PM3/30/22
to puppe...@googlegroups.com
Josh Cooper updated an issue
Facter 3 and 4 behave inconsistently with respect to fact values that return nil vs empty strings summarized as below:

|Fact Type|Facter 3|Facter 4|
|custom legacy|omitted|omitted|
|custom structured|nil|nil|
|external legacy|""|omitted|
|external structured|omitted|nil|

{{custom}} means the fact is defined using the Ruby API {{Facter.add}}
{{external}} means the fact is defined as a JSON data file
{{legacy}} means the fact returns a single value
{{structured}} means the fact's value is a Hash

Puppet always converts nil fact values to "", see PUP-11446, so the "external legacy" case means puppet 7 may omit a fact that was previously included when requesting a catalog. For example:

{noformat}
# puppet apply -e 'notice($external_legacy == undef)'
Notice: Scope(Class[main]): false
...
# puppet apply --facterng -e 'notice($external_legacy == undef)'
Warning: Unknown variable: 'external_legacy'. (line: 1, column: 8)
Notice: Scope(Class[main]): true
...
{noformat}

If you're using {{strict_variables}} then catalog compilation will fail (since the fact is missing):

{noformat}
# puppet apply --strict_variables --facterng -e 'notice($external_legacy == undef)'
Error: Evaluation Error: Unknown variable: 'external_legacy'. (line: 1, column: 8) on node
{noformat}

The "external structured" case has the opposite problem. Previously the fact was omitted and now it will be present with an empty value (due to Puppet converting nil to ""):

{noformat}
# puppet apply -e 'notice($facts["external_structured"].keys)'
Notice: Scope(Class[main]): [boolean, integer]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].keys)'
Notice: Scope(Class[main]): [null, integer, boolean]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].values)'
Notice: Scope(Class[main]): [, 42, false]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].values[0] =~ String)'
Notice: Scope(Class[main]): true
{noformat}

These might break puppet manifests that are not expecting to receive the "extra" key-value pair.

h3. Custom Legacy Facts

Facter 3 and 4 both omit legacy facts with nil values when collecting all facts {{
{} Facter.to_hash { }} } :

{noformat}
# cat custom/custom_legacy.rb
Facter.add(:custom_legacy) { setcode { nil } }
# /opt/puppetlabs/puppet/bin/facter -j --custom-dir /root/custom | grep custom_legacy
# /opt/puppetlabs/puppet/bin/facter-ng -j --custom-dir /root/custom | grep custom_legacy
#
{noformat}

One inconsistency is if you ask for the fact by name, Facter 3 returns a fact with an empty value, while Facter 4 returns nil:

{noformat}
# /opt/puppetlabs/puppet/bin/facter -j --custom-dir /root/custom custom_legacy
{
  "custom_legacy": ""
}
# /opt/puppetlabs/puppet/bin/facter-ng -j --custom-dir /root/custom custom_legacy
{
  "custom_legacy": null
}
{noformat}

h3. Custom Structured Facts

Facter 3 and 4 are consistent when a structured fact contains a fact whose value is nil (the fact is present with a nil value)

{noformat}
# cat custom/custom_structured.rb
Facter.add(:custom_structured) do
  setcode do
    {
      "null" => nil,
      "integer" => 42,
      "boolean" => false
    }
  end
end
# /opt/puppetlabs/puppet/bin/facter -j --custom-dir /root/custom custom_structured
{
  "custom_structured": {
    "null": null,
    "integer": 42,
    "boolean": false
  }
}
# /opt/puppetlabs/puppet/bin/facter-ng -j --custom-dir /root/custom custom_structured
{
  "custom_structured": {
    "boolean": false,
    "integer": 42,
    "null": null
  }
}
{noformat}

h3. External Legacy Facts

Facter 3 returns an empty string, while Facter 4 omits the fact:

{noformat}
# cat external/external_legacy.txt
external_legacy=
# /opt/puppetlabs/puppet/bin/facter -j --external-dir /root/external | grep external_legacy
  "external_legacy": "",
# /opt/puppetlabs/puppet/bin/facter-ng -j --external-dir /root/external | grep external_legacy
#
{noformat}

h3. External Structured Facts

Facter 3 omits structured facts whose values are nil, while Facter 4 returns the fact with a nil value:

{noformat}
# cat external/external_structured.json
{
  "external_structured": {
    "null": null,
    "integer": 42,
    "boolean": false
  }
}
# /opt/puppetlabs/puppet/bin/facter -j --external-dir /root/external external_structured
{
  "external_structured": {
    "boolean": false,
    "integer": 42
  }
}
# /opt/puppetlabs/puppet/bin/facter-ng -j --external-dir /root/external external_structured
{
  "external_structured": {
    "boolean": false,
    "integer": 42,
    "null": null
  }
}
{noformat}


h3. Summary

In the "external legacy" case, facter 3 returns a {{nil}} value which puppet converts to "", see PUP-11446, while facter 4 omits the fact. For example:

{noformat}
# puppet apply -e 'notice($external_legacy == undef)'
Notice: Scope(Class[main]): false
...
# puppet apply --facterng -e 'notice($external_legacy == undef)'
Warning: Unknown variable: 'external_legacy'. (line: 1, column: 8)
Notice: Scope(Class[main]): true
...
{noformat}

If you're using strict_variables then catalog compilation will fail (since the fact is missing):

{noformat}
# puppet apply --strict_variables --facterng -e 'notice($external_legacy == undef)'
Error: Evaluation Error: Unknown variable: 'external_legacy'. (line: 1, column: 8) on node
{noformat}

The "external structured" case has the opposite problem. Previously the fact was omitted and now it will be present with an empty value (due to Puppet converting nil to ""):

{noformat}
# puppet apply -e 'notice($facts["external_structured"].keys)'
Notice: Scope(Class[main]): [boolean, integer]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].keys)'
Notice: Scope(Class[main]): [null, integer, boolean]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].values)'
Notice: Scope(Class[main]): [, 42, false]
...
# puppet apply --facterng -e 'notice($facts["external_structured"].values[0] =~ String)'
Notice: Scope(Class[main]): true
{noformat}

These might break puppet manifests that are not expecting to receive the "extra" key-value pair.

Nirupama Mantha (Jira)

unread,
Apr 12, 2022, 4:23:02 PM4/12/22
to puppe...@googlegroups.com

Josh Cooper (Jira)

unread,
Jul 22, 2022, 12:14:03 PM7/22/22
to puppe...@googlegroups.com
Josh Cooper updated an issue
Change By: Josh Cooper
Component/s: Facter 4
This message was sent by Atlassian Jira (v8.20.11#820011-sha1:0629dd8)
Atlassian logo

Josh Cooper (Jira)

unread,
Jan 9, 2023, 12:55:02 PM1/9/23
to puppe...@googlegroups.com

Josh Cooper (Jira)

unread,
Jan 9, 2023, 3:04:01 PM1/9/23
to puppe...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages