I wanted smartmontools installed to monitor the disk health of my LAN server at home. This is not an uncommon thing to want to do, so I thought I’d write and share a Chef cookbook for it. I also took this opportunity to write up the experience so I can illustrate how easy it is to write a cookbook for Chef.
The first thing to do when writing a cookbook is to create the cookbook directory structure with knife cookbook create
. This command will create a README.rdoc by default, and I prefer Markdown, so I specify the -r md
option.
knife cookbook create smartmontools -r md
By default, metadata and the default recipe are created with boilerplate content for author and copyright. I have configured the values in my knife.rb:
cookbook_copyright "Joshua Timberman" cookbook_license "apachev2" cookbook_email "cookbooks@housepub.org"
The resulting directory structure will be created:
% tree cookbooks/smartmontools cookbooks/smartmontools ├── README.md ├── attributes ├── definitions ├── files │ └── default ├── libraries ├── metadata.rb ├── providers ├── recipes │ └── default.rb ├── resources └── templates └── default 10 directories, 3 files
README Driven Development
I’m a big fan of Tom Preston-Werner’s blog post on README driven development. I don’t write the complete README before I start writing code for a new cookbook. I do write it as I go.
In order to write a proper README for a cookbook, and to write the cookbook itself, we’ll need to know a bit more about the software we’re installing. The best way to do that depends on the software, but often it is as simple as merely installing the package on a test system such as a virtual machine and explore its contents.
apt-get install smartmontools dpkg -L smartmontools
Of note for smartmontools, documentation is in /usr/share/doc/smartmontools
and configuration is in /etc
. In particular, /etc/smartd.conf
and /etc/smartmontools
.
For now assume that the cookbook README.md is being written along the way.
List of Resources
One of the things I do when I am writing a new cookbook and exploring the contents of a package is to be mindful of Chef Resources I want to manage in the recipe(s). In the case of smartmontools, at this point I have determined I need a few specific resources.
Install the Package
First, as I’ve installed the package, I clearly need a package. I’m pretty confident that this particular package will not break backwards compatibility, and can be safely upgraded to the latest version if necessary.
package "smartmontools" do action :upgrade end
Configuration files are templates
The next resources in the recipe are the configuration files. I want to dynamically configure these, so I am going to use templates.
template "/etc/default/smartmontools" do source "smartmontools.default.erb" owner "root" group "root" mode 0644 notifies :reload, "service[smartmontools]" end template "/etc/smartd.conf" do source "smartd.conf.erb" owner "root" group "root" mode 0644 notifies :reload, "service[smartmontools]" end
I’m not going to write these from scratch. Instead, I will copy the source files from the installed package on my test system. These will go into templates/default
in the cookbook.
% tree templates templates └── default ├── smartd.conf.erb └── smartmontools.default.erb
Templates are dynamically generated using ERB, and they can use Node attributes. I can use the automatically detected attributes, or I can set new attributes for the node in the cookbook.
Attributes Used in the Templates
The attributes go in the attributes/default.rb
file in the cookbook. The ones I use are:
default['smartmontools']['start_smartd'] = "yes" default['smartmontools']['smartd_opts'] = "" default['smartmontools']['devices'] = [] default['smartmontools']['device_opts'] = "-H -l error -l selftest"
Attributes are definitely something to document in the README.
In templates/default/smartd.conf.erb
, I check if there’s a list of devices to monitor, and if so iterate over the list passing in the default options (device_opts
). The cookbook doesn’t at this time support per-device options – the same ones are applied to all devices. If devices
is empty, then the configuration will use DEVICESCAN.
<% if node['smartmontools']['devices'].length > 0 -%> <% node['smartmontools']['devices'].each do |device| -%> <%= "/dev/#{device} #{node['smartmontools']['device_opts']}" %>