How to write to a cloned node attribute


#1

I’ve written a provider and have a method to expand some data. However, even after I clone it, it still errors:

316     # Expand an array of hashes whose values may contain arrays into an array of hashes of strings
317     # This returns empty if an invalid data structure is given. the array within the hash must only contain strings.
318     # Expand [{'a' => 'aaa', 'b' => ['bbb', 'ccc'],...},...]
319     # Into [{'a' => 'aaa', 'b' => 'bbb'}, {'a' => 'aaa', 'b' => 'ccc'}]
320     def expand_rules(ruleset)
321       return unless ruleset.is_a?(Array)
322       rules = ruleset.clone
323       rules.each do |rule|
324         rule.each do |key,value|
325           if value.is_a?(Array)
326             value.each do |inel|
327               return unless inel.is_a?(String)
328               temp = rule.clone
329               temp[key] = inel
330               rules.push(temp)
331             end
332             break
333           end
334         end
335       end
336       # Remove temporary and oroginal data that should have been expanded
337       rules.reject{|rule| rule.any? {|key,value| value.is_a?(Array)}}
338     end

Which results in this stack trace:

Chef::Exceptions::ImmutableAttributeModification
------------------------------------------------
Node attributes are read-only when you do not specify which precedence level to set. To set an attribute use code like `node.default["key"] = "value"'

Cookbook Trace:
---------------
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:328:in `initialize_clone'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:328:in `clone'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:328:in `block (3 levels) in expand_rules'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:326:in `each'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:326:in `block (2 levels) in expand_rules'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:324:in `each'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:324:in `block in expand_rules'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:323:in `each'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:323:in `expand_rules'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:245:in `insert_rules'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:219:in `block in action_compile'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:185:in `each'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:185:in `action_compile'
/var/chef/cache/cookbooks/icam_ipt/libraries/ipt_save.rb:113:in `action_all'

I found this, but I’m wondering if there’s a better way to accomplish this:


#2

Main thing is node['whatever'] is read-only (frozen) and should not be modified. clone does copy this state, so you can’t modify the input of your method.

To modify it you’ll have to call node.default['whatever'] =, node.override['whatever'] = or node.set['whatever'] =.
You’re not giving any clue on how you call this method and why you’re doing things this way, so it’s hard to give an advice on how to tackle the real problem.

Edit after diggin in the code: using .dup instead of clone should work.

ref: https://github.com/chef/chef/blob/master/lib/chef/node/immutable_collections.rb