Dump :node object in chef - ruby

I am a dev tasked with making some changes to the cookbooks owned by the devops team. We don't have access to knife or the chef server.
I tried to add some debugging to an existing chef recipe
members = search(
:node,
search_str,
filter_result: {
'name' => ['name'],
}
#
# print statements NOT working as expected
#
p ":node=" + :node.to_s # <--- prints ":node=node"
pp :node.to_s # <--- prints "node"
pp :node # <--- prints :node
Is it possible to dump the entire json structure of the :node object? Any workarounds. I want to see the structure of the :node object to debug the search() call as the search isn't working as expected.
Thank you.

:node in ruby is a symbol, not a variable. Symbols always start with colon (:). For starters you can think of them as of immutable strings. That is why you get such output. You just print a lot of strings.
What you actually need is just node:
pp "node:", node
But beware, node is a huge object with a lot of attributes.

Related

Creating nested Puppet fact (Ruby) by iterating over gem query output

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.
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!)

Redis-objects Ruby gem, how to retrieve Redis list and iterate?

I'm trying to use redis-objects Ruby gem to store some Redis data in lists.
I am able to create a list by following the example in the documentation.
I am able to find the list from Redis using lrange. Not sure if that is the best way, I couldn't find a method provided by redis-objects.
Initially when I iterate the elements in the list I get the elements in the form of Hashes.
However after I get the list using lrange those are not hashes and I cannot access the data.
What would be the appropriate way to find the list and get the items in hash form?
You can see the code below and the outputs from the console.
#list = Redis::List.new('list_name', :marshal => true)
#list << {:name => "Nate", :city => "San Diego"}
#list.each do |el|
puts el
puts el.class
puts "#{el[:name]} lives in #{el[:city]}"
end
redis = Redis.current
#list = redis.lrange("list_name", 0, -1)
#list.each do |el|
puts el
puts el.class
puts "#{el[:name]} lives in #{el[:city]}"
end
Each of the puts:
{:name=>"Nate", :city=>"San Diego"}
Hash
Nate lives in San Diego
{: nameI" Nate:ET: cityI"San Diego;T
String
Completed 500 Internal Server Error in 349ms
TypeError - no implicit conversion of Symbol into Integer:
Right. The text below, from the Gem documentation explains it!
There is a Ruby class that maps to each Redis type, with methods for
each Redis API command. Note that calling new does not imply it's
actually a "new" value - it just creates a mapping between that Ruby
object and the corresponding Redis data structure, which may already
exist on the redis-server.
So I don't need to use lrange to get to the list. Using Redis::List.new('list_name', :marshal => true) will get me a handle to the list. Then I can iterate, add or remove items from the list.
Reading is helpful...

How to read data from a different file without using YAML or JSON

I'm experimenting with a Ruby script that will add data to a Neo4j database using REST API. (Here's the tutorial with all the code if interested.)
The script works if I include the hash data structure in the initialize method but I would like to move the data into a different file so I can make changes to it separately using a different script.
I'm relatively new to Ruby. If I copy the following data structure into a separate file, is there a simple way to read it from my existing script when I call #data? I've heard one could do something with YAML or JSON (not familiar with how either work). What's the easiest way to read a file and how could I go about coding that?
#I want to copy this data into a different file and read it with my script when I call #data.
{
nodes:[
{:label=>"Person", :title=>"title_here", :name=>"name_here"}
]
}
And here is part of my code, it should be enough for the purposes of this question.
class RGraph
def initialize
#url = 'http://localhost:7474/db/data/cypher'
#If I put this hash structure into a different file, how do I make #data read that file?
#data = {
nodes:[
{:label=>"Person", :title=>"title_here", :name=>"name_here"}
]
}
end
#more code here... not relevant to question
def create_nodes
# Scan file, find each node and create it in Neo4j
#data.each do |key,value|
if key == :nodes
#data[key].each do |node| # Cycle through each node
next unless node.has_key?(:label) # Make sure this node has a label
#WE have sufficient data to create a node
label = node[:label]
attr = Hash.new
node.each do |k,v| # Hunt for additional attributes
next if k == :label # Don't create an attribute for "label"
attr[k] = v
end
create_node(label,attr)
end
end
end
end
rGraph = RGraph.new
rGraph.create_nodes
end
Given that OP said in comments "I'm not against using either of those", let's do it in YAML (which preserves the Ruby object structure best). Save it:
#data = {
nodes:[
{:label=>"Person", :title=>"title_here", :name=>"name_here"}
]
}
require 'yaml'
File.write('config.yaml', YAML.dump(#data))
This will create config.yaml:
---
:nodes:
- :label: Person
:title: title_here
:name: name_here
If you read it in, you get exactly what you saved:
require 'yaml'
#data = YAML.load(File.read('config.yaml'))
puts #data.inspect
# => {:nodes=>[{:label=>"Person", :title=>"title_here", :name=>"name_here"}]}

Cannot deserialize object from a JSON string (but only to Hash)?

I wrote the dictation gem on my Mac, and deserialization works fine. When I installed it on another Mac it would not work because it "fails" to deserialize object, because it can only deserialize to a Hash.
Private Mac Ruby version: ruby-1.9.3-p0, json v1.8.0
Another Mac Ruby version: ruby-1.9.3-p448, json v1.8.0
I also tried different Ruby versions and Gem versions on both, but none of them works, only the initial one where I first wrote it.
When I try this code in the working environment:
require 'json'
class Word
attr_accessor :value, :translation
def initialize(value, translation)
#value = value
#translation = translation
end
def to_json(*args)
{
'json_class' => self.class.name,
'data' => [ #value, #translation ]
}.to_json(*args)
end
class << self
def json_create(object)
new(*object['data'])
end
end
end
str = '{"json_class":"Word","data":["Morgen","Tomorrow"]}'
p JSON.parse(str)
It prints a Word object, which is expected:
#<Word:0x007fcce22c9c58 #translation="Tomorrow", #value="Morgen">
With the other environment, it always prints a Hash:
{"json_class"=>"Word", "data"=>["Morgen", "Tomorrow"]}
I also tried to pass :object_class key, it throws another exception:
p JSON.parse(str, :object_class => Word)
# => ArgumentError: wrong number of arguments (0 for 2)
I could not figure out the require 'json' version during runtime using:
puts Gem.loaded_specs['json'].version
because Gem.loaded_specs.keys doesn't contain it.
Thanks for any hint.
Replied from the author of JSON lib - on newer version, due to security reason, to deserialize custom object, either you can:
JSON.parse(str, :create_additions => true)
or you can:
JSON.load(str)
So, I overlooked the JSON#load part in ruby-doc:
load(source, proc = nil, options = {})
Load a ruby data structure from a JSON source and return it. A source
can either be a string-like object, an IO-like object, or an object
responding to the read method. If proc was given, it will be called
with any nested Ruby object as an argument recursively in depth first
order. To modify the default options pass in the optional options
argument as well.
BEWARE: This method is meant to serialise data from trusted user
input, like from your own database server or clients under your
control, it could be dangerous to allow untrusted users to pass JSON
sources into it. The default options for the parser can be changed via
the ::load_default_options method.
This method is part of the implementation of the load/dump interface
of Marshal and YAML.
Deserializing directly into a rich object (especially if your JSON comes from an unknown source) can be a pretty serious attack vector (recent Rails vulnerabilities are related to that).
I would guess that this ability was disabled between Ruby versions, or, at least changed to a whitelist-based approach. I wasn't able to find any links to support this claim though, so I might be wrong.
Anyway, you might find it simpler and more compatible to initialize your class from the deserialized hash instead:
class Word
def self.from_json(json)
args = JSON.parse(json)["data"];
new(*args)
end
end
Here is another workaround, because my code is not used in web communication, vulnerability is not a problem here.
Before I was doing:
JSON.parse(str)
Now just need to add few lines:
obj = JSON.parse(str)
if obj.is_a?(Hash)
class_name = obj['json_class'].split('::').inject(Kernel) { |namespace, const_name| namespace.const_get(const_name) }
args = obj['data']
word = class_name.new(*args)
else
word = obj
end

OpsWorks Config Data: TypeError - Symbol as array index

I'm trying to use the IP of an instance on AWS OpsWorks in a Chef recipe, code looks like this:
variables(
:db_host => (node[:scraper][:db_host] rescue nil),
:db_user => (node[:scraper][:db_user] rescue nil),
:db_pass => (node[:scraper][:db_pass] rescue nil),
:db_name => (node[:scraper][:db_name] rescue nil),
:beanstalk_host => (node[:opsworks][:layers][:admin][:instances].first[:private_ip])
)
But I get a TypeError - Symbol as array index error.
I'm not a Ruby developer, so I'm a bit lost with what to try. Even the docs list that syntax:
The following example obtains the private IP address of the HAProxy layer's first instance:
node[:opsworks][:layers][:lb][:instances].first[:private_ip]
When I log the value of node[:opsworks][:layers][:admin][:instances].first I get:
["admin1", {"elastic_ip"=>"54.221.245....
The error is correct: you can't use a symbol as an array index.
By the output of you last line, since it starts with a [, it is an array.
Most likely you are trying to index the dictionary on the second position of the array with the symbol :private_ip. In this case, the correct line is
node[:opsworks][:layers][:admin][:instances].first[1][:private_ip]
You need to go step by step in your mind. If node[:opsworks][:layers][:admin][:instances].first returns
["admin1", {"elastic_ip"=>"54.221.245....}]
Then node[:opsworks][:layers][:admin][:instances].first[1] will return
{"elastic_ip"=>"54.221.245....}

Resources