Skip to content

chant carve (emit / bridge / apply)

chant carve emit --from <tf-dir> --select <address> (--state <tfstate> | --env <env> [--live-name <id>]) [-o <dir>]
chant carve bridge --from <tf-dir> --select <address> [--apply-rewrites] [-o <dir>]
chant carve apply --from <tf-dir> --select <address> --env <env> [--stack <name>] [--write] [--write-source] [-o <dir>]
chant carve status [--from <dir>] [--json]

The carve commands peel a resource out of a Terraform estate into native chant source — incrementally, never the whole estate. The long tail stays in Terraform; “we can’t map this” becomes “leave it where it is,” not a failure. Start with chant carve advise to see what is cheap to carve.

A carve never destroys or recreates. It reuses the adopt-an-orphan loop: Terraform stops managing the resource, chant adopts the still-live resource, and you sit at the observe position until you turn the dial to apply. Every step before graduation is reversible with terraform import.

The flow is three steps.

Adopts the selected resource into typed chant source and reports the boundary — every dependency edge the carve would cut. There are two adoption sources:

  • --state <tfstate> (recommended) adopts offline, straight from the Terraform state file. This is the correct source for a Terraform-managed resource: it is created through the provider API, not CloudFormation, so it is not in any CFN stack, but its resolved attributes are in the state. No cloud call.
  • --env <env> adopts via the live cloud->code import path (for resources already in a CloudFormation stack). It selects by native type; if the stack has several resources of that type, narrow it with --live-name <logical-id>. This path is exercised end to end against a real AWS endpoint by just carve-emit-e2e (Floci in Docker).
chant carve emit --from ./infra --select aws_s3_bucket.assets --state ./terraform.tfstate

The state path emits a native chant Declarable with CloudFormation-style properties mapped from the Terraform attributes; attributes without a mapping are preserved in a reference comment so nothing is dropped. It covers 78 common AWS carve targets across S3, IAM, DynamoDB, SNS/SQS, Lambda, KMS, Secrets Manager, SSM, ECR, CloudWatch, Route 53, EFS, EC2 (instances + networking), autoscaling, ELBv2, RDS, ElastiCache, API Gateway, Step Functions, ACM, and ECS; advise and emit cover exactly the same AWS types, so there is no rank-it-but-can’t-emit gap. A type outside that set is refused with the supported list, the same way on both --state and --env. Kubernetes types are ranked by advise but cannot be emitted yet. Nothing is patched or applied; the resource lands at the observe position.

After emit, chant lint ./out --lexicon aws audits the adopted resource — advisory findings you may choose to fix, and errors that block chant build (e.g. a bucket with no public-access block). Auditing what you inherited is part of the point.

Generates the edits to the Terraform you leave behind so its plan stays valid once the resource is chant-owned.

  • Inbound edges (a survivor reads the carved resource) become a data source plus rewired references. Required immediately, or terraform plan errors on the dangling reference.
  • Outbound edges (the carved resource read a survivor) become deferred deploy-time inputs, wired at apply.
chant carve bridge --from ./infra --select aws_s3_bucket.assets

Safe by default: it writes the proposed data sources, rewritten survivor files, and a runbook to an output directory for review. --apply-rewrites edits the survivor .tf in place. The rewrite is idempotent, and it excises the carved resource’s own declaration (and any folded sub-resources) from the survivor source — without that, the next terraform apply would re-create what terraform state rm released.

Resolves the ownership marker that makes the resource chant-owned and finalizes the ordered apply runbook.

chant carve apply --from ./infra --select aws_s3_bucket.assets --env prod --stack assets

This is BYOL-honest: it makes no cloud call. It computes the marker and the plan; the apply itself is whatever lifecycle you brought — the native CLI, a CI pipeline, an ApplyOp — and it stamps the ownership marker the emitted source carries. --write saves the graduation doc. --write-source stamps the ownership marker into the emitted chant source as well, so the adopted declaration carries the identity chant lifecycle reads back.

Read-only. It walks the tree under --from for every *.carve.json manifest the three steps above write. Without --from it walks the current directory. Each carved address prints as one row with the step it has reached, so a renderer no longer has to discover manifests by guessing depth.

chant carve status --from ./infra
chant carve status --from ./infra --json

--json emits the same rows as one document on stdout. An empty tree says so and points at carve emit.

Manifests record file paths relative to their own directory, so a tree that moves still reads correctly.

# 1. see what's cheap to carve
chant carve advise --from ./infra
# 2. adopt the bucket into chant source, see its boundary
chant carve emit --from ./infra --select aws_s3_bucket.assets --env prod
# 3. Terraform stops managing it (does NOT destroy it)
terraform state rm aws_s3_bucket.assets
# 4. patch the survivors so their plan stays valid
chant carve bridge --from ./infra --select aws_s3_bucket.assets --apply-rewrites
terraform plan && terraform apply
# 5. graduate: mark it chant-owned, then apply with your lifecycle
chant carve apply --from ./infra --select aws_s3_bucket.assets --env prod --write

At every step before 5, rollback is terraform import aws_s3_bucket.assets <id>.

These are the emit, boundary-bridging, and apply-graduation phases of the strangler-fig design (#197), built on the read-only advisor (#214). Emit targets a Declarable (a single native resource); a whole service with a deploy pipeline is a Component, not a leaf carve. Removing the carved resource’s own block from the .tf is part of the bridge patch, not a manual runbook step. Emit adopts the AWS types the carve table lists plus kubernetes_manifest, whose kind is read out of the manifest body in state so one rule covers every CRD. The typed kubernetes_* resources and the google types the advisor ranks are refused by emit, and every kubernetes type is refused by bridge until it has a data-source mapping (#2034).