Qik-n-EZ: Using a Shell’s STDOUT in a Chef ruby_block
Chef gets a bad rap for being “hard” — especially when compared to Ansible .. This is especially true when developers of Chef cookbooks don’t understand the two-pass model .. A common question amongst new Chef cookbook developers goes something like this: “I want to run a shell command and capture its output (i.e. STDOUT) .. I then want to loop through that shell command’s output to run other Chef resources, and I want those other Chef resources to be aware of the shell command’s output .. How can I do that ??” .. Sometimes it’s easier to use bullets:
- run a shell command
- the output of the shell command == A, B, C
- loop through the shell command’s output one at a time
- A
- B
- C
- run other Chef resources per loop item
- other Chef resources are aware of the loop item value
Often, the new Chef cookbook developer will try and run a Ruby loop in the raw, which will execute during the compile phase — this is a big no-no .. Instead, you should run the shell command, using shell_out
, in a Chef ruby_block
resource .. This will give you access to the shell command’s output where you can manipulate the output using good ol’ fashioned Ruby, and then “notify” other Chef resources as desired ..
One thing to be aware of is to make sure you are lazily evaluating properties in a resource that can’t be known until the execution phase of the chef-client run. Alan Thatcher has a nice writeup here about the hows and the whys ..
Enough talk — show me the code !!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ruby_block 'shell out fun' do | |
block do | |
# this is me running my command and assigning the output to a var | |
ls = shell_out('ls /var/') | |
# this is me assigning the STDOUT to a var | |
raw_output = ls.stdout | |
# i used the Ruby p method to print out the raw chars | |
# then i knew how to manipulate the string | |
# in this case i am going to split the output by newlines and store it in an array | |
clean_output = raw_output.split(/\n/) | |
# here i am looping thru the array | |
clean_output.each do |line| | |
# setting the temp var you want to store this in and use later | |
node.run_state[:output] = line | |
# here i go notifying the resource i want to invoke knowing i have set | |
# the temp variable i plan to use later | |
resources(:execute => 'say_hello').run_action(:run) | |
end | |
end | |
end | |
execute 'say_hello' do | |
# here i am going to lazy fetch the command value | |
# so it will grab the latest value of the temp var we are using | |
command lazy { "echo 'hello #{node.run_state[:output]}'" } | |
# don't forget to make this do NOTHING until notified | |
action :nothing | |
end |
So, is Chef “hard” ?? I would argue no — but it sure is nuanced, and for good reason ..