0

I have working Ruby code to query DNS details and create Puppet custom facts (puppet 5, Facter 3.11.6) however I am trying to modify it to create nested facts from the key/value pairs that the query obtains.

Code that works to set individual facts with the key name is:

  require 'resolv'
  Resolv::DNS::Config.default_config_hash.each do | key, value |
    if !value.nil?
      Facter.add("dns_#{key}") do
        if value.is_a?(Array)
          setcode { value.join(',') }
        else
          setcode { value }
        end
      end
    end
  end

which creates individual facts thus:

dns_nameserver => 192.168.1.1,192.168.1.2
dns_ndots => 1
dns_search => test.domain

My failed attempt so far to create a nested fact under the parent fact of 'DNS' is:

require 'resolv'
Facter.add("dns") do
  value ={}
  Resolv::DNS::Config.default_config_hash.each do | key, result |
    if !result.nil?
      if result.is_a?(Array)
        setcode { value['#{key}'] = result.join(',') }
      else
        setcode { value['#{key}'] = result }
      end
    end
  end
end

which gives a limited result of just:

dns => 1

Other code I have tried seems to put an array output into the string and multiple IPs are quoted inside square brackets over 2 lines instead of being output as per the first code block at top of page.

The fact structure I am TRYING to achieve (by modifying the top of page code) is:

dns => {
  nameserver => 192.168.1.1,192.168.1.2,
  ndots => 1,
  search => test.domain,
}

Thanks in advance for any assistance.

xit
  • 135
  • 1
  • 12

1 Answers1

0

I finally got this with the assistance from a poster who put some great code leads here, but unfortunately removed it soon afterward. Here is the code that works:

require 'resolv'
Facter.add(:networking_dns) do
  setcode do
    Resolv::DNS::Config.default_config_hash.each_with_object({}) do | (key, value), sub|
      if !value.nil?
        sub[key] = value
        sub
      end
    end
  end
end

Now for some explanatory notes (please feel free to correct me or offer any optimisations to this):

# the resolv gem is required
require 'resolv'
# create the parent fact (has no value of its own)
Facter.add(:networking_dns) do
# start building instructions in the fact
  setcode do
# use the resolv gem to lookup values in /etc/resolv.conf and add .each to process all key/value pairs returned
# also add _with_object({}) and sub in the variables to set a blank value for sub.  Saves doing it separately.  Sub can be any name but denotes the declaration for the nested facts
    Resolv::DNS::Config.default_config_hash.each_with_object({}) do | (key, value), sub|
# create facts only when the value is not nil
      if !value.nil?
        sub[key] = value
        sub
# using a closing blank entry for a nested fact is critical or they won't create!  Place this outside of the case statement to prevent blank values
      end
    end
  end
end
# use the appropriate number of ends and indent for readability

Thanks to the person who posted their guidance here before removing it. I would like to upvote you if you post again.

Any tips on optimisation to the able solution are welcome, as I'm still grasping Ruby (spent hours on this!)

xit
  • 135
  • 1
  • 12
  • You should move the return for `sub` outside of the case, otherwise this fact will return `nil` if the final object is a `NilClass`. – Matthew Schuchard Jan 04 '19 at 11:36
  • Thanks Matt, could you please offer a code block as I'm not quite sure where you mean to move it. BTW, this fact works for both Windows and Linux which is awesome. – xit Jan 04 '19 at 11:46
  • 1
    It needs to be after the `end` for the `case` statement. – Matthew Schuchard Jan 04 '19 at 11:49
  • thanks. I'm also curious if this fact can be appended to the built-in networking facts, to appear as: networking { dns => { nameserver => 192.168.1.1,192.168.1.2, ndots => 1, search => test.domain, } Unsure if built-in facts can be appended to... – xit Jan 04 '19 at 12:03
  • You would need a writeable attribute for the core Facter keys. I doubt `Facter.value` is writeable, but I have not looked at the source recently. It is also bound to Ruby from C++14 so there is that interception possibility also. – Matthew Schuchard Jan 04 '19 at 12:10
  • I moved the return sub outside the case statement as suggested and agree that is best practice, however the nameserver still returns a blank value if not present or no value in /etc/resolv.conf. Not a big deal as actually that is desired output in this particular case (notify of a nil value). I can't see an easy way to append the built-in networking parent fact so have renamed it networking_dns to maintain clarity and ordering. Thanks again! – xit Jan 05 '19 at 00:53
  • Edited the above solution as arrays need to remain intact to allow value selectors such as [0] or [1] . This then made the case statement redundant so re-added the check for nil values. – xit Jan 07 '19 at 07:18