CNK's Blog

Adding Library Code to Your Chef Runs

Using Mixins Inside of Chef Recipes

There are a couple of wrinkles about mixing ruby modules into your chef code. The first appears to be that the chef DSL takes over everything (or nearly everything) - including the include command. So using normal ruby like include MyModule inside a recipe file causes a compile time error:

    ..... omitted ...
    resolving cookbooks for run list: ["testcookbook::default"]
    Synchronizing Cookbooks:
      - testcookbook
      Compiling Cookbooks...
      [2015-07-09T13:53:52-07:00] WARN: this should log node info bar from 'Chef::Log'

    ================================================================================
    Recipe Compile Error in .../testcookbook/recipes/default.rb
    ================================================================================

    NoMethodError
    -------------
    No resource or method named `include' for `Chef::Recipe "default"'

That seems odd - but it is probaby a good thing since if it worked, it might end up including your library code into the wrong place. With nearly all libraries, you want your module code available in some specific execution scope - which is probably not the compile time scope. To use our UsefulMethods module in our recipe context, we need the following in our recipe file:

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

This blog post on the Chef.io site does a really nice job of explaining how (and why) to write libraries and then use them in your recipes. In their example code, the library code needs to be used inside the user resource: Chef::Resource::User.

Creating Modules Inside a Namespace

The second example in the custom libraries section of Customizing Ruby shows another option for how to get your library code exactly where you want it. Instead of defining a generic module and then including it in your recipe, as above, you can set up your library within the namespace in which you want to use it. In the case of our UsefulModules code, we rewrite the library as a class inside the Chef::Recipe namespace:

    class Chef::Recipe::StopFile
        def self.stop_file_exists?
            ::File.exists?("/tmp/stop_chef")
        end
    end

And then in our recipe file we don’t have to send any message to include the new class. Because it was created inside the Chef::Recipe namespace, it gets loaded into our recipe context when the library file is loaded at the beginning of the chef run. We can just call the class method like so:

    if StopFile.stop_file_exists?
       ....