Re: template variables arguments and methods


#1

I have to correct my question. #3 doesn’t work either. All my @bar sections in my erb are blank when rendered.

Why won’t methods defined on the recipe evaluate before passing control to the ERB?

Brandon Richins

From: Brandon Richins <Brandon.Richins@imail.orgmailto:Brandon.Richins@imail.org>
Reply-To: "chef@lists.opscode.commailto:chef@lists.opscode.com" <chef@lists.opscode.commailto:chef@lists.opscode.com>
Date: Monday, March 9, 2015 at 4:29 PM
To: "chef@lists.opscode.commailto:chef@lists.opscode.com" <chef@lists.opscode.commailto:chef@lists.opscode.com>
Subject: [chef] template variables arguments and methods

I have a question about Chef templates, variables arguments, and methods. When using templates in a recipe, I sometimes want to use a method instead of a local variable as the value to variable key. ex. def baz { ‘baz’ } vs baz = ‘baz’. When I try to use baz as a method in the template variables section, using the syntax of #1 and #2, Chef fails to converge the template and raises an undefined method error for baz. For #1 and #2 if baz is a local variable in the template it works fine. Today I discovered syntax #4 which worked when baz is a method.

I’m going to show my own ignorance about Ruby now. I understand { bar: ‘b’ } to be a Hash in Ruby. So I think #1 is passing a Hash to a “variables” method on a template object. For #2 I was guessing it was coercing the list of arguments into a Hash (kind of like varargs in Java). For #3 I was guessing it’s evaluating the outer {} as a block and the inner {} as a Hash. Can you confirm these assumptions?

So why do #1 and #2 raise an undefined method error and #3 works. It seems the Chef/Template DSL is treating methods differently than local variables when it comes to templates.

def baz
’baz’
end

  1. Parentheses then braces

template ‘foo’ do
variables({
bar: baz,
a: ‘b’,
})
end

  1. Parentheses and no braces

template ‘foo’ do
variables(
bar: baz,
a: ‘b’,
)
end

  1. Double braces

template ‘foo’ do
variables{{
bar: baz,
a: ‘b’,
}}
end

Thanks,

Brandon Richins


#2

I’ve done a bit of digging on this. Because of the way Chef is using
instance_eval, only local variables (and not methods) from recipes are
available to resource blocks.

For a more thorough discussion of how instance_eval works in this case,
this is pretty much the definitive article:

http://www.skorks.com/2013/03/a-closure-is-not-always-a-closure-in-ruby/

There is a caveat to this, however. The “correct” work is being done by
chef to ensure that provider methods are available to resource blocks.

In the recipe dsl, we have an explicit check to ensure that providers and
only providers are given this privilege:

enclosing_provider: self.is_a?(Chef::Provider) ? self : nil

And there is accompanying method_missing in resource to check if the
provider responds to that method and passes the call through if it does.

Is there a reason we couldn’t generalize this, so that DRY recipes with
methods could be used? All that would have to happen is a little name
hygeine, changing enclosing_provider to something like enclosing_context
and changing the recipe dsl to:

enclosing_context: self

Another option is to add an enclosing_context instance variable to the
resource class, and then have the resource builder pass in the original
"self" that the resource block closure was created with. Something like:

resource.enclosing_context = block.binding.eval "self"

Then you just have method missing in the resource class check both the
enclosing_provider and enclosing_context for the method.

I have hit the need for recipe methods so many times, and cursed as I had
to implement things as local variables instead. It really would only take
changing 6-10 lines of code to have Chef support them.

Is this writing up an RFC/Patch for? Has it any chance of being accepted?

-Ben Bytheway

On Mon, Mar 9, 2015 at 4:45 PM, Brandon Richins Brandon.Richins@imail.org
wrote:

I have to correct my question. #3 doesn’t work either. All my @bar
sections in my erb are blank when rendered.

Why won’t methods defined on the recipe evaluate before passing control
to the ERB?

Brandon Richins

From: Brandon Richins Brandon.Richins@imail.org
Reply-To: "chef@lists.opscode.com" chef@lists.opscode.com
Date: Monday, March 9, 2015 at 4:29 PM
To: "chef@lists.opscode.com" chef@lists.opscode.com
Subject: [chef] template variables arguments and methods

I have a question about Chef templates, variables arguments, and methods.
When using templates in a recipe, I sometimes want to use a method
instead of a local variable as the value to variable key. ex. def baz {
‘baz’ } vs baz = ‘baz’. When I try to use baz as a method in the template
variables section, using the syntax of #1 and #2, Chef fails to converge
the template and raises an undefined method error for baz. For #1 and #2
if baz is a local variable in the template it works fine. Today I
discovered syntax #4 which worked when baz is a method.

I’m going to show my own ignorance about Ruby now. I understand { bar:
‘b’ } to be a Hash in Ruby. So I think #1 is passing a Hash to a
“variables” method on a template object. For #2 I was guessing it was
coercing the list of arguments into a Hash (kind of like varargs in Java).
For #3 I was guessing it’s evaluating the outer {} as a block and the inner
{} as a Hash. Can you confirm these assumptions?

