Configuration Helpers
Chef Habitat allows you to use Handlebars-based tuneables in your plan, and you can also use both built-in Handlebars helpers and Chef Habitat-specific helpers in defining your configuration logic.
Built-in Helpers
You can use block expressions to add basic logic to your template such as checking if 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:
if
unless
each
with
lookup
>
(partials)log
Note
Per Handlebars Paths, 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
if
helper evaluates conditional statements. The valuesfalse
, 0, “”, as well as undefined values all evaluate to false inif
blocks.
Here’s an example that will only write out configuration for the unixsocket tunable if a value was set by the user:
{{#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 controlling 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 each to render multiple server entries:
{{#each cfg.servers as |server| ~}} server { host {{server.host}} port {{server.port}} } {{/each ~}}
You can also use each with @key
and this
. Here is an example that takes the [env]
section of your default.toml and makes an env file you can source from your run hook:
{{#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 = 3434
And for both each and unless, you can use @first
and @last
to 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
@first
and @last
variables also work with the Chef Habitat helper eachAlive
, and in the example above, it would be preferable to the built-in each
helper because it checks whether the service is available before trying to retrieve any values.- unless
- For
unless
, using@last
can also be helpful when you need to optionally include delimiters. In the example below, the IP addresses of the alive members returned by theservers
binding is comma-separated. The logic check{{#unless @last}}, {{/unless}}
at the end ensures that the 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 flavour includes a number of 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_value
to “this is new”.
- pkgPathFor
- Returns the absolute filepath to the package directory of the package best resolved from the given package identifier. The named package must exist in the
pkg_deps
of the plan from which the template resides. The helper will return a nil string if the named package is not listed in thepkg_deps
. As result you will always get what you expect 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 specific file in core/nginx
package 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
toJson
helper.
Given a default.toml that looks like:
[web]
[[servers]]
host = "host-1"
port = 4545
[[servers]]
host = "host-2"
port = 3434
and 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 is in JSON format and has the same structure as your TOML configuration data.
- toToml
- The
toToml
helper can be used to output TOML.
Given a default.toml that looks like:
[web]
port = 80
and a template:
{{toToml cfg.web}}
when rendered, it will look like:
port = 80
This can be useful if you have an app that uses TOML as its configuration file format, but may have not been designed for Chef Habitat, and you only need certain parts of the configuration data in the rendered TOML file.
- toYaml
- The
toYaml
helper can be used to output YAML.
Given a default.toml that looks like:
[web]
port = 80
and a template:
{{toYaml cfg}}
when rendered, it will look like:
+++
web:
port: 80
The helper outputs a YAML document (with a line beginning with +++
), so it must be used to create complete documents: you cannot insert a section of YAML into an existing YAML document with this helper.
- strJoin
- The
join
helper can be used to create a string with the variables in a list with a separator specified by you. For example, wherelist: ["foo", "bar", "baz"]
,{{strJoin list ","}}
would return"foo,bar,baz"
.
You cannot join an object (e.g. {{strJoin web}}
), but you could join the variables in an object (e.g. {{strJoin web.list "/"}}
).
- strConcat
- The
concat
helper can be used to connect multiple strings into one string without a separator. For example,{{strConcat "foo" "bar" "baz"}}
would return"foobarbaz"
.\
You cannot concatenate an object (e.g. {{strConcat web}}
), but you could concatenate the variables in an object (e.g. {{strConcat web.list}}
).