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
Rails 4 How to Call Accessible_Attributes from Model
"Msvcrt-Ruby18.Dll Was Not Found" with Ruby
How to Get the Array Index or Iteration Number with an Each Iterator
Why Aren't Global (Dollar-Sign $) Variables Used
Possible to Use Stylesheet.Css.Erb in Rails
Optional Parens in Ruby for Method with Uppercase Start Letter
How to Find Current Abstract Route in Rails Middware
Why Doesnt My Ruby Coding for Finding Prime Numbers Work
Result of 'Rake Routes' in Ruby Script
Getting Google File Picker to Work with Drive.File Scope
If(!X) VS If(X==False) in Ruby
Rails 3 - How to Set Start Date to Date_Select Method