RIP chef-rewind 2016-06-15
It’s been a bit since their release, but I wanted to draw attention to some new APIs in
Chef: edit_resource
and delete_resource
. These were both added in Chef 12.10
along with a few other friends.
chef-rewind
has been a mainstay of
the Chef ecosystem for years. It allows modifying resources from wrapper code in
a way that often allowed avoiding forks or special cases in community cookbooks.
The general idea boils down to grabbing the existing Chef::Resource
object
from the global resource collection and then modifying it. We’ll talk about
structural issues later, but the biggest stumbling block with chef-rewind
has been
complications getting the gem installed and loaded so you can use it from
recipe code. With these new APIs in Chef 12.10, we don’t need to worry about
this anymore.
edit_resource
The edit_resource
helper doesn’t match the chef-rewind
syntax exactly, but
it inherits the same concept. It comes in two variants, edit_resource
and
edit_resource!
. The !
version will raise an exception if the requested
resource doesn’t exist, while the non-!
will fall back to creating the
resource for you.
As an example, let’s say we want to tweak a template
resource from a wrapper
cookbook to both reset which Erb template file it will use and to change a
variable being passed in:
include_recipe 'communitycookbook'
edit_resource!(:template, '/etc/myapp.conf') do
source 'other.erb'
cookbook 'wrapper'
variables.update(port: 8080)
end
In this case we opted to use the exception-raising variant so we won’t keep going if the resource isn’t found. This will grab the existing resource object and run our block against it.
We can also use edit_resource
to power simple accumulator patterns with the
non-exception-raising variant. This version will use the block to create the
resource if it can’t be found, so the first time the code runs it will create
the template and each later time it will update it:
def my_helper(value)
edit_resource(:template, '/etc/myapp.conf') do
source 'myapp.erb'
owner 'root'
variables['values'] ||= []
variables['values'] << value
end
end
delete_resource
As before, delete_resource
comes in both exception-raising and vanilla
flavors. In this case the vanilla variant simply does nothing and returns nil
if the resource to be deleted can’t be found. This replaces the unwind
helper
from chef-rewind
. Let’s say we have a community cookbook installing something
via a system package but we want to install our own build instead.
include 'communitycookbook'
delete_resource(:package, 'something')
package 'mycompany-something'
This can also be used with edit_resource
when you want to remove already-queued
notifications.
The Dangers Of Rewind
While these new APIs do remove the complexity of installing and loading a gem,
they don’t entirely address some of the limitations chef-rewind
has. Notably
as more and more logic moves in to custom resources, these APIs don’t allow
peering inside a custom resource or provider.
There is also the general problem of using mutation of global variables, which this effectively is. Mutable globals have been a negative cliché in programming for decades now, and for good reason. Excessive use of this kind of “spooky action at a distance” can lead to un-debuggable and un-reabable code.
Tread lightly.
We Have To Go Deeper
While edit_resource
and delete_resource
cover the high-level functionality
from chef-rewind
, there are some other niche helper methods worth mentioning
for low-level manipulation tasks.
find_resource
The find_resource
helper is used by edit_resource
, and can be used to
do a “find existing or create” if you don’t want to edit the resource if it
already exists.
res = find_resource(:template, '/etc/myapp.conf') do
source 'myapp.erb'
end
If no block is given, this will return nil
. A find_resource!
variant is
available as well, which is equivalent to the older resources()
helper, though
a bit nicer arguments and clearer to read.
declare_resource
The declare_resource
helper is the core of the resource DSL. It creates the new
resource object and then adds it to the current resource collection. This can be
useful when you want to create a resource where the type is variable somehow.
declare_resource(:template, '/etc/myapp.conf') do
source 'myapp.erb'
end
type = value_for_platform_family(debian: :deb, rhel: :rpm)
declare_resource(:"#{type}_package", 'myapp') do
source "/myapp.#{type}"
end
with_run_context
Possibly the most niche of these extra APIs, with_run_context
allows swapping
the “current” run context for the scope of a block. You can pass it :root
to
get the top-level run context, :parent
to get the parent of the current
context, or a Chef::RunContext
object. This should probably never be used from
anything except very intricate meta-code but it is available. Some examples of
potential use cases include global accumulators or definition-like macro resources.
tl;dr
Use edit_resource
instead of rewind
and delete_resource!
instead of
unwind
. Profit.
Thanks again to Bloomberg for supporting my Chef community work.