Understanding and Controlling Execution Order in Chef Recipes: Ruby_block vs Remote_file Priority


2 views

When working with Chef, many developers encounter unexpected execution orders between different resource types. Consider this common scenario:

ruby_block "block1" do
    block do
        puts "in block1"
    end
    action :create
end

remote_file "/tmp/foo" do
    puts "in remote_file"
    source "https://yahoo.com"
end

Despite appearing sequentially in the recipe, the output shows:

==> default: in remote_file
==> default: in block1

This isn't just about debug output - it affects actual functionality. When we need the ruby_block to set parameters for subsequent resources:

ruby_block "set_url" do
    block do
        node.default['download'] = {}
        node.default['download']['source'] = 'https://google.com'
    end
    action :create
end

remote_file "/tmp/foo" do
    source node['download']['source'] # Fails because ruby_block hasn't run yet
end

Chef operates in two distinct phases during a run:

  1. Compilation Phase: Resources are evaluated and added to the resource collection
  2. Execution Phase: Resources run in the order determined by dependency resolution

This explains why puts statements appear "out of order" - they're executed during compilation, while the actual resource actions happen later.

1. Explicit Run Order Using notifies

ruby_block "set_url" do
    block do
        node.default['download']['source'] = 'https://google.com'
    end
    action :nothing
end.run_action(:run)

remote_file "/tmp/foo" do
    source node['download']['source']
end

2. Immediate Execution with run_action

ruby_block "set_url" do
    block do
        node.default['download']['source'] = 'https://google.com'
    end
    action :nothing
end.run_action(:run)

remote_file "/tmp/foo" do
    source node['download']['source']
end

3. Using Lazy Evaluation

ruby_block "set_url" do
    block do
        node.default['download']['source'] = 'https://google.com'
    end
    action :run
end

remote_file "/tmp/foo" do
    source lazy { node['download']['source'] }
end
  • Always assume resources might not execute in written order
  • For critical ordering, use explicit dependencies
  • Consider splitting complex recipes into multiple files
  • Use lazy evaluation when dealing with computed attributes

What you're observing is due to Chef's two-phase execution model:

  1. Compilation Phase: Resources are collected and evaluated
  2. Execution Phase: Resources are converged in order

This explains why your puts statements appear out of order - they're being evaluated during compilation, while the actual resource execution happens later.

The key issue is that node.default['test']['foo'] is evaluated during compilation, before your ruby_block has executed:

# This gets evaluated IMMEDIATELY during compilation
remote_file "/tmp/foo" do
  source node.default['test']['foo']  # currently nil/empty
end

The proper way to handle this is with lazy evaluation:

ruby_block "set download URL" do
  block do
    node.normal['download_url'] = 'https://google.com'
  end
end

remote_file "/tmp/foo" do
  source lazy { node['download_url'] }
end

If you need the ruby_block to execute immediately:

ruby_block "block1" do
  block do
    node.default['test']['foo'] = 'https://google.com'
  end
  action :nothing
end.run_action(:create)

remote_file "/tmp/foo" do
  source node['test']['foo']
end

For complex dependencies, use notifications:

ruby_block "set values" do
  block do
    node.default['source_url'] = calculate_url()
  end
  notifies :create, 'remote_file[/tmp/data]', :immediately
end

remote_file "/tmp/data" do
  source lazy { node['source_url'] }
  action :nothing
end

To verify execution order, use Chef's logging:

ruby_block "debug block" do
  block do
    Chef::Log.info("This executes during convergence")
  end
end

log "compilation_message" do
  message "This appears during compilation"
  level :info
end