Getting Started
Most chant lexicons ask you to declare infrastructure in TypeScript. This one
starts from the .tf files you already have. You name the root modules, chant
parses them, and the blocks join the build as entities that the post-synth
checks can read. Nothing is written back over the HCL.
Install
Section titled “Install”npm install --save-dev @intentius/chant-lexicon-terraformName your roots
Section titled “Name your roots”Add the terraform namespace to chant.config.ts. Importing the package is
what brings the key into ChantConfig.
import type { ChantConfig } from "@intentius/chant/config";import "@intentius/chant-lexicon-terraform";
export default { lexicons: ["terraform"], terraform: { binary: "terraform", roots: { app: { dir: "./terraform" }, }, },} satisfies ChantConfig;dir resolves against the project root, the directory holding
chant.config.ts, because a .tf tree usually sits beside the typed source
rather than inside it. Each root also takes an optional workspace, a
varFiles list and a backendConfig map.
binary is "terraform" or "tofu". The two are wire-compatible for
everything this lexicon does, so the choice is recorded rather than guessed.
chant buildEvery block of every configured root becomes one entity, keyed
<root>/<address>:
| HCL | Entity type | Key |
|---|---|---|
terraform { ... } | Terraform::Terraform | app/terraform |
provider "null" {} | Terraform::Provider | app/provider.null |
resource "null_resource" "web" {} | Terraform::Resource | app/null_resource.web |
data "aws_ami" "base" {} | Terraform::Data | app/data.aws_ami.base |
module "cdn" {} | Terraform::Module | app/module.cdn |
variable "region" {} | Terraform::Variable | app/var.region |
output "url" {} | Terraform::Output | app/output.url |
locals { ... } | Terraform::Locals | app/locals |
Each entity carries props.address, props.body (the block, verbatim from the
parser), props.file and props.root.
The build emits no Terraform of its own. Your .tf files stay the only source
of truth, and terraform apply keeps working exactly as before.
What the checks tell you
Section titled “What the checks tell you”TF001 fires once per root whose terraform block declares neither a
backend "<type>" nor a cloud {}. Such a root keeps its state in a local
terraform.tfstate: unshared, unlocked, and holding every resource attribute
in plaintext. The first apply from a second machine starts from an empty state
and proposes to create the estate again.
Add a backend to clear it:
terraform { backend "s3" { bucket = "acme-tfstate" key = "app/terraform.tfstate" region = "us-east-1" }}Failure modes
Section titled “Failure modes”A root whose dir does not exist, or one holding a .tf the parser refuses,
produces a warning naming the root and contributes no entities. The rest of the
build carries on, and the other roots are unaffected. Reading someone else’s
estate is the job here, so half of it parsing is more useful than none of it.
Driving the root: TerraformApplyOp on the local executor
Section titled “Driving the root: TerraformApplyOp on the local executor”Reading the root’s blocks doesn’t run anything. To actually init/plan/apply
it, declare a TerraformApplyOp and run it with chant run:
import { TerraformApplyOp } from "@intentius/chant-lexicon-terraform";
export const { op } = TerraformApplyOp({ name: "app-apply", root: "app", gate: "never", // no approval step, so a run goes straight through, see below});gate: "never" matters here specifically: every other gate mode emits a Gate
phase, and a run that reaches a gate nobody has approved records a pending fact
on the gate ledger, ends with status gated and exits 3, so Apply never runs
until someone records the resolution with chant approve. With it, chant run app-apply executes Init, Plan and Apply straight through in your terminal. See
Ops for every TerraformApplyOp option, the four activities and
builders underneath it, and the compensation rule for a failed apply.
The worked example lives in examples/getting-started/: a config, a
terraform/ root with a local backend, and the entities it produces.
examples/apply-gated/ pairs a gated TerraformApplyOp with an explicit
rollback command.