Rahul Rahul - 1 month ago 4
Ruby Question

Ruby undefined method `[]' for nil:NilClass

I'm trying to make nagios check for BBU (Battery Backup Unit), I have this ruby script called

check_bbu


#!/usr/bin/ruby
require 'json'

output = %x{/usr/sbin/storcli /c0/bbu show j}

begin
j = JSON.parse(output)
result = j["Controllers"][0]["Command Status"]["Status"]
status = j["Controllers"][0]["Response Data"]["BBU_Info"][0]["State"]
rescue Exception => e
puts "CRITICAL: error reading BBU status: #{e}"
exit 2
end

if result != 'Success'
puts "CRITICAL: command not successful, result: #{result}"
exit 2
end

if status != 'Optimal'
puts "CRITICAL: BBU not optimal, status is #{status}"
exit 2
end

puts "OK: BBU is optimal"


But when I run this plugin I'm getting following error,

$ ./check_nrpe -u -t 30 -H foo.example.com -c check_bbu
CRITICAL: error reading BBU status: undefined method `[]' for nil:NilClass


What am I doing wrong in this ?

Answer

For example to my comment, lets imagine that we have correct JSON:

j = {"Controllers" => [{"Comand Status" => {"status" => 'ok'}}]}
=> {"Controllers"=>[{"Comand Status"=>{"status"=>"ok"}}]} 

so by typing your first line, it's should return correct result:

> j["Controllers"][0]["Comand Status"]["status"]
=> "ok" 

But also you can get wrong JSON, for example:

j_e = {"Controllers" => []}

so now it's return error:

>j_e["Controllers"][0]["Comand Status"]["status"]
NoMethodError: undefined method `[]' for nil:NilClass

to solve it, you can use something like:

def try data, keys # there we take arguments: data == j, and keys == array with keys
  return data if data.nil? # there we check: if data, that we send == nil, for example j = nil, array = ['first', etc..] we should stop this method and return nil as result
  value = data[keys.shift] # on this line we try to get some data from j ##keys.shift will delete first element from array that we send to this method and return as `key` for `data`, for example data['Controllers'] so keys now will looks like: [0, 'Comand Status', 'status']
  unless keys.empty? #on this line we check if on the pred line we was used the last key and keys now looks like: [] we: *return value and if it's not empty we just **call this method 1 more time
    try(value, keys) #**value = [{"Comand Status" => {"status" => 'ok'}}] and keys = [0, 'Comand Status', 'status']
  else
    value #*nil or value
  end
end



j = {"Controllers"=>[{"Comand Status"=>{"status"=>"ok"}}]}
try(j, ['Controllers', 0, 'Comand Status', 'status'])
>'ok'
try j, ['Controllers', 1, 'Comand Status', 'status']
> nil

in you code this should looks like:

require 'json'

def try data, keys
  return data if data.nil?
  value = data[keys.shift]  
  unless keys.empty?
    try(value, keys)
  else
    value
  end
end


output = %x{/usr/sbin/storcli /c0/bbu show j}
begin
  j = JSON.parse(output)
  result = try(j, ["Controllers", 0, "Command Status", "Status"])
  status = try(j, ["Controllers", 0, "Response Data", "BBU_Info", 0, "State"])
rescue Exception => e
  puts "CRITICAL: error reading BBU status: #{e}"
  exit 2
end

if result != 'Success'
  puts "CRITICAL: command not successful, result: #{result}"
  exit 2
end

if status != 'Optimal'
  puts "CRITICAL: BBU not optimal, status is #{status}"
  exit 2
end

puts "OK: BBU is optimal"

Also, for Ruby 2.3.0+

it's much easier, just:

j.dig("Controllers", 0, "Comand Status", "status")