Ringo,
To avoid this
[Thu, 26 May 2011 13:32:20 +0000] FATAL: NoMethodError: undefined method `include' for #Chef::Recipe:0xb6c36764
Full stack trace looks like this:
NoMethodError: undefined method include' for #<Chef::Recipe:0xb6c36764> /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/recipe_definition_dsl_core.rb:56:in
method_missing'
/var/chef/cache/cookbooks/mysql/recipes/server.rb:22:in from_file' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/cookbook_version.rb:578:in
load_recipe'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:40:in include_recipe' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:27:in
each'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/mixin/language_include_recipe.rb:27:in include_recipe' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:72:in
load'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:69:in each' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/run_context.rb:69:in
load'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/client.rb:195:in setup_run_context' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/client.rb:159:in
run'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:239:in run_application' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:229:in
loop'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application/client.rb:229:in run_application' /usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/../lib/chef/application.rb:66:in
run'
/usr/lib/ruby/gems/1.8/gems/chef-0.10.0/bin/chef-client:26
/usr/bin/chef-client:19:in `load'
But seriously, the main reason is that recipe files are not regular Ruby...that is to say they are a DSL that is evaluated by the chef-client at runtime. Chef uses some of Ruby's method_missing magic to evaluate the recipe DSL (resource definitions) in your recipe code, in this case 'include' cannot be evaluated as it is passed up the method_missing chain.
The variant we use in 'mysql::recipe'
::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)
actually sends the 'include' message to Chef::Recipe class definition with the Opscode::OpenSSL::Password module as a payload. In this case we want to make the method 'secure_password' available to this instance of Chef::Recipe (ie Chef::Recipe:0xb6c36764). So essentially we are in an instance of Chef::Recipe sending a message to it's class definition.
We could have also used 'extend' like so:
extend Opscode::OpenSSL::Password
That would make every method in the Opscode::OpenSSL::Password module available to EVERY instance of Chef::Recipe, but that feels dirty.
We could have accomplished the same thing as using extend by creating our library slightly differently also:
class Chef
class Recipe
def secure_password
RANDOM CODE HERE
end
end
end
This essential would open up the Chef::Recipe class at runtime and add a secure_password method and thus making it available to every instance of Chef::Recipe. I believe this is the example you will see on the wiki in the Libraries section [0].
Hope that helps.
Seth
--
Opscode, Inc.
Seth Chisamore, Senior Technical Evangelist
IRC, Skype, Twitter, Github: schisamo
[0] http://wiki.opscode.com/display/chef/Libraries
On Thursday, May 26, 2011 at 9:27 AM, Ringo De Smet wrote:
Hello,
While investigating the setup of some cookbooks, I bumped into this line:
https://github.com/opscode/cookbooks/blob/master/mysql/recipes/server.rb#L20
Can someone explain me why the meta-level invocation is used here
instead of just a direct invocation?
So
::Chef::Recipe.send(:include, Opscode::OpenSSL::Password)
instead of
include Opscode::OpenSSL::Password
Cheers,
Ringo