chant carve (emit / bridge / apply)
Synopsis
Section titled “Synopsis”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]Description
Section titled “Description”The carve commands peel a resource out of a Terraform estate into native chant source, one resource at a time. The long tail stays in Terraform, and a resource chant cannot map is left where it is. 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.
1. carve emit — adopt into chant source
Section titled “1. carve emit — adopt into chant source”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 byjust carve-emit-e2e(Floci in Docker).
chant carve emit --from ./infra --select aws_s3_bucket.assets --state ./terraform.tfstateThe 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. 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 stays 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).
2. carve bridge — patch the survivors
Section titled “2. carve bridge — patch the survivors”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
datasource plus rewired references. Required immediately, orterraform planerrors 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.assetsThe data source is usually the carved resource’s own type with its identity attribute in the body. A carve provider can declare a different shape when the provider’s read-back differs: the data-source type, where each argument comes from in the carved body, and the attribute path survivors read through. kubernetes_manifest is the case that needs it, since the kubernetes provider ships no manifest data source:
data "kubernetes_resource" "app_config" { api_version = "v1" kind = "ConfigMap"
metadata { name = "app-config" namespace = "apps" }}Rewrites follow the shape, so kubernetes_manifest.app_config.object.metadata.name becomes data.kubernetes_resource.app_config.object.metadata.name, and a reference through the resource-only manifest attribute resolves to object. A type with a dotted identity attribute and no shape is refused, with the message naming what is missing.
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.
3. carve apply — graduate
Section titled “3. carve apply — graduate”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 assetsIt makes no cloud call. It computes the marker and the plan; you run the apply with your own tooling, such as the native CLI, a CI pipeline or an ApplyOp. That apply 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.
4. carve status
Section titled “4. carve status”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 reads one list instead of guessing how deep the manifests sit.
chant carve status --from ./infrachant 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.
The handoff, end to end
Section titled “The handoff, end to end”# 1. see what's cheap to carvechant carve advise --from ./infra
# 2. adopt the bucket into chant source, see its boundarychant 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 validchant carve bridge --from ./infra --select aws_s3_bucket.assets --apply-rewritesterraform plan && terraform apply
# 5. graduate: mark it chant-owned, then apply with your lifecyclechant carve apply --from ./infra --select aws_s3_bucket.assets --env prod --writeAt 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. Bridge refuses only a type whose identity attribute is a path into nested values and whose carve provider declares no data-source shape; kubernetes_manifest declares one (#2034), so it bridges.