Chef

Table Of Contents

About Libraries

A library allows arbitrary Ruby code to be included in a cookbook, either as a way to extend the classes used by the chef-client or to implement a new class directly. A library is defined in /libraries/library_name.rb for each cookbook. A library that is included in a cookbook is automatically required and will be available to all recipes, attributes, file definitions, providers, and definitions. The contents of a library will determine the potential uses of that library in a cookbook.

A library can be used to:

  • Access attributes that are stored in files
  • Do basic programming techniques, such as a loop
  • Create a custom namespace that can be called directly from any recipe (which also helps keep the Chef::Recipe namespace clean)
  • Connect to a database
  • Talk to an LDAP provider
  • Do anything that can be done with Ruby

Syntax

The basic syntax of a library:

your_cookbook/libraries/your_example_library.rb
# define a module to mix into Chef::Recipe::namespace
module YourExampleLibrary
  def your_function()
    # ... do something useful
  end
end

your_cookbook/recipes/default.rb
# open the Chef::Recipe class and mix in the library module
class Chef::Recipe::namespace
  include YourExampleLibrary
end

your_function()

Note

In the preceding example, the ::namespace part of the Chef::Recipe::namespace syntax should only be used when a custom namespace has been added that extends the default libraries.

Examples

The following examples show how to use cookbook libraries.

Create a Namespace

A database can contain a list of virtual hosts that are used by customers. A custom namespace could be created that looks something like:

# Sample provided by "Arjuna (fujin)". Thank you!

require 'sequel'

class Chef::Recipe::ISP
  # We can call this with ISP.vhosts
  def self.vhosts
    v = []
    @db = Sequel.mysql(
      'web',
      :user => 'example',
      :password => 'example_pw',
      :host => 'dbserver.example.com'
    )
    @db[
      "SELECT virtualhost.domainname,
           usertable.userid,
           usertable.uid,
           usertable.gid,
           usertable.homedir
       FROM usertable, virtualhost
       WHERE usertable.userid = virtualhost.user_name"
      ].all do |query|
      vhost_data = {
        :servername   => query[:domainname],
        :documentroot => query[:homedir],
        :uid          => query[:uid],
        :gid          => query[:gid],
      }
      v.push(vhost_data)
    end
    Chef::Log.debug("About to provision #{v.length} vhosts")
    v
  end
end

After the custom namespace is created, it could then be used in a recipe, like this:

ISP.vhosts.each do |vhost|
  directory vhost[:documentroot] do
    owner vhost[:uid]
    group vhost[:gid]
    mode 0755
    action :create
  end

  directory "#{vhost[:documentroot]}/#{vhost[:domainname]}" do
    owner vhost[:uid]
    group vhost[:gid]
    mode 0755
    action :create
  end
end

Extend a Recipe

A customer record is stored in an attribute file that looks like this:

mycompany_customers({
  :bob => {
    :homedir => "/home/bob",
    :webdir => "/home/bob/web"
  }
}
)

A simple recipe may contain something like this:

directory node[:mycompany_customers][:bob][:webdir] do
  owner "bob"
  group "bob"
  action :create
end

Or a less verbose version of the same simple recipe:

directory customer(:bob)[:webdir] do
  owner "bob"
  group "bob"
  action :create
end

A simple library could be created that extends Chef::Recipe::, like this:

class Chef
  class Recipe
    # A shortcut to a customer
    def customer(name)
      node[:mycompany_customers][name]
    end
  end
end

Loop Over a Record

A customer record is stored in an attribute file that looks like this:

mycompany_customers({
  :bob => {
    :homedir => "/home/bob",
    :webdir => "/home/bob/web"
  }
}
)

If there are many customer records in an environment, a simple recipe can be used to loop over every customer, like this:

all_customers do |name, info|
  directory info[:webdir] do
    owner name
    group name
    action :create
  end
end

A simple library could be created that extends Chef::Recipe::, like this:

class Chef
  class Recipe
    def all_customers(&block)
      node[:mycompany_customers].each do |name, info|
        block.call(name, info)
      end
    end
  end
end

Template Helper Modules

A template helper module can be defined in a library. This is useful when extensions need to be reused across recipes or to make it easier to manage code that would otherwise be defined inline on a per-recipe basis.

template "/path/to/template.erb" do
  helpers(MyHelperModule)
end