Chef, ruby hashes and templates

Viewed 2928

I know this is more a ruby question than chef, but...

I have some attributes like:

default['my_cookbook']['some_namespace1']['some_attribute1'] = 'some_value1'
default['my_cookbook']['some_namespace1']['some_attribute2'] = 'some_value2'
default['my_cookbook']['some_namespace1']['some_attribute2'] = 'some_value3'
...
default['my_cookbook']['some_namespace2']['some_attribute1'] = 'some_value1'
default['my_cookbook']['some_namespace2']['some_attribute2'] = 'some_value2'
default['my_cookbook']['some_namespace2']['some_attribute2'] = 'some_value3'
...

On the other hand, I am creating a template resource like this:

template 'template_name' do
  source 'template_source.erb'
  variables (
    my_namespace_1: node['my_cookbook']['some_namespace1'],
    my_namespace_2: node['my_cookbook']['some_namespace2']
  )
end

Then in the template_source.erb I try:

...
<%= @my_namespace_1['some_attribute1'] %> #=> 'some_value1'
...

However when I run Kitchen I get this, instead of 'some_value1':

Chef::Mixin::Template::TemplateError
------------------------------------
undefined method `[]' for nil:NilClass

How should I send the template variable to use it this way?

2 Answers

EDIT: This applies only to Ruby in general and not to Chef in particular.

Pass a nested hash:

template 'template_name' do
  source 'template_source.erb'
  variables (
    my_namespace_1: {
      some_attribute1: node['my_cookbook']['some_namespace1']['some_attribute1']
    }
  )
end

But rather than copying the values verbatim you can use the full power of the Hash class to slice, dice and merge together whatever you want:

template 'template_name' do
  source 'template_source.erb'
  variables (
    node['my_cookbook'].slice('some_namespace1', 'some_namespace2')
  )
end

One gotcha in Ruby that you have tripped on is that symbols are usually used as hash keys:

# newer literal syntax
a_hash = {
  foo: 'bar'
}

# or with the older hash-rocket syntax
a_hash = {
  :foo => 'bar'
}

Symbols are extemly efficient since they are interned strings that are stored in table - when comparing symbols you compare the object ID instead of comparing each character in the string.

In fact strings are only really used when you want keys in the hash that are not valid Ruby symbols - like when building a hash of HTTP headers.

Ruby does not treat symbol and string keys indifferently:

{
  foo: 'bar'
}[:foo] 
# => bar

{
  foo: 'bar'
}['foo'] 
# => nil

So to access the passed variable in the template you would use:

<%= @my_namespace_1[:some_attribute1] %>

What you have in the example should be working. I am guessing you have a typo somewhere in your original recipe that you corrected when generic-ifying the code.

Related