How do you modularize a Chef recipe?

Janaka picture Janaka · May 7, 2013 · Viewed 6.9k times · Source

Here is an example of a working recipe that loops through an array of website names and creates them in IIS using the function createIisWebsite().

def createIisWebsite(websiteName)
    iis_site websiteName do
      protocol :http
      port 80
      path "#{node['iis']['docroot']}/#{websiteName}"
      host_header  "#{websiteName}.test.kermit.a-aws.co.uk"
      action [:add,:start]
    end
end
In our actual solution this data is stored elsewhere and accessed via a web API.
websiteNames = ["website-2", "website-3", "website-4"]

for websiteName in websiteNames do
    createIisWebsite websiteName
end

Now I want to be able to call the function createIisWebsite() from multiple recipes within this Cookbook.

I have tried throwing it into a helper module (library). There I cannot get the reference to iis_site to work.

I have tried moving the function to default.rb and then doing include_recipe "::default". That does not seem to work either.

I get a "Cannot find a resource for createIisWebsite on windows version 6.2.9200"

The reason I am taking this approach is because I want to have a recipe containing the list of websites per cluster of web servers. I get the feeling I am not taking the best practice route.

Any ideas?

Answer

zts picture zts · May 8, 2013

The problem is that the function is being defined inside a recipe, and can only be used within that recipe. The include_recipe method ensures that a given recipe is loaded, but it doesn't import anything into the recipe doing the including.

Since your function is being used to declare a Chef resource with some calculated parameters, the closest thing to look at is the Definition (Chef Docs). Definitions look similar to Resources, having a name and a set of optional parameters, but are actually simple macros that are expanded into the recipe when it is compiled.

In your cookbook directory, create definitions/my_iis_website.rb containing something like:

define :my_iis_website do
    iis_site websiteName do
        protocol :http
        port 80
        path "#{node['iis']['docroot']}/#{websiteName}"
        host_header  "#{websiteName}.test.kermit.a-aws.co.uk"
        action [:add,:start]
    end
end

Then, replace the loop in your recipe with:

for websiteName in websiteNames do
    my_iis_website websiteName
end

If your recipes for each cluster of server would be identical but for the list of sites, you might want to consider storing this data in attributes or data bags instead. This helps you to avoid repeating yourself in your recipes, and will also allow you to add sites without updating your cookbook.