chef attributes value not getting parsed in another attribute - ruby

I am setting attributes in default.rb as
default[:my_app] = {
:vol => "data02",
:commitlog => "/foo/bar/node[:vol]/commitlog",
}
But :vol value is not getting parsed in commitlog attribute and I am getting following error.
mError executing action `create` on resource 'directory[/foo/bar/node[:vol]/comitlog]'[0m

You're missing the String interpolation syntax, e.g. y = "The value of X is #{X}." You probably want:
default[:my_app] = {
:vol => "data02",
:commitlog => "/foo/bar/#{node[:vol]}/commitlog",
}
Also, keep in mind that if you make one attribute depend on the value of another, you might override node[:my_app][:vol] later and expect the value of node[:my_app][:commitlog] to change with it, and it may not. The attributes will be parsed together, potentially before your override affects the first one.

Even after I am using the interpolation syntax, and when I am using node[:my_app][:commitlog] in recipe it shows /foo/bar//commitlog

Related

Puppet - create NESTED custom fact

I have successfully created a .rb custom fact that parses a built-in fact to create a new value, however I am now trying to use it as nested custom fact for Puppet.
The hierarchy I want to create is similar to built-in facts, eg running Facter (or Facter -p) would show:
custom_parent => {
custom_fact_1 => whatever
custom_fact_2 => whatever2
}
and usage in a puppet manifest would be:
$custom_parent.custom_fact_1
So far I have tried leading syntax such as:
Facter.add (:custom_parent)=>(custom_fact_1) do
Facter.add (:custom_parent)(:custom_fact_1) do
Facter.add (:custom_parent.custom_fact_1) do
Facter.add (:custom_parent:custom_fact_1) do
Facter.add (custom_parent:custom_fact_1) do
...and many other variations however cannot get a nested custom fact array to create. I've Googled for a while and if anyone knows if it is possible I would be very grateful.
I did find that nested facts can be created using an array in a .yaml file in the /etc/puppetlabs/facter/facts.d/ directory as below, however this sets FIXED values and does not process logic which I require in my custom fact.
{
"custom_parent":
{
"custom_fact_1": "whatever",
"custom_fact_2": "whatever2",
}
}
Thanks in advance.
The hierarchy I want to create is similar to built-in facts, eg
running Facter (or Facter -p) would show:
custom_parent => {
custom_fact_1 => whatever
custom_fact_2 => whatever2
}
There are no "nested" facts. There are "structured" facts, however, and these may have hashes as their values. To the extent that Facter presents output that you characterize as "nested", that's surely what you're looking at.
Since the elements of a structured fact's value are not facts in their own right, they need to be specified in the resolution of the fact itself:
Facter.add (:custom_parent) do
{
:custom_fact_1 => 'whatever',
:custom_fact_2 => 'whatever2',
}
end
The whatever and whatever2 do not need to be literal strings; they can be more or less arbitrary Ruby expressions. Of course, you can also set the members separately from creating the hash (but in the same fact resolution):
Facter.add (:custom_parent) do
value = new Hash()
value[:custom_fact_1] = 'whatever'
value[:custom_fact_2] = 'whatever2'
value
end
Thank you #John Bollinger. Your example was very close however I found that I needed to use type => aggregate and chunk to get it to work. I also combined it with a defined function, with the end result being based on the code below.
If you have any other suggestions to improve code conformity on this please feel free to point it out. Cheers
# define the function to process the input fact
def dhcp_octets(level)
dhcp_split = Facter.value(:networking)['dhcp'].split('.')
if dhcp_split[-level..-1]
result = dhcp_split[-level..-1].join('.')
result
end
end
# create the parent fact
Facter.add(:network_dhcp_octets, :type => :aggregate) do
chunk(:dhcp_ip) do
value = {}
# return a child => subchild array
value['child1'] = {'child2' => dhcp_octets(2)}
# return child facts based on array depth (right to left)
value['1_octets'] = dhcp_octets(1)
value['2_octets'] = dhcp_octets(2)
value['3_octets'] = dhcp_octets(3)
value['4_octets'] = dhcp_octets(4)
# this one should return an empty fact
value['5_octets'] = dhcp_octets(5)
value
end
end

Using slice! on a variable is modifying the node attribute that populated the variable

In OpsWorks Stacks, I have set a layer attribute using the custom JSON field:
{
"layer_apps" : [
"app_manager"
]
}
The app_ portion of the attribute is necessary for the workflow. At times, I need to temporarily remove the app_ portion within a cookbook. To do this, I use slice!:
node['layer_apps'].each do |app_name|
install_certs_app_name = app_name
install_certs_app_name.slice!('app_') # 'app_manager' => 'manager'
# snip
end
However, once this is done, even though app_name isn't being directly modified, each node['layer_apps'] attribute gets sliced, which carries on to subsequent cookbooks and causes failures. The behaviour I expected was that slice! would modify app_name, and not the current node['layer_apps'] attribute. Thinking that app_name was a link to the attribute rather than being it's own variable, I tried assigning its value to a separate variable (install_certs_app_name and similar in other cookbooks), but the behaviour persisted.
Is this expected behaviour in Ruby/Chef? Is there a better way to be excluding the app_ prefix from the attribute?
app_name is being directly modified. That's the reason for the bang ! after the method... so that you're aware that the method mutates the object.
and app_name and install_certs_app_name are referencing the same object.
Note that slice and slice! both return "app_" but the bang object mutates the caller by removing the sliced text.
If you did
result = install_certs_app_name.slice!('app_')
puts result
==> app_
puts install_certs_app_name
--> manager
Try (instead)
install_certs_app_name = app_name.dup
install_certs_app_name.slice!('app_')
So you have two separate objects.
Alternatively,
install_certs_app_name = app_name.sub('app_', '')
In case you'd want a variable sliced, what you'll is the non-destructive version:
str.slice and not str.slice!
These are often referred to as Bang-methods, and replace the variable in place.
Below is an example with the .downcase method. This is the same principle for .slice.
EDIT:
However, since .slice returns the part that's been cut out, you could just remove the app_-part .sub like
"app_manager".sub("app_",'') #=> "manager"
http://ruby-for-beginners.rubymonstas.org/objects/bangs.html
https://ruby-doc.org/core-2.2.0/String.html#method-i-slice
When you assigning app_name to install_certs_app_name you still referencing to the same object. In order to create new object you can do:
install_certs_app_name = app_name.dup
New object with the same value is created. And slicing install_certs_app_name does not affect app_name this way.

Ruby String to access an object attribute

I have a text file (objects.txt) which contains Objects and its attributes.
The content of the file is something like:
Object.attribute = "data"
On a different file, I am Loading the objects.txt file and if I type:
puts object.attribute it prints out data
The issue comes when I am trying to access the object and/or the attribute with a string. What I am doing is:
var = "object" + "." + "access"
puts var
It prints out object.access and not the content of it "data".
I have already tried with instance_variable_get and it works, but I have to modify the object.txt and append an # at the beginning to make it an instance variable, but I cannot do this, because I am not the owner of the object.txt file.
As a workaround I can parse the object.txt file and get the data that I need but I don't want to do this, as I want take advantage of what is already there.
Any suggestions?
Yes, puts is correctly spitting out "object.access" because you are creating that string exactly.
In order to evaluate a string as if it were ruby code, you need to use eval()
eg:
var = "object" + "." + "access"
puts eval(var)
=> "data"
Be aware that doing this is quite dangerous if you are evaluating anything that potentially comes from another user.

Chef attributes "no implicit conversion of String into Integer"

I'm writing a chef recipe which simply creates a database config file, but I'm stumped simply access the attributes. I have a few PHP applications being deployed to each instance, and OpsWorks uses the same recipes for everyone, so I have a few different settings in the attributes file.
attributes/database-settings.rb
# API
default[:api][:path] = 'app/config/database.php';
default[:api][:host] = 'test';
default[:api][:database] = 'test';
default[:api][:username] = 'test';
default[:api][:password] = 'test';
recipes/database-settings.rb
Chef::Log.info("Database settings!");
node[:deploy].each do |application, deploy|
if node.has_key?(application)
Chef::Log.info("Application: #{application}");
path = node["api"]["path"]; # ERROR HAPPENING HERE
Chef::Log.info("Path: #{path}");
template path do
source "database.erb"
mode 0440
variables({
:host => node["api"]["host"],
:database => node["api"]["database"],
:username => node["api"]["username"],
:password => node["api"]["password"]
})
end
end
end
The error I'm getting is no implicit conversion of String into Integer. I've tried creating and accessing the settings in every way I can think of, such as...
node[:api][:path] # no implicit conversion of Symbol into Integer
node['api']['path'] # no implicit conversion of String into Integer
node[:api].path # undefined method `path' for #<Chef::Node::ImmutableArray:0x007fa4a71086e8>
node[application][:path] # no implicit conversion of Symbol into Integer
I'm sure there's something very obvious I'm doing wrong here, but I've tried everything I can think of an I just can't seem to find any way of getting this to work?! Ideally I'd like to use a variable where I can "api", but using an if/else wouldn't be too terrible for 3 apps...
That is a common error seen when you try to access an object thinking it is a hash, but is actually an array. In fact, from one of your errors, it can be read that node["api"] is a Chef::Node::ImmutableArray.
Ok so the problem wasn't really that I was accessing the config wrongly, it was that the different attribute files were all being merged into a single config and I didn't realise this.
I had these config files...
attributes/database_settings.rb
default[:api][:path] = 'app/config/database.php';
default[:api][:username] = 'example';
attributes/writable_directories.rb
default[:api] = ['public/uploads', 'storage/cache'];
When I tried to access default[:api][:path] I was actually accessing the array of directories when seemed to override the database settings attributes. Moving these into default[:directories][:api] and default[:database][:api][:path] etc fixed this.
Note that you will also get this error if you accidentally enter a space between "node" and the items indexing it:
node[:foo][:bar]
will work, while
node [:foo][:bar]
will throw this exception. It can be hard to spot.

How do I evaluate non-SQL logic within a SQL call with ActiveRecord?

I'm new to Ruby and trying to make an ActiveRecord where call.
I also want to evaluate logic during the call, so that I get an object returned where the SQL query and my logic is true.
def new_target
#Need to make sure the array doesn't include the existing target
t = robot.where(
"name != :robot_name",
{:robot_name => self.name}
).first
I'd like to say something like !self.targets.include? (the returned robot).
So I'm searching for all robots that have a different name than the current one, but want to make sure I don't already have them within this robot's target array.
You code can be something like
t=robot.where(
"name!= :robot_name and name not in (:target_names) ",
{
:robot_name => self.name,
:target_names => self.targets.map{|robot| robot.name}
}
)

Resources