Demystifying Common Idioms in Chef Recipes

Here at Opscode, we think of the Chef recipe DSL, or domain-specific language, as being quite straightforward to learn. Despite the fact that under the hood it’s all Ruby, you most definitely do not need to know Ruby to use Chef. Your humble correspondent would not consider himself a Ruby programmer by any stretch, but he knows enough of the Chef DSL and basic Ruby to be able to help customers automate their infrastructure.

However, there are some curious idioms that sometimes appear in recipe code that are worth explaining. You can think of these as Ruby poking her friendly head out from behind the curtain of the DSL. Here are the most common ones explained.

::Chef::Recipe.send

Occasionally in recipe code you may see something like ::Chef::Recipe.send(:include, Opscode::OpenSSL::Password). What does this mean?

Chef has two phases of execution: a compilation phase and an execution phase. In the compilation phase, the recipes are evaluated as Ruby code, and resources are added to the resource collection. Next, in the execution phase, Chef runs the appropriate provider’s action on each resource. If you’re an object-oriented programmer, you can think of the provider as being the implementation; the resource is the interface. Chef uses the Ohai data to determine what provider to choose for a given resource.

In normal, non-Chef Ruby, you can include other classes in your own by using the include syntax; for example, include Opscode::OpenSSL::Password. Used in a recipe, however, the recipe compiler will try to find a provider for a resource of type “include”. Clearly, this would fail; hence the method shown here, which will use a little bit of Ruby metaprogramming to include the requisite OpenSSL utility class.

What’s with the ::Chef and ::File instead of Chef and File?

In some recipes, you may see ::File used to refer to the Ruby File class. Why isn’t it just “File”?

The reason is because Chef has its own classes named File. Specifically, there are resource and provider classes within Chef called File. To avoid ambiguity — ambiguity leads to errors — whenever we want to refer to Ruby’s File class, we use ::File.

Resource Declarations with end.run_action(:something)

Some recipes have resource declarations that look normal except for something weird: end.run_action(:some_action). They look like this:

package "foo" do
  action :nothing
end.run_action(:install)

This is an indication that the recipe author wants to run the package[foo] resource’s :install action during Chef’s compilation phase. This might be because the resource’s action is required as a pre-requisite for the execution phase. One example is installing certain Ruby gems into Chef itself so that the execution phase can use them. This is such a common use case that we invented a resource, chef_gem, that does exactly this. It replaces older code that looked like this:

gem_package "foo" do
  action :nothing
end.run_action(:install)
Gem.clear_paths

which could be a bit tough to understand, especially with the Gem.clear_paths call (used to tell the Rubygems framework about the newly-installed gem).

def initialize inside LWRPs resource declarations

In some older cookbooks, resource declarations often have a funny method definition like

def initialize(*args)
  super
  @action = :create
end

The LWRP framework makes it easy to create new subclasses of Chef::Resource and Chef::Provider without actually writing Ruby code, just like how Chef recipes are really a way for you to write Chef::Recipe classes, all fronted by a nice DSL. So what’s this initialize method all about?

Prior to Chef 11, there was no way to set the default action for an LWRP in the DSL: the default_action syntax did not exist yet. This bit of idiomatic Ruby ensured that when the DSL was converted to a real Chef::Resource subclass object and instantiated, that the superclass’s initializer would be called with the default action of :create.

As you can see, we’ve now made it infinitely easier to set default actions for your custom resources!

Chef::Log in recipes versus the log resource

Occasionally you may see people using Chef::Log statements in recipe code rather than using the log resource. There is no functional difference between the two; they will both log to Chef’s logger, whose output is generally sent to a file like /var/log/chef/client.log on UNIX/Linux. So why prefer the former versus the latter?

First, we suspect that sometimes this is the result of copy and paste from other people’s recipes. Nevertheless, there are some good reasons to use Chef::Log; for one, it means that log statements don’t actually create a resource in the resource collection. The log resource will always register as a resource that is updated; in other words, the :write action always runs. Sometimes this may not be desirable, particularly if you consider more-than-zero resources updated during normal operation an exceptional condition.

Wrapping Up

We like to think that Chef’s various DSLs allow you to express your infrastructure in a natural way that is self-documenting. No system is perfect, however, which is why you may occasionally see Ruby making a cameo in DSL code. We hope that this post has explained some of the common idioms so that you can become better-informed Chefs!

Author Julian Dunn

Julian is a product manager at Chef & started his career at the company in professional services. His first experience with Chef was at SecondMarket, a New-York based alternative markets startup. He has fifteen years of systems administration & software development experience at outfits large and small across such diverse sectors as advertising, broadcasting, Internet security and construction. When he's not helping customers, he enjoys good craft beer, indie music, and writing biographies about himself in the third person.

  • Teemu Matilainen

    Nice summary!

    Anyway default_action was already introduced in Chef 0.10.10: https://tickets.opscode.com/browse/CHEF-1789

  • Ranjib Dey

    On chef log resource: This is not exactly equivalent to Chef::Log methods, mainly because the log resource is executed on compilation time, while the log resource is invoked on the execution time. It also means, if you want to log something when a resource is updated you can do so using the log resource(by notifying it), this also means the logging details are captured in the run list, and passed to handlers. Doing the same with Chef::Log methods will be cumbersome. Following is a simpler approximation to log resource using the Chef::Log method(s)

    ruby_block "foo" do
    block do
    Chef::Log.info("Foo")
    end
    end

  • Ranjib Dey

    On Chef::Recipe.send(:include, Foo) : this trick is mainly used for mixing in modules. Note, the include is a private method defined in Module, Class inherits it, hence it becomes a private instance method for Class, consequently a static but private method for all the objects, since chef recipes are instance_eval ed, one can not invoke private class methods such as include from inside the recipe. Doing Chef::Recipe.send(:include) is a metaprogramming way to break the encapsulation.

    If all you want is to add a couple of helper methods defined inside a module, you can extend it instead. e.g.

    extend Foo::Bar

    x = m1 # m1 is defined inside Foo::Bar
    template ‘/x/y/z’ do
    variables(:something=> x)
    end

    will extend the module Foo::Bar in the current recipe (unlike the metaprogramming hack which pollutes all the recipe) and one can use the methods defined inside the module any where inside the recipe , but out side the resources. if you want to use the module defined methods inside a resource, you can extend the module inside the resource itself, e.g.

    template ‘/x/y/z’ do
    extend Foo::Bar
    variables(:something=> m1) # m1 is defined inside Foo::Bar
    end