rubychef-infraruby-hash

Ruby Hash Not Populating


I am working in Chef, trying to create/populate a ruby hash with networking device information, as populated by nmcli. I think the code is correct, as VS Code isn't complaining, and it seems to run just fine in chef-shell -z but I'm not able to query the Ruby Hash as I would expect, and I'm really starting to lose my mind.

Fresh eyes and any expert help here are appreciated, thank you!

interfaces = Hash.new
#DEVICE,TYPE
dev = Mixlib::ShellOut.new("nmcli -terse -field device,type device").run_command.stdout.split(/\n/)
dev.each do |output|
  if "#{output.split(":")[1]}" == 'ethernet'
    interfaces["ethernet" => "#{output.split(":")[0]}"]
  elsif "#{output.split(":")[1]}" == 'wifi'
    interfaces["wifi" => "#{output.split(":")[0]}"]
  else
    Chef::Log.debug("Interface #{output.split(":")} is not supported")
  end
end
chef (17.6.18)>  
 => ["wlp61s0:wifi", "enp0s31f6:ethernet", "lo:loopback"] 
node[interfaces] #nil
node[:interfaces] #nil
node['interfaces'] #nil
node["interfaces"] #nil

When I attempt to edit the code, as suggested by BroiSatse

This line interfaces["wifi" => "#{output.split(":")[0]}"] returns the value stored in the hash under the key {"wifi" => "#>{output.split(":")[0]}"}. It does not perform any assignment and most > likely returns nil.

What you need is:

interfaces["wifi"] ="#{output.split(":")[0]}"

So I tried that, but I still get a nil response from the Hash. Here is the Chef output/error:

chef (17.6.18)> interfaces = Hash.new
chef > #DEVICE,TYPE
chef (17.6.18)> dev = Mixlib::ShellOut.new("nmcli -terse -field device,type device").run_command.stdout.split(/\n/)
 => ["wlp61s0:wifi", "enp0s31f6:ethernet", "lo:loopback"] 
chef > dev.each do |output|
chef >   if "#{output.split(":")[1]}" == 'ethernet'
chef >     interfaces["ethernet"] = "#{output.split(":")[0]}"
chef >   elsif "#{output.split(":")[1]}" == 'wifi'
chef >     interfaces["wifi"] = "#{output.split(":")[0]}"
chef >   else
chef >     Chef::Log.debug("Interface #{output.split(":")} is not supported")
chef >   end
chef (17.6.18)> end
 => ["wlp61s0:wifi", "enp0s31f6:ethernet", "lo:loopback"] 
chef (17.6.18)> node[interfaces] #nil
 => nil 
chef (17.6.18)> node[:interfaces][:ethernet] #nil
(irb):95:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
    from /opt/chef/embedded/lib/ruby/gems/3.0.0/gems/chef-17.6.18/lib/chef/shell.rb:93:in `block in start'
    from /opt/chef/embedded/lib/ruby/gems/3.0.0/gems/chef-17.6.18/lib/chef/shell.rb:92:in `catch'
    from /opt/chef/embedded/lib/ruby/gems/3.0.0/gems/chef-17.6.18/lib/chef/shell.rb:92:in `start'
    from /opt/chef/embedded/lib/ruby/gems/3.0.0/gems/chef-bin-17.6.18/bin/chef-shell:31:in `<top (required)>'
    from /usr/bin/chef-shell:158:in `load'
    from /usr/bin/chef-shell:158:in `<main>'
chef (17.6.18)> node['interfaces'] #nil
chef (17.6.18)> node["interfaces"] #nil
 => nil 
chef (17.6.18)> 

UPDATE: Monday May 2, 2022 12:08 PST

When I do this command I can see that there is data in the Hash ... but all attempts to actually query the data fail ... I don't know what I'm doing wrong:

chef (17.6.18)> puts "#{interfaces}"
{"wifi"=>"wlp61s0", "ethernet"=>"enp0s31f6"}
 => nil 
chef (17.6.18)> 

Solution

  • Since you are running this in Chef, you can use automatic attributes collected by Ohai, instead of running command to get its stdout.

    Ohai is a tool for collecting system configuration data, which it then provides to Chef Infra Client to use in cookbooks.

    Comprehensive details about the machine's network configuration are stored under node['network']['interfaces'] hash.

    Try this in your recipe to see what details are captured:

    pp node['network']['interfaces']
    

    We can use these automatic attributes in combination with the select filter to get the network device names without looping.

    interfaces = Hash.new
    
    # exclude any devices that don't have a "type", such as loopback
    ifaces = node['network']['interfaces'].select { |k, v| ! v['type'].nil? }
    
    # select device names for devices of type "en" (ethernet)
    interfaces['ethernet'] = ifaces.select { |k, v| v['type'].match(/^en/) }.keys.first
    
    # select device names for devices of type "wl" (wireless/wifi)
    interfaces['wifi'] = ifaces.select { |k, v| v['type'].match(/^wl/) }.keys.first
    

    The above code will get the "first" device that matches en or wl respectively. If there are multiple devices of a type, then .first can be removed and the entire list of devices will be available.