+++ title = "About Unified Mode" draft = false
gh_repo = "chef-web-docs" product = ["client"]
[menu] [menu.infra] identifier = "chef_infra/resources/unified_mode.md Use Unified Mode" parent = "chef_infra/resources" weight = 20 +++
{{< readfile file="content/reusable/md/unified_mode_overview.md" >}}
{{< readfile file="content/reusable/md/unified_mode_client_releases.md" >}}
{{< readfile file="content/reusable/md/unified_mode_enable.md" >}}
If a unified mode resource calls a non-unified mode resource, the called resource is not executed in unified mode. Each resource maintains its own state whether it is in unified mode or not. You do not need to modify a custom resource that calls a unified mode resource since the calling context will not affect the resource's execution. Resources using unified mode may call resources not using unified mode and vice versa.
In unified mode, the Chef Infra Language executes from top to bottom, eliminating the compile and converge phases.
With the deferred execution of resources to converge time, the user has to understand many different details of the Ruby parser to understand what constructs relate to Chef Infra resources and what constructs are parts of the core Ruby language to determine when those expression are executed. All that complexity is removed in unified mode.
Several aspects of the Chef Infra Language still work but are no longer necessary in unified mode. Unified mode eliminates the need for lazy blocks and the need to lazy Ruby code through a Ruby block.
In unified mode, it is now easy to write a rescue wrapper around a Chef Infra resource:
begin
execute "a command that fails" do
command "/bin/false"
end
rescue Mixlib::ShellOut::ShellCommandFailed => e
raise "failing because /bin/false returned a failed exit status"
end
A simple motivating example is to have a resource that downloads a JSON message using the [remote_file]({{< relref "/resources/remote_file" >}}) resource, parse the JSON using the [ruby_block]({{< relref "/resources/ruby_block" >}}), and then render a value into a [file]({{< relref "/resources/file" >}}) or [template]({{< relref "/resources/template" >}}) resource.
Without unified mode, correctly writing this simple resource is complicated:
provides :downloader
action :doit do
remote_file "/tmp/users.json" do
source "https://jsonplaceholder.typicode.com/users"
end
array = nil
ruby_block "convert" do
block do
array = FFI_Yajl::Parser.parse(IO.read("/tmp/users.json"))
end
end
file "/tmp/phone" do
content lazy { array.last["phone"] }
end
end
Since the remote_file and file resources execute at converge time, the Ruby code to parse the JSON needs to be wrapped in a ruby_block resource, the local variable then needs to be declared outside of that scope (requiring a deep knowledge of Ruby variable scoping rules), and then the content rendered into the file resource must be wrapped with lazy
since the Ruby parses all arguments of properties at compile time instead of converge time.
Unified mode simplifies this resource:
unified_mode true
provides :downloader
action :doit do
remote_file "/tmp/users.json" do
source "https://jsonplaceholder.typicode.com/users"
end
array = FFI_Yajl::Parser.parse(IO.read("/tmp/users.json"))
file "/tmp/phone" do
content array.last["phone"]
end
end
Unified mode eliminates the need for the ruby_block resource, the lazy
evaluation, and the variable declaration, simplifying how the cookbook is authored.
Another advantage is in error recovery and the use of rescue.
unified_mode true
provides :redis_install
action :install do
version = "5.0.5"
# the downloading of this file acts as a guard for all the later
# resources -- but if the download is successful while the later
# resources fail for some transient issue, will will not redownload on
# the next run -- we lose our edge trigger.
#
remote_file "/tmp/redis-#{version}.tar.gz" do
source "http://download.redis.io/releases/redis-#{version}.tar.gz"
notifies :run, "execute[unzip_redis_archive]", :immediately
end
begin
execute "unzip_redis_archive" do
command "tar xzf redis-#{version}.tar.gz"
cwd "/tmp"
action :nothing
notifies :run, "execute[build_and_install_redis]", :immediately
end
execute "build_and_install_redis" do
command 'make && make install'
cwd "/tmp/redis-#{version}"
action :nothing
end
rescue Exception
file "/tmp/redis-#{version}.tar.gz" do
action :delete
end
raise
end
end
This simplified example shows how to trap exceptions from resources using normal Ruby syntax and to clean up the resource. Without unified mode, this syntax is impossible. Normally when the [execute]({{< relref "resources/execute" >}}) resources are parsed, they only create the objects in the resource_collection
to later be evaluated so that no exception is thrown while Ruby is parsing the action
block. Every action is delayed to the later converge phase. In unified mode, the resource runs when Ruby is done parsing its block, so exceptions happen in-line with Ruby parsing and the rescue clause now works as expected.
This is useful because the TAR extraction throws an exception (for example, the node could be out of disk space), which deletes the TAR file. The next time Chef Infra Client runs, the TAR file will be redownload. If the resource did not have file cleanup after an exception, the TAR file would remain on the client node even though the resource is not complete and the extraction did not happen, leaving the resource in a broken, indeterminate state.
{{< readfile file="content/reusable/md/unified_mode_actions_later_resources.md" >}}
The accumulator pattern works unchanged. Notifications to the :root
run context still behave identically. Since the compile and converge phases of custom resources both fire in the converge time (typically) of the enclosing run_context
, the effect of eliminating the separate compile and converge phases of the custom resource has no visible effect from the outer context.
{{< readfile file="content/reusable/md/unified_mode_troubleshooting.md" >}}