Search code examples
rubychef-inframetadatachef-recipe

Cookbook dependencies based on chef version


I have a wrapper cookbook that uses resources from other cookbooks, but they have different constraints. I tried defining my metadata.rb as follow :

name 'yp_linko'
maintainer 'The Authors'
maintainer_email '[email protected]'
license 'all_rights'
description 'Installs/Configures yp_linko'
long_description 'Installs/Configures yp_linko'
version '1.3.4'

depends 'apt'
if chef_version '<= 12' then
  depends 'ypg_tomcat'
else
  depends 'yp_tomcat'
end

This didn't work as chef grabbed both cookbooks during convergence. I tried a couple different syntax (only_if, unless, etc...) and none worked. Anyone has ideas to work around this issue?


Solution

  • TL;DR

    You're using a constraint from the Chef DSL in a way that it's not meant to be used. You need to branch based on a Boolean expression, not a metadata constraint. I provide a couple of ways to do that using String#to_f as examples (not recommended if you care about patch levels in semantic versioning), as well as the more accurate but often-overlooked Gem::Version.

    Don't Use a Constraint for Branching

    if chef_version '<= 12'

    You're trying to use a constraint from the DSL. This constraint has a specific purpose: to declare the chef-client versions supported by the cookbook, not to provide logical branching. Without looking at the underlying Chef code for the DSL, I'd say it's unlikely that the expression is feeding your if-then expression the way you expect. Setting aside for the moment whether this is the right pattern to follow at all, you can try grabbing the current version of your Chef tools in a couple of different ways:

    1. Branching on the chef-client version, likely cast as a Float (it's normally a String). For instance:

      if Chef::VERSION.to_f <= 12
      
    2. Get the node value from ohai with something like:

      if node['chef_packages']['chef']['version'].to_f <= 12
      
    3. Parse the value directly from the client, e.g.:

      depends %x(chef-client --version).split[1].to_f <= 12 ? 'ypg_tomcat' : 'yp_tomcat'
      

    However, in all cases, you're going to have to deal with the fact that you're being passed a String containing semantic versioning, not a Float or Integer. So, you'll have to figure out how you really want to parse the information, which is potentially error-prone (see below for a trick using Gem::Version, though). In any case, once you've parsed it the way you want, you can match it using a comparison operator to get the branching behavior you want.

    Better Options

    Rather than trying to make the metadata constraint hold business logic, you should probably move the data out to an attribute. Consider an attribute such as node['yp_linko']['tomcat_cookbook'], which you can set based on some other detectable node value other than semantic versioning.

    Another approach would be to declare both cookbooks a dependency, and then include the one you want inside a recipe in your yp_linko cookbook. For example, assuming you haven't declared incompatible chef-client versions in the Tomcat cookbooks:

    # metadata.rb
    depends 'yp_tomcat'
    depends 'ypg_tomcat'
    
    # default.rb
    if Chef::VERSION <= Gem::Version.new(12)
      include ypg_tomcat::default
    else
      include yp_tomcat::default
    end
    

    And finally, you should consider whether it really makes sense to be running different versions of your Chef clients within the infrastructure in the first place. There may be a business need to do that, but it's the real problem you're actually trying to solve for. Branching in your cookbooks is an X/Y solution to an infrastructure issue. It seems likely that you'd have other cookbooks with similar issues, so it's at least worth considering whether it makes more sense to get all your clients on the same version rather than solving the problem at the cookbook level.