Kontakt: +49 511 59095 – 942

Local Preprocessing in Target Mode

Thumbnail

Local Preprocessing in Target Mode

If you ever created configuration files with any automation system, you know that this involves a lot of templating. This is actually one of the most basic tasks that Chef performs and it is done using the template resource.

With Chef’s Target Mode this currently is a bit more complicated.

Currently, the template resource is not in the list of supported resources for target mode though. So while our naive recipe for remote templating might look like this:

template 'Preprocess template' do
  path File.join(Chef::Config[:file_cache_path], 'template.cfg')
  source 'template.cfg.erb'
  variables({
    hostname: 'nodename',
    domain_name: 'lab.local'
  })
end

### some target_mode compatible resources below
...

We will be greeted upon execution with an error, that Chef could not find a resource/provider for platform xy “with target_mode: true”. That makes our life too complicated.

I already raised that with Chef, asking for implementing some sort of guarding like run_locally do ... end but until then, we can only work around the issues with a bit of hacking.

Let us have a look at the solution first and then address, why and how this works:


Chef::Config.target_mode_enabled = false
template 'Preprocess template' do
  path File.join(Chef::Config[:file_cache_path], 'template.cfg')
  source 'template.cfg.erb'
  variables({
    hostname: 'nodename',
    domain_name: 'lab.local'
  })
  
  action :nothing
end.run_action(:create)
Chef::Config.target_mode_enabled = true

You will notice two weird places in this code snippet: - toggling the Target mode manually - disabling the resource and then doing run_action on it

The first item is pretty self-explanatory. If we want to run resources locally that are not available in Target Mode yet, we will just make Chef think this is not Target Mode at all. Easy enough.

People who have worked with Chef for a while will probably know why we need the second bit in our solution. I will not go too deep into the Chef execution model with its compile and converge phases (Noah aka Coderanger wrote an excellent blog post about that in 2015 for anybody who is curious). But in short, Chef will first collect the resources it is expected to execute and only then start deploying the changes in a later step. That also means that any Ruby code (like toggling target_mode_enabled) will run before that template resource is even executed. So, we will just trigger it in the compile phase - and that is exactly what happens here by first disabling it and then using run_action(:create).

It is worth noting, that from Chef 14.14.1 and 15.2.29 onwards, we can switch custom resources to only have one phase (using unified_mode true in the definition) if you do not need to be backwards compatible.

Caution

While this post is neither about best practices, elegant coding or future-proof ways to solve things, it gets the job done for now. Please check, if Chef has already provided a better way for such tasks when you are reading this post in a distant future.