brbcoffee
brbcoffee

Reputation: 85

How do I pass a hash to a custom function in puppet?

I have defined a custom function currently based on the very simple example here: https://docs.puppet.com/guides/custom_functions.html

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    filename = args[0]
    hash_to_be_transformed = args[1]
    File.open(filename, 'a') {|fd| fd.puts hash_to_be_transformed }
  end
end

This kinda works. I can call it like this:

  $my_hash = { key => "value1" , key2 => "value2" }
  notify{ "new hash!! $my_hash" :}
  transform_service_hash('/var/tmp/blah',$my_hash)

and the file displays:

mgt21 ~ # cat /var/tmp/blah
keyvalue1key2value2

But, if I try to access elements of the hash, nothing changes:

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    filename = args[0]
    hash_to_be_transformed = args[1]
    element1 = hash_to_be_transformed["key"] 
    File.open(filename, 'a') {|fd| fd.puts element1 }
  end
end

The above block outputs the exact same data to /var/tmp/blah.

And, interestingly, if I remove the filename pass and define it statically in the module:

  $my_hash = { key => "value1" , key2 => "value2" }
  notify{ "new hash!! $my_hash. element1 is: $my_hash.key" :}
  transform_service_hash($my_hash)

and

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    hash_to_be_transformed = args[0]
    element1 = hash_to_be_transformed["key"] 
    File.open('/var/tmp/blah2', 'a') {|fd| fd.puts element1 }
  end
end

I get the following error: "Error 400 on SERVER: can't convert Hash into String" with a line reference pointing to "transform_service_hash($my_hash)"

I am new to both puppet and ruby...so I'm unsure I am not passing the element properly, if I am not receiving it properly, or if it something that puppet cannot handle. Please note that I am using version 3.8 of puppet and 1.8.7 of ruby.

Thanks for any help. I've been banging my head against this, and google hasn't been forthcoming yet.

---Edit to clarify my goals (I also edited my code a bit for specificity): I am attempting to pass a hash into a custom ruby function within puppet. The "test" hash has two elements: one string and one array. It is defined as such:

  $my_hash = { key => "value1" , key2 => ['array_value1', 'array_value2'] }
  $my_display_element=$my_hash["key2"][0]
  notify{ "new hash!! $my_hash. the first value of the array stored in element2 is: $my_display_element" :}
  transform_service_hash('/var/tmp/blah',$my_hash)

The function appears like so:

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    filename = args[0]
    hash_to_be_transformed = args[1]
    element1 = args[1]["key"]
    element2 = args[1]["key2"][0]
    #element1 = hash_to_be_transformed["key"]
    #element2 = hash_to_be_transformed["key2"][0] 
    File.open(filename, 'a') {|fd| fd.puts "hash_to_be_transformed: #{hash_to_be_transformed}\n" }
    File.open(filename, 'a') {|fd| fd.puts "element1: #{element1}\n" }
    File.open(filename, 'a') {|fd| fd.puts "element2: #{element2}\n" }        
  end
end

For now, I just want to be able to see that I am able to access elements within the passed hash like a hash. So I'd love for the output file to look like:

hash_to_be_transformed: keyvalue1key2array_value1array_value2
element1: value1
element2: array_value1

However, in the output file, I see:

mgt21 ~ # cat /var/tmp/blah 
keyvalue1key2array_value1array_value2

Clearly, something is off here as my text is not being added and the full hash is just printed out just once and seemingly in string form.

I believe that this may be related to the error that I get when I don't pass in a file name (see above). I think that my hash is getting interpreted (or passed) as a string and, as such, I am unable to access the elements. Unfortunately, I still have been unable to verify this or figure out why it might be happening.

---Edit2 based on Matt's answer below.

