Boolean property in Puppet provider - ruby

I am writing a Puppet provider and I need a boolean property. I declared it with:
newproperty(:no_sync, :boolean => true) do
desc "Whether to omit syncing the file after every logging, ony when action_type is file."
end
Then I need to declare the no_sync function in the provider which should return true or false. However, when I do this, it seems the values returned as not properly interpreted by Puppet. I've tried returning strings (:true and :false respectively), but as a result they always get interpreted as true (which is quite logical).
How are we supposed to declare boolean properties in a Puppet provider?

Returning the symbols :true and :false from the provider method is the correct thing to do.
You could take a look at the macauthorization source code for an example of how the type is defined. The provider for this type returns :true or :false.

Related

How to make Ruby Mocha mock only check about one parameter

I want to mock this function:
def self.set_segment_info(segment_info, history_record)
history_record.segment_info = segment_info
end
In my test, I want a mock that only confirms that I called set_segment_info with an expected value. I don't care about what I pass in for history_record.
How would I do this? I tried
SegmentHistoryRecord.expects(:set_segment_info).with(:segment_info => expected_segment_info, :history_record => anything)
But that doesn't work.
I ran into this today and ended up doing something like:
SegmentHistoryRecord.expects(:set_segment_info).with(
expected_segment_info,
anything
)
I find it more readable that the do version and it helped me avoid a rubocop issue with too many parameters.
Here's an implementation where, if your function takes a lot of parameters, it's more convenient to specify a value for just the one you care about, instead of for all of them:
expected_segment_info = # ...
SegmentHistoryRecord.expects(:set_segment_info).with() { |actual_parameters| actual_parameters[:segment_info] == expected_segment_info }
(Where, as in the original question, set_segment_info is the function being mocked, and segment_info is the parameter whose value you want to match. Note that the history_record parameter -- and any others that might be present -- don't need to be included.)
SegmentHistoryRecord.expects(:set_segment_info).with() do |param1, param2|
# change below to your verification for :segment_info
# and leave param2 doing nothing, the expectation will ignore param2
param1 == expected_segment_info
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.

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 to check if a key exists in a Ruby hash?

I am using search of Net::LDAP, the returned entry is something like this.
#<Net::LDAP::Entry:0x7f47a6491c00
#myhash=
{:loginshell=>["/bin/bash"],
:cn=>["M... R..."],
:homedirectory=>["/mnt/home/m..."],
:uid=>["m..."],
:userpassword=>["{CRYPT}$1$3zR/C...$R1"],
...
}>
I tried to do the following, but failed.
(1)
e = entry.to_hash
e.has_key? "uid"
(2)
entry.has_key? "uid"
The first error says "to_hash" undefined, the second "has_key" undefined. Then I really don't know how to do it, basically I want to find if "uid" is present and if so get its correspondent value.
Thank you very much for the tip.
BTW, it only responds to "entry.uid", but if the search key is provided as a string, how to do that? for example,
def get_value(key)
if entry has key
return key's value
end
end
:uid is a Symbol. That's not a String.
try this:
e.has_key? :uid
The key "uid" doesn't exist. Try
e = Entry.new.myhash
e.has_key?(:uid)
That should return true. If that gives you an error, the problem might lie in your class. Make sure that myhash is defined in the initialize method, and that you use a getter method (or attr_reader) to be able to access the variable. You could use
attr_reader :myhash
right before the initialize method.

How to create XML object from string using xml-mapping in Ruby

I'm using xml-mapping in Ruby (on Sinatra) for some XML stuff. Generally I follow this tutorial: http://xml-mapping.rubyforge.org/. I can create objects and write them to XML strings using
login.save_to_xml.to_s
But when I try
login = Login.load_from_xml(xml_string)
I get the following error:
XML::MappingError - no value, and no default value: Attribute username not set (XXPathError: path not found: username):
Here is the XML string I receive:
<login><username>ali</username><password>baba</password></login>
This is what the class looks like:
class Login
include XML::Mapping
text_node :username, "username"
text_node :password, "password"
end
So the class name is the same, the nodes are named the same. I actually get the exact same string when I create an instance of my object and fill it with ali/baba:
test = Login.new
test.username = "ali"
test.password = "baba"
p test.save_to_xml.to_s
<login><username>ali</username><password>baba</password></login>
What am I missing?
Thanks,
MrB
EDIT:
When I do
test = login.save_to_xml
And then
login = Login.load_from_xml(test)
it works. So the problem seems to be that I'm passing a string, while the method is expecting.. well, something else. There is definitely a load_from_xml(string) method in the rubydocs, so not sure what to pass here. I guess I need some kind of reverse to_s?
It looks like you save_to_xml creates a REXML::Element. Since that works, you may want to try:
Login.load_from_xml(REXML::Document.new(xml_string).root)
See the section on "choice_node" for a more detailed example http://xml-mapping.rubyforge.org/

Resources