access nested node attributes in a chef recipe

If you use chef bookmark this page as you will need to access nested keys at some point.Chef uses ohai to build a hash of each host chef-client is installed on.In a recipe this is stored in a hash named node.If you want to access a value for a key its simple.

ip = node['ipaddress']

Also if you want to determine if a key exists before you try and access it you can use attribute?()

node.attribute?('ipaddress')

BTW in most cases you will want to make sure the key exists because if it doesn’t chef-client will throw an error.

For nested keys its a bit more difficult, first of all you should always make sure the keys exists. This involves using the has_key? method in nested if statements.Then you can just pull the value from the keys.Below is one way to do it in a recipe.In this example I am making sure the keys filesystem>/dev/sda6>mount exist in the node hash.Then once I’m sure the hash exists I pull out the value.

if node.has_key? "filesystem"     if node["filesystem"].has_key? "/dev/sda6"        if node["filesystem"]["/dev/sda6"].has_key? "mount"           if node['filesystem']['/dev/sda6']['mount'] == '/srv'                execute "foo" do                command "touch /tmp/nested_keys_exist!"                action :run              end           end       end     end  end

Managing different environments with chef attributes and a case statement.

Like everyone else using chef I manage several environments such as dev, staging, testing & production. Many times the only difference is a single config file( for me that is most daemons such as mongo, mysql, redis, varnish, etc….)There are several ways of doing it. One way is to use templates( which I find to be confusing and more than what I need).Another way is to create a different recipe for each environment ( mongo.dev, mongo.prod ,etc….) This seams like too much work.What I found to be the simplest and cleanest way is to use the case statement with attributes.Don’t forget the creators of chef( opscode ) allow you to exit the DML and just write ruby so take advantage of it.
Lets so you have a recipe to install and configure mongo.The entire recipe is the same except for the config file.This can be easily managed using attributes and case statement.First create an empty role for each of the different environments (dev, prod, etc…), then assign them the same attribute in this case app_environment with a different value for the environment.In json this looks like this:

{"overrides":{},"defaults":{"app_environment":"dev"}}

Then in the recipe use the case statement to determine which config file the node gets:

cookbook_file "/etc/mysql/my.cnf" dosource_file = case node[:app_environment]when "dev" then "my.cnf.dev"when "prod" then "my.cnf.prod"endsource source_filenotifies :restart, "service[mysql]", :immediately mode "0644"owner "root"end