I decided to simplify my code to isolate this "can't convert Hash into String error". I also made his suggested changes to remove the ambiguity from my key declarations.

  $my_hash = { 'key' => "value1" , 'key2' => ['array_value1', 'array_value2'] }
  $my_display_element=$my_hash["key2"][0]
  notify{ "new hash!! $my_hash. the first value of the array stored in element2 is: $my_display_element" :}
  transform_service_hash($my_hash)

and

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    hash_to_be_transformed = args[0]
    element1 = args[0]['key']
    element2 = args[0]['key2'][0]
    File.open('/var/tmp/blah', 'a') {|fd| fd.puts "hash_to_be_transformed: #{hash_to_be_transformed}\n" }
    File.open('/var/tmp/blah', 'a') {|fd| fd.puts "element1: #{element1}\n" }
    File.open('/var/tmp/blah', 'a') {|fd| fd.puts "element2: #{element2}\n" }
  end
end

But, I still end up with the same "Hash to String error". It is worth noting that I also tried simplifying my hash to:

  $my_hash = { 'key' => "value1" , 'key2' => "value2" }

and I still get the "Hash to String error".

Upvotes: 1

Views: 1017

Answers (2)

brbcoffee
brbcoffee

Reputation: 85

After much gnashing of teeth (and some very helpful pointers from @MattSchuchard), I realized that none of the changes to my function were going into effect. One needs to restart the puppetmaster service after each change to a custom function: docs.puppet.com/guides/custom_functions.html (appropriately under "Gotchas").

Once I started restarting this service after each change to the function, my hash was able to be parsed properly:

from the .pp file:

  $filename = "/var/tmp/test"
  $my_hash = { 'key' => "value1" , 'key2' => ["M\'lady\n*doffs cap*", 'array_value2'] }
  transform_service_hash($filename, $my_hash)

from the ruby file:

module Puppet::Parser::Functions
  newfunction(:transform_service_hash) do |args|
    filename = args[0]
    hash_to_be_transformed = args[1]
    array_val = hash_to_be_transformed['key2'][0]
    File.open(filename, 'a') {|fd| fd.puts "#{array_val}\n" }
  end
end

and output:

mgt21 tmp # cat test 
M'lady
*doffs cap*

Upvotes: 0

Matthew Schuchard
Matthew Schuchard

Reputation: 28739

I quickly took your custom parser function and converted it into pure ruby like the following:

hash = { 'key' => 'value1', 'key2' => %w(array_value1 array_value2) }

def newfunction(filename, a_hash)
  element1 = a_hash['key']
  element2 = a_hash['key2'][0]
  File.open(filename, 'a') do |fd|
    fd.puts "hash_to_be_transformed: #{a_hash}"
    fd.puts "element1: #{element1}"
    fd.puts "element2: #{element2}"
  end
end

newfunction('foo.txt', hash)

This results in the output text file like the following:

hash_to_be_transformed: {"key"=>"value1", "key2"=>["array_value1", "array_value2"]}
element1: value1
element2: array_value1

This seems to confirm my initial suspicion about what is going wrong here. Your hash in Puppet of:

$my_hash = { key => "value1" , key2 => ['array_value1', 'array_value2'] }

has keys of implicit/ambiguous types. In the ruby code I used to test, I explicitly established them as strings. This also correlates strongly with these lines in your code failing:

element1 = args[1]["key"]
element2 = args[1]["key2"][0]

and your error message of:

Error 400 on SERVER: can't convert Hash into String

because you are specifying in your ruby code that you expect the keys to be string. Changing your hash in Puppet to:

$my_hash = { 'key' => "value1" , 'key2' => "value2" }

should fix this.

On an unrelated note, I recommend the use of linters to help you learn these languages. Puppet-Lint, Rubocop, and Reek will all help point out suboptimal and messy parts of your code to help you learn the new languages.

On a related note, you may want to put something like this at the top of your custom parser function:

raise(Puppet::ParseError, 'newfunction expects two arguments') if args.length != 2

Upvotes: 1

Related Questions