Kontakt: +49 511 59095 – 942

Custom Resource Diffs in Chef


Custom Resource Diffs in Chef

If you are writing custom resources regularly, you might have been annoyed by a general “diff” functionality in Chef. In this post we will work on some snippets to make this possible

While the file and template resources will output an overview of added/removed/changed lines during the Chef run, there is no built-in facility for your own resources.

In my current project I am working heavily with Chef Target Mode (you might have noticed from the last posts), so I cannot simply use the existing resources for change output.

Current state

Chef bundles the Diff::LCS gem for it’s output on the mentioned resources, so there is no external dependency needed.

When searching for built-in functionality, I discovered some code lines in Chef::ResourceReporter and Chef::DataCollector::RunEndMessage which are checking if a resource responds to a diff method call. Sadly, I was not able to make this work.

I also found that the existing code in Chef::Util::Diff does only work with local files and not string input.

Customized Diff Method

Our new method is basically some copy & paste from the Chef::Util::Diff#udiff resource with it’s file-specific lines removed.

def str_udiff(old_data, new_data)
  diff_str = ""
  file_length_difference = 0

  diff_data = ::Diff::LCS.diff(old_data, new_data)

  return diff_str if old_data.empty? && new_data.empty?
  return "No differences encountered\n" if diff_data.empty?

  # loop over diff hunks. if a hunk overlaps with the last hunk,
  # join them. otherwise, print out the old one.
  old_hunk = hunk = nil
  diff_data.each do |piece|
      hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference)
      file_length_difference = hunk.file_length_difference
      next unless old_hunk
      next if hunk.merge(old_hunk)

      diff_str << old_hunk.diff(:unified) << "\n"
      old_hunk = hunk
  diff_str << old_hunk.diff(:unified) << "\n"

So by passing in the current and new values into this function, we will get the output we want. Please be aware, that Diff::LCS expects this input to be an array of lines, not a String.

Using this helper is easy in our custom resources:

  description = ["update my configuration"]
  description << str_udiff(

  converge_by(description) do
     # Do your work

As the lines method preserves line endings, the str_udiff code would result in double newlines. Luckily, there is the chomp option available to fix that.

By using converge_by you can supply a custom description in a custom resource, which is not documented very well.

Getting Fancy

The only thing I was not happy about with this solution is the missing eye candy. I would love to have some output which marks removed lines in red and added lines in green. While I was browsing for Rubygems to use (before realizing Chef already bundled something), I found the excellent samg/diffy tool. This one already includes some code for coloring diff output in its diffy/format.rb file.

We can use that code with slight adjustments to have a diff function with ANSI colors:

def diff(current, new)
  diff = Chef::Util::Diff.new.str_udiff(
    current.lines(chomp: true),
    new.lines(chomp: true)

  diff.lines.map do |line|
    case line
    when /^(---|\+\+\+|\\\\)/
    when /^\+/
    when /^-/
    when /^@@/
  end.join("\n") + "\n"

As this one does the conversion of String into Arrays for us, our use inside the custom resource gets even easier:

  description = ["update my configuration"]
  description << diff(@current_resource.content, @new_resource.content)

  converge_by(description) do
     # Do your work

Have fun!