How to Use a Ruby Block to Assign Variables in Chef Recipe

How to use a Ruby block to assign variables in chef recipe

Your problem is compile time versus converge time. Your block will be run at converge time but at this time the node['host'] in the execute resource has already been evaluated.

The best solution in this case would be to use lazy evaluation so the variable will be expanded when the resource is converged instead of when it is compiled:

execute "Enable on hosts" do
command lazy { "#{path}/command --enable -N #{node['hosts']}" }
end

Here's a little warning though, the lazy operator has to include the full command attribute text and not only the variable, it can work only as first keyword after the attribute name.

Chef: Can a variable set within one ruby_block be used later in a recipe?

Option 1: You could also put your file resource inside the ruby_block.

ruby_block "get_file_list" do
block do
files = Dir['/some/dir/*']

files.each do |f|
t = Chef::Resource::File.new(f)
t.owner("woohoo")
t.group("woohoo")
t.mode("0600")
t.action(:create)
t.run_context=(rc)
t.run_action(:create)
end

end
end

Option 2: You could use node.run_state to pass data around.

ruby_block "get_file_list" do
block do
node.run_state['transferred_files'] = Dir['/some/dir/*']
end
end

node.run_state['transferred_files'].each do |file|
file "#{file}" do
group "woohoo"
user "woohoo"
end
end

Option 3: If this were just one file, you could declare a file resource with action :nothing, look up the resource from within the ruby_block, and set the filename, and then notify the file resource when the ruby_block runs.

Option 4: If this is the example from IRC today, just place your rsync and the recursive chown inside a single bash resource. rsync and chown are already idempotent, so I don't think it's objectionable in this particular case.

Chef - Setting instance variable within ruby_block

You should use the run_state to store such variables spanning over multiple resources:

ruby_block "set_hash" do
block do
node.run_state['foo'] = {'key' => 'val'}
end
action :run
notifies :write, "log[inspect_hash]", :immediately
end

log "inspect_hash" do
lazy { message node.run_state['foo'] }
action :nothing
end

Chef - get variable from ruby block

There's two thing here.

First, your second Chef::Log.info will be run at compilation phase, at this time your ruby_block has not been converged. See here about it. You can prefix your logs with 1) and 2) to witch one runs first.

Second, there's a scoping problem, when you define a variable in a block, it is available only within this block.

In chef you can use node.run_state['variable'] as a global variable usable in all recipes, without an use case it's hard to showcase this.

Side note: you should not use the backticks `` construction to execute commands and prefer using shell_out from the recipe DSL.

initialize chef attribute with ruby block?

As per your request I am posting this as an answer.

Right now you are using {} incorrectly as this is not a block but a Hash literal which is why it is complaining. In order to make this a block you must use a lambda or Proc object.

lambda

lambda's can be created using one of 2 different syntax styles

-> { "This is a lambda" } 
#=> #<Proc:0x2a954c0@(irb):1 (lambda)>
lambda { "This is also a lambda" }
#=> #<Proc:0x27337c8@(irb):2 (lambda)>

Either way is perfectly acceptable

Proc

Procs can be created using Proc.new { "This is a proc" }

for this question the semantic differences are not needed.

lambda and Proc will lazy evaluate the statement inside the block on #call which means that the value can remain fluid.

Let's take your example:

 NOW = Time.now.strftime('%m%d%Y_%H%M')
# in this case NOW will be evaluated once and will always equal the
# string result of when it was first interpretted
TODAY = -> {Time.now.strftime('%m%d%Y_%H%M')}
# in this case TODAY is simply a lambda and it's value will be dependent
# upon the time when you "call" it so something like this will clearly illustrate
# the difference
while NOW == TODAY.call
puts "Still the same time"
end
# after this NOW will still reflect it's initial set value and for
# a while ~ 1 minute this will output "Still the same time"
# at some point TODAY.call will increment up by 1 minute because it is
# re-evaluated on each `#call` thus allowing it to change periodically with the clock

I hope this in some way helps you better understand the concept.

pass variables from a chef resouce to other resources in the same chef recipe

Since you are running some Ruby code to get the file contents, you can even do it outside Chef resources. Something like:

if File.exists?("/work/customer/Var.js")
var = File.read("/work/customer/Var.js")
end

http_request 'cusromer update' do
action :put
# rest of code
end

Note that I've used var with lower case v.

Explanation:

The chef-client goes through several phases during the run. The two relevant for this answer are:

  • Compile
  • Converge

All variable and resources are compiled and assignments happen in the compile phase. So var remains unassigned as there is no definition in for it in compile phase. Whereas ruby_block and http_request run in the converge phase.



Related Topics



Leave a reply



Submit