Brando__ Brando__ - 1 year ago 43
Ruby Question

Ruby accessing nested hash and erb template

I have the following hash that lists what services exist on what machine type and Chef code block. Some of the services have port numbers associated with them.

services_hash = {
"bugs_services" => ["bugs"],
"max_api_services" => ["max-api"],
"max_api_ports_max-api" => 84,
"max_data_services" => ["max-data-http"],
"max_data_ports_max-data-http" => 85,
"max_logger_services" => ["max-analytics", "max-logger"],
"max_logger_ports_max-analytics" => 83,
"max_logger_ports_max-logger" => 82

%w( max_api max_data max_logger ).each do |haproxy_chef_role|
template "/home/ubuntu/ansible/deploy/role_#{haproxy_chef_role}.yml" do
source 'haproxy_services.yml.erb'
owner 'ubuntu'
group 'ubuntu'
action :create
mode 0644
number_of_services: number_of_services,
services: services_hash["#{haproxy_chef_role}_services"],
ports: ???
haproxy_chef_role: haproxy_chef_role

I also have an erb template that resembles this.

<% @services.each do |service| -%>
<% if service.include?("max-logger") %>
shell: for i in {0..<%= @number_of_services %>}; do echo <%= service %>:<%= ports %>$(printf '%02d' $i); done
<% else %>
shell: echo <%= service %>:<%= ports %>00
<% end %>
<% end %>

How can I nest hashes such that a port number is associated to a given service and I can is callable from within the erb template?

I was thinking something like this where I have an array with strings representing the services, and then a hash mapping service to port. I think this will work, but I don't know how to properly extract out the data to fill in my erb template.

services_hash = {
"max_logger_services" => [
{ "max-analytics" => 83, "max-logger" => 82] }

I was trying to do something like this earlier where I interpolate whatever I'm currently processing into another hash query, but it's not working well and seems like the wrong way to go about this.

ports: services_hash["#{haproxy_chef_role}_ports_#{services_hash["#{haproxy_chef_role}_services"]}"],


I've now got the line that returns the hash per Sebastian's answer below which returns the hash relating services to ports.

ports: services_hash['max_logger_services'].inject{|_k,v| v},

In the erb template now though, I don't quite understand how I am supposed to query the hash keys for values. Particularly that I don't seem to be able to nest variables together in an erb.

<% @services.each do |service| -%>
<% if service.include?("max-logger") %>
shell: for i in {0..<%= @number_of_services %>}; do echo disable server <%= service.gsub('max-', '') %>-backend/{{ ansible_fqdn }}:<%= @ports["<%= service %>"] %>$(printf '%02d' $i) | sudo socat stdio /run/admin.sock; done
✗ <% else %>
shell: echo disable server <%= service.gsub('max-', '') %>-backend/{{ ansible_fqdn }}:<%= @ports['<%= service -%>'] -%>00 | sud o socat stdio /run/admin.sock
<% end %>
<% end %>

This line right here seems to the be the problem. The hash is being passed to the erb but I'm not able to drop in the current service being processed into the hash to spit out a value. This is the same fundamental problem I was having that prompted me to ask this question: I can't seem to tell which service I am currently processing to find further data regarding that particular service.

<%= @ports["<%= service %>"] %>

Answer Source

If services_hash had this format:

services_hash = {
  "max_logger_services" => [
    "max-analytics", { 
      "max-analytics" => 83, 
      "max-logger" => 82

Then you could access max-analytics and to max-logger just with:

hash = services_hash['max_logger_services'][2]
p hash['max-analytics']
# => 83
p hash['max-logger']
# => 82

If you don't know if the data will be always formatted the same way, at least you can "dig" until the deeper hash values:

hash = services_hash['max_logger_services'].inject{|_k,v| v}
# => {"max-analytics"=>83, "max-logger"=>82}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download