Clean the kitchen, aka refactor cookbooks and recipes (Part 2)

Last time I described a general approach I used to decrease the amount of duplication across the recipes I developed in different cookbooks. The goals of these latter is to handle the deploy of specific PHP application via OpsWorks.

The PHP application I’m talking about are based in particular on WordPress (WP) and PrestaShop (PS).

Last time I also cited a “mysterious” recipe upon which I relied for setting the default attributes to specialize its behavior.

Actually there are two recipes that I use to this purpose but to describe the concept at their base I’m going to present here just one of them, i.e. phpapps::set_config_files.

If you read the last article of mine (if you don’t go for it! 😉 ) you can guess that the above mentioned recipe resides in the general cookbook phpapps. Or better its implementation.

As a matter of fact the actual recipe that I select and associate to the deploy phase of a PHP layer in OpsWorks isn’t the one residing in the phpapps cookbook but the one defined inside the application specific cookbooks, i.e. wordpress and prestashop.

Inside these cookbooks there is indeed a set_config_files recipe that does nothing more than include the recipe of the “parent” cookbook phpapps.

The purpose of these recipes is to properly create the config files needed by WordPress and PrestaShop applications. These are respectively the wp_config.php and the defines.inc.php and settings.inc.php.

The creation of these files is handled, as shown below, inside the “parent” recipe phpapps::set_config_files:


node['deploy'].each do |application, deploy|

  next if deploy['application_type'] != 'php' or deploy['application'] != application

  deploy['config_files'].to_h.each do |config_file, vars|

    template vars['file'] do
      only_if do
        Chef::Log.info "Set config file #{vars['file']}..."
      end
      cookbook deploy['type']
      source vars['template']
      mode 00660
      owner deploy['user']
      group deploy['group']
      variables vars.merge(deploy['environment'])
    end

  end

end

The recipe does nothing more than loop on the config_files data structure and rely on the template resource provided by the Chef DSL.

There are two peculiarities here to note. The first one is represented by the config_files data structure. As many other data needed by the recipes it is stored as a default attribute inside the specific application cookbook. In particular it is implemented as an hash. Here there is an excerpt of the default.rb file representing the config_files inside the wordpress cookbook:


node['deploy'].each do |application, deploy|
  ...
  default['deploy'][application]['config_files'] = {
    'wp_config' => {
      'file'                  => ::File.join(deploy['absolute_document_root'], 'wp-config.php'),
      'template'              => 'wp-config.php.erb',
      'db_name'               => deploy['database']['database'],
      'db_user'               => deploy['database']['username'],
      'db_password'           => deploy['database']['password'],
      'db_host'               => deploy['database']['host'],
      'auth_key'              => '',
      'secure_auth_key'       => '',
      'logged_in_key'         => '',
      'nonce_key'             => '',
      'auth_salt'             => '',
      'secure_auth_salt'      => '',
      'logged_in_salt'        => '',
      'nonce_salt'            => '',
      'table_prefix'          => 'wp_',
      'aws_access_key_id'     => nil,
      'aws_secret_access_key' => nil,
      'wplang'                => 'en-US',
      'wp_cache'              => false,
      'force_secure_logins'   => false,
      'disable_wp_cron'       => false,
      'wp_siteurl'            => domain,
      'wp_home'               => domain,
      'wp_allow_multisite'    => false,
      'wp_uploads'            => ::File.join('wp-content', 'uploads')
    }
  }  
  ...
end
...

All the data needed to create the configuration files are handled inside the default.rb file and gets passed to the over mentioned template resource inside the general phpapps::set_config_files recipe.

The second peculiarity of the general set_config_files recipe is cookbook deploy['type']. This attribute is used to tell Chef (and OspWorks) to pickup the correct default attributes and template files, i.e. the ones related to the deployed application type.

The cookbook deploy['type'] together with the default attributes allowed us to refactor the specific application set_config_files recipes by moving their common implementation inside a more general cookbook and recipe.

All the specific aspects related to the application configuration files are handled by the default attributes defined inside the specific application cookbook. The implementation of the recipe resides instead inside the general cookbook phpapps.

The key takehome message of this post is that it is possible to avoid code duplication across different cookbooks by leveraging upon the logic of the default attributes, the coobook attribute of the template resource and the inclusion of the recipes (include cookbook_name::recipe).

Next time I’ll discuss another recipe, conceptually similar to the one I briefly presented here, in which I use another feature of Chef to add a little bit of “magic” to the recipes 😉

Cheers!

Leave a Reply

Please Login to comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.