Managing Your Chef Gem Dependencies More Easily in your Gemfile
When writing Chef cookbooks, it's possible that we're going to want to use a gem as a dependency, for instance to interact with HashiCorp Vault using the vault gem.
To tell Chef that we want to have this gem installed before we perform a Chef run, we need to add it as a gem
to our metadata.rb
:
# other cookbook metadata
gem 'vault', '~> 0.16'
However, this then causes issues for us, as if we want to do any local testing, such as unit testing with ChefSpec, we can't rely on Chef to install these gems, as we're not really executing a full Chef run.
The solution, previously mentioned in my post about Chef's dependency management tools, is that we need to add this i.e. to our Gemfile
, so Bundler can install the dependency:
source '...'
gem 'vault', '~> 0.16'
This then has the problem of our Gemfile
and metadata.rb
becoming misaligned, and can be quite annoying, as we may be testing against a different version than is installed by the cookbook. Fortunately, using the knowledge from my article Programatically Determining the Version of a Chef Cookbook, we can take advantage of the Gemfile
being a Ruby file, and replace the Gemfile
with:
source '...'
require 'chef'
m = Chef::Cookbook::Metadata.new
m.from_file 'metadata.rb'
m.gems.each do |name, version|
gem name, version
end
This allows us to manage our Chef Gem dependencies for Chef and local development with Bundler, and remove the need to keep both updated!
An alternative would be to monkey patch Bundler::Dsl, but this also works as-is, and doesn't require anything else to be globally installed.
I've also raised this upstream with the Bundler team to see their thoughts about this being a first-class citizen.