I have some code that sets node attributes based on a search. I then want to use those attributes in a template. It appears the template is compiling before my code that figures out the values. So it takes 2 runs of chef-client to get to the state I want
if !node['foo']
search(:node, "recipes:bla").each do |bla|
if bla['bla'] > node['foo']
node['foo'] = bla['bla']
end
end
end
template "/tm/foo" do
source "foo"
end
I've tried putting this code in the recipe before and after the template, and in the attributes file. I don't think search
has an action or else I could try using .run_action() on it.
Is there any way to get the value of node['foo']
to be set so that it will be used in the template?
EDIT: Clarification
cook_a/attributes/default.rb
default['cook_a']['val_1'] = node['someval']
cook_a/recipes/default.rb
template "/etc/cook_a.conf" do
source "cook_a.conf.erb"
end
cook_a/templates/default/cook_a.conf.erb
some_var = <% node['cook_a']['val_1'] %>
Now over in another cookbook I'm overriding that value
coob_b/recipes/default.rb
node.set['someval'] = "foo"
include "cook_a"
But now its too late to change node['cook_a']['val_1']
and so the template writes the original value of node['someval']
during the first run. During the second run I get the correct value.
I'm reluctant to be setting node['cook_a']['val_1']
as that name may change and I'm trying to abstract away from the details of cook_a.
This is a little hard to understand, due to the overuse of
bla
, and the lack of what the template is using.Assuming the template has a statement in it similar to:
And that you are not getting the overriden value of
node['foo']
in the template (which seems to be desired asbla['bla']
?) There's a couple of ways I would set about this.Chef should actually be warning you when attempting to replace the value of
node['foo']
in a recipe with no attribute precedence level.So in your recipe you could change that approach to use
node.default['foo'] = ...
to set the result at an explicit attribute level.Expand the node attribute to a Ruby variable, and pass that to the template explicitly, to prevent overrides and compiletime vs rendertime conflicts.
This would look something like this:
And the template would change to look like:
There are other ways to write the conditional logic, but overall it means that we expand node attributes in the recipe, and pass along only the variables we want to the template, not more node attributes.
Finally, something that concerns me with this recipe is that the search is conditional on
node['foo']
returning a nil value, and then the same known nil value is used as a comparison within the assignment block. I don't think this does much, as if entering the block, it will always be nil, therefore the comparison doesn't do much.EDIT:
Post-question clarification, I believe you are running into the "chicken and egg" problem with compiletime vs rendertime attribute evaluation, as previously stated.
The only reason the correct value is being retrieved during the second run is due to the
node.set
action saving the attribute in the node object itself on Chef Server, which means that setting anything in attributes or overrides anywhere later on won't matter, since a node attribute typically wins these, as it is most explicit.Be aware about how these are set and how to remove them - they can cause confusion if abused. Read up on Attribute Precedence.
On to your attribute.
The attributes files are loaded in order of dependencies and lexical sort, so cook_b will always be evaluated after cook_a, and you are probably better off moving the
node.set
to cook_b's attributes/default.rb as anoverride
attribute, and ensure the attributes files are loaded in correct order.You might also want to try forcing the attribute to be lazy evaluated when rendering the template, like so:
cook_a/recipes/default.rb
cook_a/templates/default/cook_a.conf.erb
More on Lazy Attribute Evaluation here.