Chef Infra Best Practices: #4 Custom Resources the Building Blocks of your Cookbooks

The fourth instalment of the Shape-Up Your Infrastructure Webinar Series – “Custom Resources the building blocks of your cookbooks” 

Chef Infra Client includes more than 150 built-in resources to manage things like files, packages, templates, and services. Custom resources enable users to add their own resources as an extension of Chef Infra client. Commonly used resources can be further streamlined by packaging them into custom resources as resource-only cookbooks. Custom resources are created by making a Ruby file in the cookbook’s resource file. Cookbooks can have multiple resources. 

Custom Resources are a key part of the Application Cookbook pattern. This is where the application installation, configuration and services are encapsulated in a single cookbook. Historically this encapsulation was done similarly to Role Cookbooks, where recipes and attributes are set. This could define how software was installed (from source or package) and where it was installed to. This method of installing and configuring software leads to a lot of magic, and unexpected behaviour. Changing one node attribute can affect both the installation and configuration which might not be expected. 

Custom Resources are a way for users to build resources that can be consumed in the same way as core Chef Infra resources. They can contain business logic, security requirements and still be flexible for the end user. Custom resource contents are composed of three basic parts: 

  • Declarations of the properties used in the Custom Resource​ 
  • Loading of the current value of the properties, if they already exist​
  • Definitions of the actions that the Custom Resource can take

A Quick Intro to Chef Infra Unified Mode 

Unified Mode provides the ability to opt-in to a single phase of execution for Ruby and Chef code, vastly simplifying the creation of custom resources 

Consider the fact that a Chef Infra Client runs in two phases by default: compile which allows for Ruby code to be executed at compile time then the Chef code runs at a converge time. This generally has been very helpful, except when we’re building resources that mix both Chef and Ruby code. This mix often requires us to move running parts of the Chef run into the compile phase. Unified mode, introduced in Chef Infra Client 15.3, allows the author to use both Chef and plain Ruby in the same resource inline, without any unexpected ordering behaviour. 

Now that we have a basic understanding of what Custom resources are, we can take a look at some examples of how they can be used.  

  1. Wrapping Security Requirements  
  2. Flexible 
  3. Testable 

1. Wrapping Security Requirements 

Let’s say you have an Apache2 web server and you need to adhere to your business folder and permission structure to keep the application secure. To do this you would write a custom resource to create those folders and files with the correct permissions. Allowing your end user to not need to worry about setting the correct permissions on the folders. 

The end user can simply call the apache2_install resource to install the software and have the directories setup for them. 

apache2_install "package"

2.  Flexible 

Not all software within a business can be consumed in the same way every time. Some business units may have a different location they expect their logs to be. You can allow this to be configured by the end user by using the property keyword in custom resources. 

property :log_dir, String, 
default: default_log_dir, 
equal_to: ["/var/log/apache2", "/var/log/app"] 
description: 'Log directory location.' 

Often we only want to accept a few values or Ruby types as inputs. Limiting the accepted inputs reduces guess work, as the user is told when they’ve not entered an accepted value. 

In the above example we have a `log_dir` property, which *only* takes String values, and has a default value of `default_log_dir`
`default_log_dir` is a method which outputs a String, depending on the platform we’re running on. Wrapping up platform logic complexity. 
The equal_to argument allows us to only accept specific values, in this case, “/var/log/apache2” or “/var/log/app”.

Contrast this behaviour to node attributes which are. They can accept any Ruby type, value and have no validation. 

3. Testable   

Custom Resources don’t do anything without being called in a recipe, much like core Chef resources. We now need a test cookbook to exercise, test and document the resource. 

Using a test cookbook allows us to demonstrate and test multiple ways of using the cookbook. 

Splitting Resources & Single Interfaces 

Custom Resources are designed to have a single purpose. For example the apache2_install, apache2_site, apache2_module resources all configure or install different objects in the system, they do not share state. Changing one should not change the behaviour of another. Much like changing the inputs to the core Chef `user` resource does not change how the `group` resource behaves. 

 If you’re looking for more ways to optimize your usage of Chef Infra I recommend checking out the Chef Infra Best Practices Quickfire Webinar Series and/or downloading the Chef Infra Automation Best Practices eBook

Posted in:

Dan Webb

Dan Webb was a community advocate at Progress Chef.