how to require my library in chef ruby_block

Xiaoming picture Xiaoming · Dec 30, 2013 · Viewed 15.6k times · Source

I'm developing a cookbook to deploy a simple ROR application. I write an app_helper.rb and put it into the libraries directory of my cookbook, here is the content:

module AppHelper

    def self.find_gem
      if File.exists?("/usr/local/rvm/bin/rvm")
        return `/usr/local/rvm/bin/rvm default exec which gem`.chomp
      else
        return "/usr/bin/gem"
      end
    end
  end

In the recipes/default.rb, I mix in the above module into Chef::Recipe class

class Chef::Recipe
  include AppHelper
end

As you know, the find_gem function can be called from anywhere of the recipe.

when I'm trying to use the find_gem function in my ruby_block like this:

ruby_block "find gem" do
   block do
    gem_bin = Chef::Recipe::find_gem
    # or gem_bin = find_gem
  end
end

I got a NoMethodError: undefined method 'find_gem'.

Also try to mix in the module into Chef::Resource::RubyBlock, it doesn't work neither.

class Chef::Resource::RubyBlock
  include AppHelper
end

ruby_block "find gem" do
   block do
    gem_bin = Chef::Resource::RubyBlock::find_gem
    # or gem_bin = find_gem
  end
end

Is there any way to call the function in the module from the ruby_block? Or Is there an variable of chef to location the files in the libraries, so that I can be able to require the module in the ruby_block.

Thanks!

Answer

sethvargo picture sethvargo · Dec 30, 2013

Depending on load order, you might be including your module into an anonymous Ruby class instead of what you think.

If you want to use your method in a recipe, do this at the top of your recipe:

include AppHelper

You could alternatively use :send at the end of your library:

Chef::Recipe.send(:include, AppHelper)

This is different because it will raise an exception if Chef::Recipe is not defined (whereas you are creating a new class if it doesn't exist).

That's all you should need to do unless you want to use the helper in not_if and only_if guards. Then you need to include the helper in the resource:

Chef::Resource.send(:include, AppHelper)

Okay, now that I explained all of that, it won't actually help you. The Ruby Block provider simply calls the block - it doesn't instance eval it. So including the helper in the resource doesn't do anything.

So, you'll need to use a singleton object in this instance (it's the only solution I can reliably think of). The way you've defined your method, you can call it directly from the global namespace:

AppHelper.find_gem('...')

So:

ruby_block "find gem" do
  block do
    gem_bin = AppHelper.find_gem('...')
  end
end