Configuration helpers
Chef Habitat allows you to use Handlebars-based tunables in your plan, and you can use both built-in Handlebars helpers and Chef Habitat-specific helpers to define your configuration logic.
Built-in helpers
You can use block expressions to add basic logic to your template, such as checking whether a value exists or iterating through a list of items.
Block expressions use a helper function to perform the logic. The syntax is the same for all block expressions and looks like this:
{{#helper blockname}}
{{expression}}
{{/helper}}
Chef Habitat supports the standard built-in helpers:
ifunlesseachwithlookup>(partials)log
Note
When using each in a block expression, you must reference the parent context of that block to use any user-defined configuration values referenced within the block, such as those that start with cfg. For example, if your block looked like the following, you must reference cfg.port from the parent context of the block:
{{#each svc.members}}
server {{sys.ip}}:{{../cfg.port}}
{{/each}}
The most common block helpers that you will probably use are the if and with helpers.
- if
- The
ifhelper evaluates conditional statements. The valuesfalse, 0, “”, as well as undefined values all evaluate to false inifblocks.Here’s an example that only writes configuration for the
unixsockettunable if a user sets a value:{{#if cfg.unixsocket}} unixsocket {{cfg.unixsocket}} {{/if}}
Note
~ indicates that whitespace should be omitted when rendering.TOML allows you to create sections (called TOML tables) to better organize your configuration variables. For example, your default.toml or user-defined TOML could have a [repl] section for variables that control replication behavior. Here’s what that looks like:
[repl]
backlog-size = 200
backlog-ttl = 100
disable-tcp-nodelay = no
When writing your template, you can use the with helper to reduce duplication:
{{#with cfg.repl}}
repl-backlog-size {{backlog-size}}
repl-backlog-ttl {{backlog-ttl}}
repl-disable-tcp-nodelay {{disable-tcp-nodelay}}
{{/with}}
Helpers can also be nested and used together in block expressions. Here is another example from the redis.config file where the if and with helpers are used together to set up core/redis Chef Habitat services in a leader-follower topology.
{{#if svc.me.follower}}
replicaof {{svc.leader.sys.ip}} {{svc.leader.cfg.port}}
{{/if}}
- each
- Here’s an example using
eachto render multiple server entries:{{#each cfg.servers as |server|}} server { host {{server.host}} port {{server.port}} } {{/each}}You can also use
eachwith@keyandthis. Here is an example that takes the[env]section of yourdefault.tomland creates an env file you can source from yourrunhook:{{#each cfg.env}} export {{toUppercase @key}}={{this}} {{/each}}You would specify the corresponding values in a TOML file using an array of tables like this:
[[servers]] host = "host-1" port = 4545 [[servers]] host = "host-2" port = 3434And for both each and unless, you can use
@firstand@lastto specify which item in an array you want to perform business logic on. For example:"mongo": { {{#each bind.database.members as |member|}} {{#if @first} "host" : "{{member.sys.ip}}", "port" : "{{member.cfg.port}}" {{/if}} {{/each}} }Note
The@firstand@lastvariables also work with the Chef Habitat helpereachAlive, and in the example above, it would be preferable to the built-ineachhelper because it checks whether the service is available before trying to retrieve any values. - unless
- For
unless, using@lastcan also be helpful when you need to optionally include delimiters. In the example below, the IP addresses of alive members returned by theserversbinding are comma-separated. The logic check{{#unless @last}}, {{/unless}}at the end ensures that a comma is written after each element except the last element.{{#eachAlive bind.servers.members as |member|}} "{{member.sys.ip}}" {{#unless @last}}, {{/unless }} {{/eachAlive}}
Plan helpers
Chef Habitat’s templating flavor includes custom helpers for writing configuration and hook files.
- toLowercase
- Returns the lowercase equivalent of the given string literal.
my_value={{toLowercase "UPPER-CASE"}} - toUppercase
- Returns the uppercase equivalent of the given string literal.
my_value={{toUppercase "lower-case"}} - strReplace
- Replaces all matches of a pattern within the given string literal.
my_value={{strReplace "this is old" "old" "new"}}This sets
my_valuetothis is new. - pkgPathFor
- Returns the absolute file path to the package directory best resolved from the given package identifier. The named package must exist in the
pkg_depsof the plan where the template resides. The helper returns a nil string if the named package isn’t listed inpkg_deps. As a result, you always get the expected value, and the template won’t leak to other packages on the system.Example plan contents:
pkg_deps=("core/jre8")Example template:
export JAVA_HOME={{pkgPathFor "core/jre8"}}Example pointing to a specific file in the
core/nginxpackage on disk:{{pkgPathFor "core/nginx"}}/config/fastcgi.conf - eachAlive
- Iterates over a collection of members and renders the template for members that are marked alive.
{{#eachAlive bind.backend.members as |member|}} server ip {{member.sys.ip}}:{{member.cfg.port}} {{/eachAlive}} - toJson
- To output configuration data as JSON, you can use the
toJsonhelper.Given a default.toml that looks like:
[web] [[servers]] host = "host-1" port = 4545 [[servers]] host = "host-2" port = 3434and a template:
{{toJson cfg.web}}when rendered, it will look like:
{ "servers": [ { "host": "host-1", "port": 4545 }, { "host": "host-2", "port": 3434 } ] }This can be useful if you have a configuration file that’s in JSON format and has the same structure as your TOML configuration data.
- toToml
- The
toTomlhelper can be used to output TOML.Given a default.toml that looks like:
[web] port = 80and a template:
{{toToml cfg.web}}when rendered, it will look like:
port = 80This can be useful if you have an app that uses TOML as its configuration file format, but may not have been designed for Chef Habitat, and you only need certain parts of the configuration data in the rendered TOML file.
- toYaml
- The
toYamlhelper can be used to output YAML.Given a default.toml that looks like:
[web] port = 80and a template:
{{toYaml cfg}}when rendered, it will look like:
+++ web: port: 80The helper outputs a YAML document (with a line beginning with
+++), so it must be used to create complete documents: you can’t insert a section of YAML into an existing YAML document with this helper. - strJoin
- The
strJoinhelper can be used to create a string from variables in a list, with a separator you specify. For example, wherelist: ["foo", "bar", "baz"],{{strJoin list ","}}returns"foo,bar,baz".You can’t join an object (for example
{{strJoin web}}), but you could join the variables in an object (for example{{strJoin web.list "/"}}). - strConcat
- The
strConcathelper can be used to connect multiple strings into one string without a separator. For example,{{strConcat "foo" "bar" "baz"}}returns"foobarbaz".You can’t concatenate an object (for example
{{strConcat web}}), but you could concatenate the variables in an object (for example{{strConcat web.list}}).
Trimming whitespace
The Handlebars templating language allows you to use tildes (~) inside double opening and closing braces to control whitespace. If you’re familiar with the Go programming language, the Handlebars syntax {{~ and ~}} is similar to {{- and -}} in Go. For more details, see the Handlebars documentation. The following examples demonstrate how this works.
Consider this Handlebars template:
<!--comment-->
{{#if items}}
<ul>
{{#each items}}
<li> {{item}} </li>
{{/each}}
</ul>
{{/if}}
<!--comment-->
With this context:
{
"items": [
{ "item": "one" },
{ "item": "two" },
{ "item": "three" }
]
}
The output preserves the whitespace around each statement:
<!--comment-->
<ul>
<li> one </li>
<li> two </li>
<li> three </li>
</ul>
<!--comment-->
If you add the ~ character to both sides of each mustache statement:
<!--comment-->
{{~#if items~}}
<ul>
{{~#each items~}}
<li> {{~item~}} </li>
{{~/each~}}
</ul>
{{~/if~}}
<!--comment-->
The whitespace is removed, and the rendered output collapses to a single line:
<!--comment--><ul><li>one</li><li>two</li><li>three</li></ul><!--comment-->
Whitespace control in Handlebars templates is a powerful feature that requires careful use. In this example, the only consequence is reduced human readability of the rendered output. However, in contexts where whitespace is significant, improper use can result in invalid output.
For more information, see: