1

I have a simple function which takes a JSON and 'does something' with it. The main part works good BUT the function returns not only what I want but additionally the result of .each loop!

The code:

  module Puppet::Parser::Functions
    newfunction(:mlh, :type => :rvalue) do |args|

    lvm_default_hash    = args[0]
    lvm_additional_hash = args[1]

    if lvm_additional_hash.keys.length == 1
      if lvm_additional_hash.keys.include? 'logical_volumes'
        # do stuff - we have only 'logical_volumes'
        lvm_default_hash.keys.each do |key|

          pv_array       = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
          lv_hash        = lvm_default_hash[key]['logical_volumes']
          new_lv_hash    = lvm_additional_hash['logical_volumes']
          merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]

          # this is what I want to return to init.pp
          puts Hash[key => pv_array.merge(merged_lv_hash)]

        end
      end
    end
  end
end

Variables in the init.pp are:

$default_volume_groups = {
'sys' => {
  'physical_volumes' => [
    '/dev/sda2',
  ],
  'logical_volumes' => {
    'root'   => {'size' => '4G'},
    'swap'   => {'size' => '256M'},
    'var'    => {'size' => '8G'},
    'docker' => {'size' => '16G'},
  },
},

}

and the second argument from a hieradata:

modified_volume_groups:
logical_volumes:
  cloud_log:
    size: '16G'

In the init.pp I have something like this to test it:

notice(mlh($default_volume_groups, $modified_volume_groups))

which gives me a result:

syslogical_volumesvarsize8Gdockersize16Gcloud_logsize16Gswapsize256Mrootsize4Gphysical_volumes/dev/sda2
Notice: Scope(Class[Ops_lvm]): sys

The "long" part before the Notice is the proper result from the puts but the Notice: Scope(): sys is this what I do not want to! I know that this is the result of this each loop over the default_volumes_groups:

lvm_default_hash.keys.each do |key|
  # some stuff
end

How to block of this unwanted result? It blows my puppet's logic because my init.pp sees this sys and not what I want.

Does someone knows how to handle such problem?

Thank you!

kamh
  • 65
  • 8
  • I'm inclined to think that the extra text you see printed is not part of your function's return value at all. It looks like debug output describing the call to `notice()`, as opposed to being any part of the value presented to `notice()` for it to print. – John Bollinger Jan 23 '16 at 16:58
  • Thank you but how it is happen that my function returns two values - one from the _puts_ and one from the _each_ loop over the default value? – kamh Jan 23 '16 at 17:46

3 Answers3

1

I found how to handle this problem but maybe someone could explain me why it works in this way :)

This does not work (short version):

module Puppet::Parser::Functions
  newfunction(:mlh, :type => :rvalue) do |args|

    lvm_default_hash    = args[0]
    lvm_additional_hash = args[1]

    if lvm_additional_hash.keys.length == 1
      if lvm_additional_hash.keys.include? 'logical_volumes'
        lvm_default_hash.keys.each do |key|

          pv_array       = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
          lv_hash        = lvm_default_hash[key]['logical_volumes']
          new_lv_hash    = lvm_additional_hash['logical_volumes']
          merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]

          puts Hash[key => pv_array.merge(merged_lv_hash)]

        end
      end
    end
  end
end

but this works:

module Puppet::Parser::Functions
  newfunction(:mlh, :type => :rvalue) do |args|

    lvm_default_hash    = args[0]
    lvm_additional_hash = args[1]

  # empty Hash
    hash_to_return = {}

    if lvm_additional_hash.keys.length == 1
      if lvm_additional_hash.keys.include? 'logical_volumes'
        lvm_default_hash.keys.each do |key|

          pv_array       = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
          lv_hash        = lvm_default_hash[key]['logical_volumes']
          new_lv_hash    = lvm_additional_hash['logical_volumes']
          merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]

        # assigned value in the 'each' loop we want to return to puppet
          hash_to_return = Hash[key => pv_array.merge(merged_lv_hash)]

        end

      # returned Hash - instead of previous 'puts'
        return hash_to_return

      end
    end
  end
end

Now I have what I need!

Notice: Scope(Class[Ops_lvm]): sysphysical_volumes/de
kamh
  • 65
  • 8
1

You've got it -- the first one doesn't work because in Ruby, the return value of a block or function is the last evaluated statement. In the case of the one that didn't work, the last evaluated statement was the .each. As it turns out, each evaluates to the enumerable that it was looping through.

A simple example:

def foo
  [1, 2, 3].each do |n|
    puts n
  end
end

If I were to run this, the return value of the function would be the array:

> foo
1
2
3
=> [1, 2, 3]

So what you have works, because the last thing evaluated is return hash_to_return. You could even just go hash_to_return and it'd work.

If you wanted to get rid of the return and clean that up a little bit (and if you're using Ruby 1.9 or above), you could replace your each line with:

lvm_default_hash.keys.each_with_object({}) do |key, hash_to_return|

This is because each_with_object evaluates to the "object" (in this case the empty hash passed into the method, and referred to as hash_to_return in the block params). If you do this you can remove the return as well as the initialization hash_to_return = {}.

Hope this helps!

rusty
  • 2,771
  • 1
  • 24
  • 23
0

Your custom function has rvalue type which means it needs to return value. If you don't specify return <something> by default, your last statement is implicitly your return.
In the example above, first one that does not work correctly, has last statement inside each block:

puts Hash[key => pv_array.merge(merged_lv_hash)]

Your second example is correct simply because you set value for hash_to_return in each block and then "return" it outside of each block. Not sure if this is the behavior you want since last assigned hash value (in last loop inside each block) will be the one that will be returned from this function.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Bakir Jusufbegovic
  • 2,806
  • 4
  • 32
  • 48
  • This is related to the @kamh comment that starts with: I found how to handle this problem but maybe someone could explain me why it works in this way :) – Bakir Jusufbegovic Feb 02 '16 at 14:09