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.
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:
ifunlesseachwithlookup> (partials)logNote
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 helper evaluates conditional statements. The values false, 0, “”, as well as undefined values all evaluate to false in if 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 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, 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 the servers 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 ~}}]
Chef Habitat’s templating flavour includes a number of custom helpers for writing configuration and hook files.
my_value={{toLowercase "UPPER-CASE"}}
my_value={{toUppercase "lower-case"}}
my_value={{strReplace "this is old" "old" "new"}}
This sets my_value to “this is new”.
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 the pkg_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 bind.backend.members as |member|}}
server ip {{member.sys.ip}}:{{member.cfg.port}}
{{~/eachAlive}}
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 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 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:80The 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.
join helper can be used to create a string with the variables in a list with a separator specified by you. For example, where list: ["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 "/"}}).
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}}).
© Chef Software, Inc.
Licensed under the Creative Commons Attribution 3.0 Unported License.
The Chef™ Mark and Chef Logo are either registered trademarks/service marks or trademarks/servicemarks of Chef, in the United States and other countries and are used with Chef Inc's permission.
We are not affiliated with, endorsed or sponsored by Chef Inc.
https://docs.chef.io/habitat/plan_helpers/