Compile time vs. run time in Chef recipes

2020-01-26 07:43发布

I have the following (simplified) recipe called java, to install Java of course.

File recipes/default.rb

include_recipe "install_java"

File recipes/install_java.rb

# Install RPM from yum repo via yum_install library function
yum_install("jdk1.7.0_51")

# List the directories in /usr/java
jdk_dir = `ls -ld /usr/java/jdk1.* | sort | tail -1`
if jdk_dir.empty?
  raise "Missing JDK installation"
end

When I run the recipe by "chef-client -o recipe[java]"

Synchronizing Cookbooks:
  - java
Compiling Cookbooks...
ls: /usr/java/jdk1.*: No such file or directory

=========================================================================== Recipe Compile Error in /var/chef/cache/cookbooks/java/recipes/default.rb ===========================================================================

RuntimeError
------------
Missing JDK installation

It seems like the yum_install() function is NOT being called. However, if I modify the install_java.rb recipe to just have

# Install RPM from yum repo via yum_install library function
yum_install("jdk1.7.0_51")

it works.

Why is this?

标签: chef
2条回答
叛逆
2楼-- · 2020-01-26 08:18

Ok, so Chef runs take two passes.

"Compile time"

I like to call this the collection phase.
At this point, the actual ruby code in your recipe is run. That means any statements like jdk_dir = ls -ld /usr/java/jdk1.* | sort | tail -1 are going to be executed at that point. However, the ruby code that creates a Chef resource yum_install("jdk1.7.0_51") only creates the resources. Those resources, created by your recipe code, are then added to the Chef resource_collection, but the resource actions are NOT run yet.

"Converge Time"

I call this the resolution phase. At this point - after ALL recipes have run (creating resources, but not running actions) - we are now ready to actually run the resource actions. Chef starts with the first resource in the resource_collection and runs the specified action on that resource. It works through the collection, calling notifications as needed, until all resources' actions have been run. Then your run is complete.

Your specific case

So, in your case, you are trying to access the directory in the collection phase but you don't create the directory until the resolution phase. If you want to run ruby code during the resolution phase you can do so in a ruby_block resource. For example:

ruby_block 'verify java is there' do
  block do
    if jdk_dir.empty?
      raise "Missing JDK installation, reinstall"
    end
  end
end

If this ruby_block resource is placed after your yum_install (which should probably be yum_package) resource, then it will get placed after the install resource in the collection_phase, and then executed during the resolution phase (i.e., run time) of the Chef run.

查看更多
何必那么认真
3楼-- · 2020-01-26 08:24

So I can't be entirely certain, but it is likely that yum_install is a resource (or some kind of helper that creates a resource). Recipes are effectively run in two passes. First each file is executed (literally via Ruby's exec). When a resource is created in the DSL it "compiles" to an in-memory resource object that gets added to the global resource collection (a big array of all the resource). Once all recipe files are compiled, Chef loops over the resource collection and runs each resource's action as requested (sending notifications in-between if needed).

So all in all this means that the yum_install line likely doesn't actually do anything at the moment it runs aside from push an object into an array. You can wrap the other code in a ruby_code block to delay it until converge time.

查看更多
登录 后发表回答