Merging two chef attributes

1.3k Views Asked by At

Two different files in our repository contain these lines:

# library_cookbook_1/attributes/default.rb
default[:library_cookbook_1][:foo] = true
# library_cookbook_2/attributes/default.rb
default[:library_cookbook_2][:foo] = true

I would like to merge both of the attributes. It isn't enough to simply set one equal to the other, because they are both used interchangeably. My greatest concern is that somebody makes this mistake:

"environment_json_attributes": {
    "library_cookbook_1": {
        "foo": false //Now it's false half of the time...
    }
}

Forgetting to set the value of both variables in our code is a fatal mistake under our current structure. I want to merge the attributes in a way that maintains the override hierarchy, so that if library_cookbook_1 has a role level override, but library_cookbook_2 has an environment level override, chef will handle the overrides as if the attributes were just one attribute.

The ideal merging code would look something like this:

> node.attributes.debug_value('library_cookbook_1', 'foo')
{'precedence1' => true, 'precendence3' => false}
> node.attributes.debug_value('library_cookbook_2', 'foo')
{'precedence1' => false, 'precendence2' => true}

> attr_merge(['library_cookbook_1', 'foo'], ['library_cookbook_2', 'foo'])
> node.attributes.debug_value('library_cookbook_1', 'foo')
{'precedence1' => true, 'precendence2' => true, 'precendence3' => false}
> node.attributes.debug_value('library_cookbook_2', 'foo')
{'precedence1' => true, 'precendence2' => true, 'precendence3' => false}

> node[:library_cookbook_1][:foo].precedence4 = true
> node.attributes.debug_value('library_cookbook_2', 'foo')
{'precedence1' => true, 'precendence2' => true, 'precendence3' => false, 'precedence4' => true}

Ideally, I want each attribute to be a pointer to the same thing, so that way, future calls and assignments apply to both.

Now, the obvious answer is to simply refactor away one of the attributes. Unfortunately, chef makes refactoring attributes difficult - we have 10 different environments, 7 different roles, 20 nodes per environment, etc. We're sure to make a mistake that will surface in subtle ways if we try to refactor the whole thing.

2

There are 2 best solutions below

0
On

We're sure to make a mistake that will surface in subtle ways if we try to refactor the whole thing.

You need to fix this first! You need to have enough automated testing to have confidence in changes. If you worry about making changes then you will get bogged down and never get anywhere with Chef.

Use ChefSpec to quickly unit test your cookbook code (without applying any of the changes)

Maybe you need something like Vagrant or a chefdev environment with VM's to test fully converging nodes with your new cookbooks before your real environments.

As a side note, you might want to stay away from roles too, in multi environment setups.

the obvious answer is to simply refactor away one of the attributes

You are going to need refactoring no matter what method you choose as Chef doesn't support what you want out of the box. Adding in non obvious kludges for fear of changing things will only end up biting you later.

So, your obvious answer is going to be my answer....

Refactor the cookbooks to use a single attribute. Single attributes do precedence quite well already.

0
On

Refactor the cookbook that uses the attributes to use a single attribute instead. Then add some code that if either of the original attrs are set to something other than nil (or a sentinel) that it aborts the Chef run. Might be mildly annoying for the first hour or so but you'll very quickly find where the old values are set.

Failing that, make a cookbook that takes a single attribute and sets a force_override of the two old values based on it. This would mean the new value is always going to win if there is a conflict.