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?
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
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
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
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
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
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
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
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!