Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 502
Tampa Avatar asked Jul 15 '12 09:07

Tampa


1 Answers

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
like image 198
Holger Just Avatar answered Oct 20 '22 21:10

Holger Just