I'm writing a Chef recipe to install our application code and execute it. The recipe needs to be particular about the directory this code ends up in (for running templates, setting log forwarding etc.). Thus the directory itself pops up in a lot of places in different recipes.
I am trying to fetch/define a variable so I can re-use it in my resource block with string interpolation. This is pretty straightforward:
home = node['etc']['passwd'][node['nodejs']['user']]['dir']
With an example usage being to run npm install
while telling it to plunk the repo downloads in the home directory, like this:
execute "npm install" do
command "npm install #{prefix}#{app} --prefix #{home}"
end
Except that the first block which defines the home
variable will run at compile time. On a fresh server, where my nodejs user account may not exist yet, this is a problem, giving a
NoMethodError undefined method '[]' for nil:NilClass
I have a few workarounds, but I would like a specific solution to make the home variable only be fetched at recipe execute time, not compile time.
Workaround 1
Dynamically evaluate the home variable inside a ruby block, like so:
ruby_block "fetch home dir" do
block do
home = node['etc']['passwd'][node['nodejs']['user']]['dir']
end
end
This does not seem to actually work, giving a NoMethodError undefined method home for Chef::Resource::Directory when you try to do something like this:
directory ".npm" do
path "#{home}/.npm"
end
I feel like I must be doing something wrong here.
Workaround 2
Lazily evaluate a parameter on every single resource that needs it. So instead do this:
directory ".npm" do
path lazy "#{node['etc']['passwd'][node['nodejs']['user']]['dir']}/.npm"
end
But it would be really great to just have to maintain that line of code once, store it in a variable and be done with it.
Workaround 3
Create the user at compile time. This of course works, using the notify trick linked here, like this:
u = user node['nodejs']['user'] do
comment "The #{node['nodejs']['user']} is the user we want all our nodejs apps will run under."
username node['nodejs']['user']
home "/home/#{node['nodejs']['user']}"
end
u.run_action(:create)
This solves my problem exactly, but there are other cases where I can imagine wanting the ability to delay evaluation of a variable, so I let my question stand.
What I would Like
I would really like to be able to do
home lazy = node['etc']['passwd'][node['nodejs']['user']]['dir']
But that's not legal syntax, giving NameError Cannot find a resource for home on ubuntu version 13.10 (which is an odd syntax error, but whatever). Is there a legal way to accomplish this?
home = lazy {node['etc']['passwd'][node['nodejs']['user']]['dir']}
– Antinodehome = DelayedEvaluator.new {node['etc']['passwd'][node['nodejs']['user']]['dir']}
orlambda {node['etc']['passwd'][node['nodejs']['user']]['dir']}
iflazy
is not available in top scope. – Antinodehome = lazy {
syntax did not compile. Thehome = DelayedEvaluator.new
syntax compiles and runs, but it needs some accessor to get the string value out. If I just do#{home}
, it shows atostring
looking value of the reference/class, like#<Chef::DelayedEvaluator:0x000000027ac400@/var/chef/cache/cookbooks...
etc.lambda
does something similar:<Proc:0x00000002847270@
. I can't find any examples on how to useDelayedEvaluator
in that type of syntax. Any ideas? – Escalatehome.call
. @thoughtcroft answer is actually proposing the same, just with lambda and not Proc. – Antinodelambda.call
, the#{ }
syntax displays it as aProc
. I guess Proc is the class name lambda resolves to in Ruby? – Escalate