Habitat
kitchen-habitat is a Test Kitchen provisioner for Habitat.
Test Kitchen builds a throwaway machine, applies your configuration to it, runs your tests, and destroys it. This provisioner makes the “apply your configuration” step install a Habitat supervisor on that machine and load a Habitat service into it — so you can test the package you just built, on a real operating system, before you promote it.
Requirements
- Ruby 3.1 or newer
- A Test Kitchen driver to supply the machine — this gem only provisions. kitchen-vagrant, kitchen-dokken, or any cloud driver will do.
- A Habitat package to test: either a package already in Builder, or a local
.hartartifact you built withhab studio.
You do not need the hab CLI on the instance beforehand. The provisioner installs it, then installs and starts a supervisor, as part of converge.
Installation
This provisioner ships with Cinc Workstation and Chef Workstation.
To install it yourself, add it to your Gemfile:
gem "kitchen-habitat"
Or install the gem directly:
gem install kitchen-habitat
Quick start
The smallest useful kitchen.yml names a driver for the machine, this provisioner, and the package you want to run:
---
driver:
name: vagrant
provisioner:
name: habitat
hab_license: accept
package_origin: core
package_name: redis
verifier:
name: inspec
platforms:
- name: ubuntu-22.04
suites:
- name: default
kitchen test
Or step through it:
kitchen create # build the machine
kitchen converge # install hab, start the supervisor, load the service
kitchen verify # run your tests
kitchen destroy # tear the machine down
hab_license: accept is required for the supervisor to start on Linux. Without it the converge fails with Habitat license not accepted. See the Chef license documentation.How it works
A converge runs four steps in order:
- Install the
habCLI. Ifhabis already on the machine, this is skipped. Otherwise the official install script is downloaded and run —install.shon Linux,install.ps1on Windows. - Install and start a supervisor. On Linux a
hab-supsystemd unit is written and enabled. On Windows thecore/windows-servicepackage is installed and the Habitat service is started. Supervisor flags come from thehab_sup_*andevent_stream_*options. - Copy your local files into the sandbox. A
.hartartifact from your results directory, and auser.tomlplus any config files fromconfig_directory, are staged onto the machine. - Install and load the service. The package is installed with
hab pkg install, then loaded withhab svc loadif it has arunhook. The provisioner waits for the service to appear inhab svc status, giving up afterservice_load_timeoutseconds.
Because step 4 only loads packages that ship a run hook, a package that is a library or a build-time dependency converges successfully without a service being started.
Setting Provisioner Configuration
All options are set under the provisioner: key in kitchen.yml, and can be overridden per-platform or per-suite.
Habitat CLI
| Option | Default | Description |
|---|---|---|
hab_license |
nil |
Set to accept to accept the Habitat license. The supervisor will not start on Linux without it. |
hab_version |
latest |
Version of the hab CLI to install. On Linux, any value other than latest is passed to the install script as -v <version>. |
hab_channel |
stable |
Release channel the hab CLI is installed from. Windows only — the Linux install script does not take a channel. |
depot_url |
nil |
Habitat Builder (depot) URL to install packages from, exported to the supervisor as HAB_BLDR_URL. Linux only. When unset, the hab CLI’s own default from ~/.hab/etc/cli.toml applies. |
Supervisor
These map to hab sup run flags on the supervisor the provisioner starts.
| Option | Default | Description |
|---|---|---|
hab_sup_peer |
[] |
Supervisors to peer with to join a ring, as host or host:port. Each becomes a --peer. |
hab_sup_bind |
[] |
Service bindings, as name:service.group, e.g. database:postgresql.default. Each becomes a --bind. |
hab_sup_group |
nil |
Service group the supervisor belongs to (--group). When unset, Habitat’s own default, default, applies. |
hab_sup_ring |
nil |
Ring key name (--ring). |
hab_sup_listen_gossip |
nil |
Address and port for gossip traffic (--listen-gossip), e.g. 0.0.0.0:9638. |
hab_sup_listen_ctl |
nil |
Address and port for the control gateway (--listen-ctl), e.g. 0.0.0.0:9632. |
hab_sup_listen_http |
nil |
Address and port for the HTTP gateway (--listen-http), e.g. 0.0.0.0:9631. |
Service
| Option | Default | Description |
|---|---|---|
package_origin |
core |
Origin of the package to run. Overridden if artifact_name or a fully-qualified package_name is given. |
package_name |
nil |
Name of the package to run. Required unless supplied via artifact_name or install_latest_artifact. May be a full identifier — core/redis/4.0.14 is split into origin, name, and version for you. |
package_version |
nil |
Version of the package to run. |
package_release |
nil |
Release of the package to run. |
channel |
stable |
Channel the package is installed from and updated against. Distinct from hab_channel, which is about the CLI. |
service_topology |
nil |
Service topology (--topology): standalone or leader. Unset means standalone. |
service_update_strategy |
nil |
Update strategy (--strategy): at-once or rolling. Unset means updates are not checked for. |
service_load_timeout |
300 |
Seconds to wait for the service to show up in hab svc status before failing the converge. |
Local artifacts and config
| Option | Default | Description |
|---|---|---|
artifact_name |
nil |
Filename of a local .hart to upload and run. Origin, name, version, and release are parsed from the filename. The file must be in the results directory. |
install_latest_artifact |
false |
Upload and run the newest .hart in the results directory matching package_origin and package_name. Both must be set. package_version and package_release are ignored. |
results_directory |
auto-detected | Directory holding built .hart artifacts, relative to kitchen.yml. When unset, results, ../results, and ../../results are tried in that order. |
config_directory |
nil |
Directory holding a user.toml, and optionally default.toml, hooks, and config files, to ship to the service under test. Relative to kitchen.yml. |
user_toml_name |
user.toml |
Name of the file in config_directory to install as the service’s user.toml. Lets one directory hold several, e.g. user-ha.toml. |
override_package_config |
false |
Load configuration and hooks from config_directory instead of the ones baked into the package, via the supervisor’s --config-from. |
Event stream (Chef Automate)
Reports supervisor and service events to a Chef Automate Application Dashboard.
| Option | Default | Description |
|---|---|---|
event_stream_application |
nil |
Application name to report under. |
event_stream_environment |
nil |
Application environment for this supervisor. |
event_stream_site |
nil |
Where the services are deployed — a datacenter, or a cloud region. |
event_stream_url |
nil |
Chef Automate URL including port 4222, e.g. automate.example.com:4222. |
event_stream_token |
nil |
Chef Automate API token. |
Choosing the supervisor
By default the stock supervisor that ships with the hab CLI is used, and nothing extra is installed. Setting any of these makes the provisioner install the supervisor you asked for before starting it.
| Option | Default | Description |
|---|---|---|
hab_sup_origin |
core |
Origin of the supervisor package. |
hab_sup_name |
hab-sup |
Name of the supervisor package. |
hab_sup_version |
nil |
Version of the supervisor package to pin. |
hab_sup_release |
nil |
Release of the supervisor package to pin. |
hab_sup_artifact_name |
nil |
Filename of a local supervisor .hart to upload and run. Must be in the results directory. |
These four identity options combine into a package identifier — core/hab-sup, core/hab-sup/1.6.652, and so on — installed with hab pkg install. When hab_sup_artifact_name is given instead, that artifact is uploaded alongside your service artifact and installed from the path it lands at.
Leave all five unset and the converge is unchanged: no supervisor package is installed and the one bundled with the hab CLI is used.
Examples
Run a package from Builder
---
driver:
name: vagrant
provisioner:
name: habitat
hab_license: accept
package_origin: core
package_name: redis
verifier:
name: inspec
platforms:
- name: ubuntu-22.04
suites:
- name: default
Test the artifact you just built
Assumes you have already run a build in hab studio, so a .hart is sitting in results/.
---
driver:
name: vagrant
customize:
memory: 2048
provisioner:
name: habitat
hab_license: accept
package_origin: mycompany
package_name: wildfly
results_directory: results
install_latest_artifact: true
verifier:
name: inspec
platforms:
- name: ubuntu-22.04
suites:
- name: default
verifier:
inspec_tests:
- tests
To pin an exact artifact instead of taking the newest, swap install_latest_artifact for artifact_name:
provisioner:
name: habitat
hab_license: accept
results_directory: results
artifact_name: mycompany-wildfly-26.1.1-20240115194501-x86_64-linux.hart
Supply a user.toml
Assumes a configs/user.toml next to your kitchen.yml.
provisioner:
name: habitat
hab_license: accept
package_origin: mycompany
package_name: wildfly
channel: unstable
config_directory: configs
To have the supervisor use the hooks and config files from that directory rather than the ones inside the package, add override_package_config: true.
Two services bound together
One suite per service, with the second peering to and binding against the first. This example uses a Docker-based driver so the containers can be linked.
---
driver:
name: docker
provisioner:
name: habitat
hab_license: accept
verifier:
name: inspec
platforms:
- name: ubuntu-22.04
suites:
- name: elasticsearch
provisioner:
package_origin: core
package_name: elasticsearch
driver:
instance_name: elastic
- name: kibana
provisioner:
package_origin: core
package_name: kibana
hab_sup_peer:
- elastic
hab_sup_bind:
- elasticsearch:elasticsearch.default
driver:
instance_name: kibana
links: elastic:elastic
Report to a Chef Automate dashboard
provisioner:
name: habitat
hab_license: accept
hab_version: latest
event_stream_application: Effortless
event_stream_environment: stable
event_stream_site: <%= ENV["region"] %>
event_stream_url: automate.example.com:4222
event_stream_token: <%= ENV["automate_token"] %>
Troubleshooting
The converge hangs, then fails after five minutes. The service never appeared in hab svc status. Usually the package has no run hook, or it crashed on startup. Run kitchen login and check hab svc status and journalctl -u hab-sup (Linux) or the Habitat service’s log (Windows). Raise service_load_timeout only if the service is genuinely slow to start.
Habitat license not accepted, and the supervisor never starts. Set hab_license: accept in your provisioner config.
You must specify a 'package_origin' and 'package_name' to use the 'install_latest_artifact' option. install_latest_artifact finds the newest .hart by matching <package_origin>-<package_name>-*.hart, so it needs both to know what to look for.
The .hart is not found, or the wrong one is uploaded. Check results_directory. Auto-detection only looks in results, ../results, and ../../results relative to kitchen.yml; anywhere else must be set explicitly.
My custom supervisor is not being used. Check that hab_sup_artifact_name names a file that is actually in the results directory. If you pinned a version or release instead, confirm that identifier exists in the depot; the converge fails at hab pkg install when it does not.
A bind fails with an unsatisfied service group. hab_sup_bind entries are name:service.group. The bound service must already be running and reachable — check that hab_sup_peer points at it and that the network between the two machines allows the gossip port.