Chef and ruby templates - how to loop though key value pairs?

34,637

There are some slight inconsistencies in your setup. In your data bag, you assign IP addresses a name (by using a hash in your JSON). However, you don't seem to use the name in your generated template at all. This has some implications which you should be aware of:

When using associative arrays (called hashes in Ruby or objects in Javascript), the order of the elements is generally not preserved and can significantly change when adding additional elements. While some effort is done on Ruby 1.9 to preserve the insertion order when looping over the hash, you shouldn't generally rely on that. This leads to two possible alternatives to improving your data bag. Which one to choose depends on your actual use case:

  • Use an array instead of the hash. In an array, the order is guaranteed to be kept. If you don't use the name anyway (i.e. the key in your original hash), you can simply use a hash and be safe here. When going this road, we can loop over the array in the template and generate the count from that.
  • If the order doesn't matter, you should use the key in the hash to name the server as generated into your template. Right now, you use server<Number> in your data bag but server.<Number> in your template. That way, we can use the key to name your servers and possibly override the generated names.

Using an Array

When using the array in your data bag, i.e. when you have something like this:

"zookeeper": [
  "111.111.111.111",
  "222.222.222.222"
],

you can loop over the array like this in your template:

<% @zookeeper.each_with_index do |ipaddress, index| %>
<%= "server.#{index}=#{ipaddress}:2888:3888" %>
<% end %>

This used the ERB template language to create your file. It used the each_with_index method to iterate over each element in the array.

Using a Hash

When using the hash variant instead, assuming you have changed the keys in your data bag to match the syntax in your final generated file, you can loop over the hash like this:

<% @zookeeper.each_pair do |name, ipaddress| %>
<%= "#{name}=#{ipaddress}:2888:3888" %>
<% end %>

This uses the each_pair method of the Hash to loop over each key-value pair and thus generates a line of output for each of these pairs.

Passing data to the template

As a final remark, your syntax to pass data to the template in your recipe is odd. At first, you should never use names that start with an uppercase letter for variables (like your ZOOKEEPER variable). In Ruby, these identify constants (like value constants, classes, modules, ...). Use a lowercase name instead. Ruby uses snake_case for variable names by convention.

When passing the value to your template, you can then just pass the variable:

db = data_bag_item("mydb", "rtb")
zookeeper = db['zookeeper']

template "/etc/zookeeper/conf/zoo.cfg" do
  path "/etc/zookeeper/conf/"
  source "zoo.cfg.erb"
  owner "root"
  group "root"
  mode "0644"
  variables :zookeeper => zookeeper
end
Share:
34,637
Tampa
Author by

Tampa

Updated on March 06, 2020

Comments

  • Tampa
    Tampa about 4 years

    1) I have a data bag as follows:

     "zookeeper":{
            "server1":"111.111.111.111",
            "server2":"222.222.222.222"
            },
    

    2) In my recipe I get the hash as follows.

    data_bag("mydb")
    db = data_bag_item("mydb", "rtb")
    ZOOKEEPER = db['zookeeper']
    

    3) Also in my recipe I have a template as follows:

    template "/etc/zookeeper/conf/zoo.cfg" do
      path "/etc/zookeeper/conf/"
      source "zoo.cfg.erb"
      owner "root"
      group "root"
      mode "0644"
     variables :zookeeper => #{ZOOKEEPER}
    end
    

    4) I need to have my template look like this

    server.1=111.111.111.111:2888:3888
    server.2=222.222.222.222:2888:3888
    

    My question is this. How do I pass the hash to the template so I can loop through the hash and create the temlplate? I am not a strong ruby coder.

    for example:

    count = 1
    for server, ipaddress in zookeeper:
          server.count=ipaddress:2888:3888
          count = count + 1
    
  • Tampa
    Tampa almost 12 years
    very very very nice answer. You cleared a lot of questions for me. Thanks
  • Rag
    Rag over 10 years
    Don't you need -%> instead of %> for the "do" and "end" statements?
  • Holger Just
    Holger Just over 10 years
    The minus at erb tags is normally only used to control the insertion of whitespace around the tags. It has no other purpose. As such, it is not needed for the correctness of the loop. Furthermore, iirc chef configures erb to omit whitespace by default so all erb tags are considered to be <%- ... -%> there.