Writing helper cookbooks with shared functions

· by Artem Sidorenko · Read in about 3 min · (610 words)

Sometimes you might need some generic functions, which are used in several cookbooks in your environment. In this case it makes sense to create a cookbook, which contains this functions.

Lets assume you have Ubuntu Linux 14.04, 16.04 and CentOS 7 in your environment managed by Chef.

In some recipes you probably create init configuration for your services. Due to the distro mix, you have to do it for systemd and upstart. Usually you would have to create a complex logic in each cookbook to cover this differences. So it makes sense to create a function init_system in the centralized site-helpers cookbook and to call it from everywhere else.

Chef Docs already cover this topic, however it might be not so easy for Non-Experts in Ruby to understand it.

Create a new cookbook

Create a new cookbook and a folder for libraries.

$ chef generate cookbook site-helpers
$ cd site-helpers
$ mkdir libraries

Create a Mixin Module

In the libraries folder create a file init_system.rb with following content:

# libraries/init_system.rb
module Site
  # This module contains helper functions, which are usefull for init systems
  module InitSystem
    # Determine the underlying init system
    # Returns 'systemd' or 'upstart'
    def init_system
      platform = node['platform']
      platform_version = node['platform_version']
      platform_family = node['platform_family']
      codename = node['lsb']['codename']

      return 'systemd' if platform_family == 'rhel' && platform_version =~ /^7\.[0-9\.]*$/
      if platform == 'ubuntu'
        case codename
        when 'xenial'
          return 'systemd'
        when 'trusty'
          return 'upstart'
        end
      end

      raise "Unsupported platform_version #{platform_version} for platform #{platform} with platform_family #{platform_family}"
    end
  end
end

Chef::Recipe.include(Site::InitSystem)
Chef::Resource.include(Site::InitSystem)

What happens here?

  • We define a module Site::InitSystem. This name and the name of this cookbook have a site prefix, like described in Environment cookbook pattern.
  • We define a function init_system, which returns the type of init system
  • The last two lines are required in order to mixin this module to the Recipe and Resource classes.

Now you can invoke init_system directly in the recipe or in the resource definition like below:

# recipes/default.rb
#
# Cookbook Name:: site-helpers
# Recipe:: default
#
# Copyright (c) 2016 Artem Sidorenko, All Rights Reserved.

file '/tmp/init_system' do
  content init_system
end

In order to get init_system available in other cookbooks, just add site-helpers dependency to the metadata.rb of the according cookbook:

depends 'site-helpers'

Using Chef Resources in the helpers

As you already might notice, the libraries are written in the native Ruby and do not support the Chef DSL. But what if you want to create Chef resources in a such helper function? Yes, its possible. Create libraries/create_init_file.rb with following content:

# libraries/create_init_file.rb
module Site
  module CreateInitFile
    # This function creates a file with init system type as a content
    def create_init_file
      run_context.resource_collection << init_file = Chef::Resource::File.new('/tmp/init_system', run_context)
      init_file.content init_system
      init_file.action :create
    end
  end
end

Chef::Recipe.include(Site::CreateInitFile)

What happens here?

  • We create a new mixin module Site::CreateInitFile and make it available only for recipes in the last line
  • In the create_init_file definition we instantiate a new file resource and add it to the resource collection of our run context. This resource collection contains all defined resources, which are executed by Chef.

As you see, the parametrization of the file resource is quite similar to the Chef DSL in the recipe. Documentation of more resource classes is available on the rubydoc

Then you can just call it in some recipe:

# recipes/default.rb
#
# Cookbook Name:: site-helpers
# Recipe:: default
#
# Copyright (c) 2016 Artem Sidorenko, All Rights Reserved.

create_init_file

In our chef log you will see something like:

$ kitchen converge centos
...
Converging 1 resources
Recipe: <Dynamically Defined Resource>
  * file[/tmp/init_system] action create (up to date)
...

See too