Thursday, May 30, 2013

Passing values from one Chef resource to another


In a Chef recipe it is sometimes useful to set the value of an attribute in one resource, and then be able to use that value in another resource.  In scripting languages, it is typical that a function will return some value which is then passed to another function.

In the Chef language doing this is not as simple as it sounds -- one cannot simply pass values from one resource to another.

Below I give an example of the typical misconception I've seen and then show you a way to achieve the desired results.

What you might expect 

Here is how one might expect things work:

However, the resource on line 15 will log "!!!!set in compile phase!!!!".

Why? All resource attributes are evaluated in the "compile" phase -- almost like a "pre-processor" step in C.   In other words, the attribute values for each resource were already set before the recipe even started to execute.

This can be a little hard to get your head around if you are just learning Chef.  For more information about the "compile" phase see Anatomy of a Chef Run.

Manipulating the Resource Collection

To achieve the desired results, we're going to rely on the inner-workings of Chef -- namely the resource_collection.  You can think of the resource_collection as a list of all the Chef resources in your run-list that are compiled and ready to be executed.

We will insert a new helper resource into our example recipe which will perform the value passing for us.  Here is our new recipe:

This time, the resource on line 23 will log "!!!!set in converge phase!!!!".  Profit!!

As you can see, the new ruby_block[update_log_message] in step #3 performs the lookup of the log[update_target] from the resource_collection and updates the message with our new attribute value.

That's all there is to it!

Make it idempotent

Now that you get the idea, let's preform our due diligence to make this idempotent -- like every good Chef should:

On line #7, we add an only_if so the ruby_block will only execute if our attribute is still the default value.

We also set the action on line #24 to :nothing, so our helper resource will only execute if the notifies clause on line #13 is triggered. 

In Closing

This idea of modifying the resource_collection is nothing new -- some of the old school (Iron?) Chefs have been doing it for years.  This idea is also the basis for Brian Berry's chef-rewind resource used in the library cookbook pattern outlined in his "How to Write Reusable Chef Cookbooks, Gangnam Style" post.

I just haven't found much documentation explaining how to do it, nor for it's use for solving this particular problem.

If there is another way to solve "passing" values or a technique that avoids the need to pass values, I would love to hear about it.

Follow up

Thanks to Noah and Kannan for the review and the feedback.  You can find some other use cases for this trick here.

I am excited to see that the delayed attribute feature will be available in Chef v10.28 and v11.6.

Also here is the code that I used in the examples -- incase you want to play with it yourself.


1 comment:

Chandana Sapparapu said...

Thanks for this excellent blog post, you saved me a lot of time!