I’m happy to announce v1.0.0 of the zap cookbook.

In keeping with semver, v1.0.0 contains breaking changes. These were necessary to support custom resources.

  • Instead of classes, register now takes resource name(s) and an optional block to translate the resource into an identifier.
  • zap_firewall has been removed as the firewall cookbook manages the rules as a whole.
  • zap_apt_repos has been removed as resource. Use the zap::apt_repos recipe instead.
  • zap_yum_repos has been removed as resource. Use the zap::yum_repos recipe instead.

The best way to describe the changes is to walk through a couple of the included recipes.

The recipe zap::iptables_d is used in conjunction with iptables cookbook. Given iptables_rule 'foo', the file /etc/iptables.d/foo will be generated.

So let’s dig into the recipe:

zap 'iptables_d' do
  action :disable

  register :iptables_rule

  collect do
      .map { |path| File.basename(path) }

collect takes a block that will be executed to “collect” all of the existing resources.

Given the following tree:

└── etc
    └── iptables.d
        ├── https
        ├── obsolete
        └── ssh

['https', 'obsolete', 'ssh'] will be returned.

register is passed an array of resource names that zap will look for in the resource collection. When zap prunes resources on disk, it will create an instance of the first resource name registered to :delete that on-disk resource.

To “delete” an iptables_rule, one need specifies a :disable action.

Back to the above example, calling collect we determined

existing = ['https', 'obsolete', 'ssh']

and walking through the resource collection, we determined

desired = ['https', 'ssh']

so ‘obsolete’ was determined to be extraneous. zap will add a resource as if

iptables_rule 'obsolete' do
  action :disable

was specified in a recipe.

Many resources do more than just delete the file. In this case, flush the rule set from the kernel so we cannot simply just rm the file.

Next let’s look at the zap::cron_d recipe used inconjunction with the cron cookbook.

zap 'cron_d' do
  register :cron_d do |r|
    # sanitized_name
    r.name.tr('.', '-')

  collect do
      .map { |path| File.basename(path) }

When cron_d creates the on-disk file, dots are translated into dashes. So when collect discovers ‘foo-bar’, it has no idea if

cron_d 'foo-bar' do


cron_d 'foo.bar' do

was specified.

We solve this dilemma by passing an optional block to register. When zap encounters cron_d 'foo.bar' in the resource collection it will return 'foo-bar' to match what collect discovers on disk.

Finally, let’s look at zap::yum_repos:

zap '/etc/yum.repos.d' do
  register :yum_repository

  collect do
      .map { |path| File.basename(path, '.repo') }

Since yum_repository 'chef-stable' will create /etc/yum.repos.d/chef-stable.repo we do the additional .map to extract the corresponding name of the resource.

In all of the above example, we did a glob One set the appropriate node attribute if you wanted to restrict zap to a particular namespace, i.e. zap only the acme-* yum repos.

There are a couple other advanced features that one can leverage when building a zap.

For the zap_crontab you specify which user’s crontab you want to zap.

register :cron do |r|
  r.name if r.user == @name

When zap walks the resource collection and finds a cron, it will return r.name is user matches. If not, nil is returned and zap will skip that resource.

Finally there is a purge callout if you need to do anything special to remove the resource.

purge do |id|
  r = Chef::Resource::Cron.new(id, @run_context)

Whew, I know this is a lot to digest. While it may seem complicated, zap can be hugely powerful tool in your toolbox.

In closing:

Stay Calm. There's a zap for that