NOAH KANTROWITZ

Eight Short Chef Tips 2015-09-24

As a perk for my Kickstarter backers, I sent postcards with some helpful Chef tips. While those cards will forever be collectors items, I want to share the tips with you all as they have proven a useful reference for new Chef users!

Tip #1: platform? and platform_family?

Tip 1: platform? and platform_family?

In recipe code, you can use platform? and platform_family? to examine the current system:

if platform?('centos')
  execute 'scl enable ruby200 "ruby install.rb"'
end

if platform_family?('debian')
  package 'openssl-dev'
end

file '/etc/motd' do
  user 'root'
  content 'Welcome!'
  only_if do
    platform?('ubuntu')
  end
end

Tip #2: Lazy Resource Attributes

Tip 2: Lazy Resource Attributes

Using the lazy helper with resource attributes you can delay computing the value until the converge phase:

package 'openssl'

file '/etc/ssl/version' do
  extend Chef::Mixin::ShellOut
  content lazy {
    shell_out!('openssl version').stdout
  }
end

Tip #3: Mini Recipe DSLs

Tip 3: Mini Recipe DSLs

Add often used values or snippets of code to your recipe as DSL methods:

# libraries/default.rb
module MyDSL
  def api_url
    "https://#{node['api_host']}:#{node['api_port']}"
  end
end

And then add it to the recipe:

extend MyDSL

log "url is #{api_url}"

Or add it to a single resource:

template '/etc/app.conf' do
  extend MyDSL
  variables url: api_url
end

Tip #4: Chef’s HTTP Client

Tip 4: Chef's HTTP Client

Chef has an http_request resource for making fire-and-forget API calls, but sometimes you want to fetch some data and use it:

template '/etc/app.conf' do
  variables({
    my_id: Chef::HTTP.new('https://cmdb/').get('/')
  })
end

This automatically gets the same TLS verification settings as the rest of chef-client and handles HTTP redirections.

Other available methods:

get(path, [headers])
post(path, data, [headers])
head(path, [headers])

Tip #5: Use %{} for Derived Attributes

Tip 5: Use %{} for Derived Attributes

Using one node attribute in the value of another is convenient in many cases:

default['version'] = '1.0'
default['url'] = "https://download/#{node['version']}"

However this can cause problems when trying to override just the first, as the value of the second has already been created. Instead we can use % string formatting to delay
 interpolation until after all
 overrides are processed:

# attributes/default.rb
default['version'] = '1.0'
default['url'] = "https://download/%{version}"
# recipes/default.rb
remote_file '/tmp/app.zip' do
  source node['url'] % {
    version: node['version']
  }
end

Tip #6: Custom Template Sources

Tip 6: Custom Template Sources

By default Chef lets you easily add per-host or per-OS overrides for template source files, but you can add your own categories:

template '/etc/app.conf' do
  source [
    "host-#{node['fqdn']}/app.erb",
    "app-#{node['app_name']}/app.erb",
    "default/app.erb",
    "app.erb",
  ]
end

You could then put a per-app override in “app-foo/app.erb” and Chef will pick it up. Each item in the list is tried and the first one that exists will be used. This works with cookbook_file as well.

Tip #7: Access Chef Data From Scripts

Tip 7: Access Chef Data From Scripts

Chef is an awesome configuration management tool to start with. Adding Chef Server gives you nice workflow advantages and gives you an API for all your Chef data. Nodes, roles, bags, and more are available via an HTTP call:

# Ruby: chef-api
ChefAPI.configure do |c|
  c.endpoint = 'https://...'
  c.client = 'name'
  c.key = '~/.chef/name.pem'
end
include ChefAPI::Resource
Node.each do |node|
  puts node.name
end
# Python: PyChef
import chef
from chef import Node
api = chef.autoconfigure()
for node in Node.list(api):
  print node.name

Tip #8: Debug Chef Attributes

Tip 8: Debug Chef Attributes

Sometimes it can be unclear at which precedence level a node attribute is being set. The debug_value function helps shed some light on this:

require 'pp'
# For node['foo']['bar']
pp node.debug_value('foo', 'bar')
# Output
[["set_unless_enabled?", false],
 ["default", "attributes default"],
 ["env_default", :not_present],
 ["role_default", "role default"],
 ["force_default", :not_present],
 ["normal", "attributes normal"],
 ["override", "attr override"],
 ["role_override", "role override"],
 ["env_override", :not_present],
 ["force_override", :not_present],
 ["automatic", :not_present]]

Looking for an engineer? I'm looking for a new opportunity!

Back to articles