So why do #1 and #2 raise an undefined method error and #3 works. It
seems the Chef/Template DSL is treating methods differently than local
variables when it comes to templates.

def baz
’baz’
end

  1. Parentheses then braces

template ‘foo’ do
variables({
bar: baz,
a: ‘b’,
})
end

  1. Parentheses and no braces

template ‘foo’ do
variables(
bar: baz,
a: ‘b’,
)
end

  1. Double braces

template ‘foo’ do
variables{{
bar: baz,
a: ‘b’,
}}
end

Thanks,

Brandon Richins


#3

Probably doesn’t even need an RFC, just an issue/PR may suffice. Nice
sleuth work. I’d support it. I know of at least a few cases where
methods added to the Recipe via helper would benefit from this within
the provider’s action blocks, resource bodies.

cheers,

–aj

On Wed, Mar 11, 2015 at 11:12 AM, Benjamin Bytheway bbytheway@gmail.com wrote:

I’ve done a bit of digging on this. Because of the way Chef is using
instance_eval, only local variables (and not methods) from recipes are
available to resource blocks.

For a more thorough discussion of how instance_eval works in this case, this
is pretty much the definitive article:

http://www.skorks.com/2013/03/a-closure-is-not-always-a-closure-in-ruby/

There is a caveat to this, however. The “correct” work is being done by chef
to ensure that provider methods are available to resource blocks.

In the recipe dsl, we have an explicit check to ensure that providers and
only providers are given this privilege:

enclosing_provider: self.is_a?(Chef::Provider) ? self : nil

https://github.com/chef/chef/blob/e1e0f850507da9cbf0e4247dcc92e6431a8cb1a1/lib/chef/dsl/recipe.rb#L143

And there is accompanying method_missing in resource to check if the
provider responds to that method and passes the call through if it does.

https://github.com/chef/chef/blob/2603e2153d6ab50179d2278025a51579edb9033f/lib/chef/resource.rb#L955

Is there a reason we couldn’t generalize this, so that DRY recipes with
methods could be used? All that would have to happen is a little name
hygeine, changing enclosing_provider to something like enclosing_context and
changing the recipe dsl to:

enclosing_context: self

Another option is to add an enclosing_context instance variable to the
resource class, and then have the resource builder pass in the original
"self" that the resource block closure was created with. Something like:

resource.enclosing_context = block.binding.eval "self"

Then you just have method missing in the resource class check both the
enclosing_provider and enclosing_context for the method.

I have hit the need for recipe methods so many times, and cursed as I had to
implement things as local variables instead. It really would only take
changing 6-10 lines of code to have Chef support them.

Is this writing up an RFC/Patch for? Has it any chance of being accepted?

-Ben Bytheway

On Mon, Mar 9, 2015 at 4:45 PM, Brandon Richins Brandon.Richins@imail.org
wrote:

I have to correct my question. #3 doesn’t work either. All my @bar
sections in my erb are blank when rendered.

Why won’t methods defined on the recipe evaluate before passing control to
the ERB?

Brandon Richins

From: Brandon Richins Brandon.Richins@imail.org
Reply-To: "chef@lists.opscode.com" chef@lists.opscode.com
Date: Monday, March 9, 2015 at 4:29 PM
To: "chef@lists.opscode.com" chef@lists.opscode.com
Subject: [chef] template variables arguments and methods

I have a question about Chef templates, variables arguments, and methods.
When using templates in a recipe, I sometimes want to use a method instead
of a local variable as the value to variable key. ex. def baz { ‘baz’ } vs
baz = ‘baz’. When I try to use baz as a method in the template variables
section, using the syntax of #1 and #2, Chef fails to converge the template
and raises an undefined method error for baz. For #1 and #2 if baz is a
local variable in the template it works fine. Today I discovered syntax #4
which worked when baz is a method.

I’m going to show my own ignorance about Ruby now. I understand { bar:
‘b’ } to be a Hash in Ruby. So I think #1 is passing a Hash to a
“variables” method on a template object. For #2 I was guessing it was
coercing the list of arguments into a Hash (kind of like varargs in Java).
For #3 I was guessing it’s evaluating the outer {} as a block and the inner
{} as a Hash. Can you confirm these assumptions?

So why do #1 and #2 raise an undefined method error and #3 works. It
seems the Chef/Template DSL is treating methods differently than local
variables when it comes to templates.

def baz
’baz’
end

  1. Parentheses then braces

template ‘foo’ do
variables({
bar: baz,
a: ‘b’,
})
end

  1. Parentheses and no braces

template ‘foo’ do
variables(
bar: baz,
a: ‘b’,
)
end

  1. Double braces

template ‘foo’ do
variables{{
bar: baz,
a: ‘b’,
}}
end

Thanks,

Brandon Richins