Converting Existing Terraform HCL to CDKTF Python

Migrating legacy HCL to programmatic infrastructure requires strict state preservation. Converting to CDKTF Python eliminates configuration drift and enables deterministic CI/CD validation pipelines. This task sits within Terraform Provider Bridging, which translates Terraform provider schemas into the typed Python classes your converted code will target.

This guide details the exact workflow for safe translation. We prioritize state integrity and secure credential handling. Every step includes testable Python IaC patterns.

Pre-Migration State Audit & Backend Locking

Before modifying infrastructure code, verify remote state integrity. Enforce backend locking immediately. Immutable state snapshots prevent catastrophic resource recreation during translation.

Always export a full state backup before executing synthesis commands. Cross-reference provider configurations against your target environment.

CLI: State Audit & Backup

terraform state list
terraform state pull > state-backup.json

Validate the backend lock status. Confirm all resource addresses resolve correctly. For comprehensive backend compatibility and lock mechanisms, review CDKTF Workflows & Terraform Synthesis.

Validation Steps:

  • Run terraform state list to verify resource inventory
  • Export terraform state pull > state-backup.json for immutable rollback
  • Confirm backend lock status via provider dashboard or CLI output

Automated Translation via cdktf convert

The cdktf convert utility generates baseline Python constructs from legacy HCL files. It does not produce production-ready code. You must explicitly pin provider versions to prevent silent breaking changes.

CLI: Automated HCL Translation

cdktf init --template="python" --local
# Convert a single HCL file to Python
cat main.tf | cdktf convert --language python --provider "hashicorp/aws@~> 6.0" > main.py

After generation, verify cdktf.json provider constraints match your requirements.txt. Replace dynamic HCL blocks with native Python iteration patterns. Run cdktf synth to validate the initial JSON payload.

Validation Steps:

  • Audit cdktf.json for exact provider version constraints
  • Run cdktf synth and inspect the output directory
  • Compare synthesized JSON against the original terraform plan output for parity

Python 3.9+ Type Enforcement & Construct Refactoring

Automated translation strips type safety. Apply strict typing annotations to all construct parameters. Replace HCL count and for_each directives with Python list comprehensions or dictionary mappings.

from typing import Dict, List, Optional, Any
import os
from constructs import Construct
from cdktf import TerraformStack
from cdktf_cdktf_provider_aws.provider import AwsProvider
from cdktf_cdktf_provider_aws.s3_bucket import S3Bucket

class MigratedStack(TerraformStack):
    def __init__(
        self,
        scope: Construct,
        ns: str,
        config: Dict[str, Optional[Any]],
    ) -> None:
        super().__init__(scope, ns)

        # Prefer OIDC/IAM roles over static credentials in production
        AwsProvider(
            self,
            "aws",
            region=config.get("region", "us-east-1"),
        )

        bucket_name = config.get("bucket_name")
        if not bucket_name:
            raise ValueError("bucket_name is required for S3 provisioning")

        S3Bucket(
            self,
            "data",
            bucket=bucket_name,
            tags=config.get("tags", {}),
        )

Enforce mypy --strict or pyright checks before synthesis. This catches AttributeError exceptions caused by untyped nested configurations.

Validation Steps:

  • Add explicit type hints to all __init__ parameters
  • Replace HCL loops with Python comprehensions
  • Execute mypy --strict main.py and resolve all warnings

State Reconciliation & Drift Detection

Legacy resource addresses rarely match CDKTF-generated identifiers. Map legacy IDs to the new construct tree safely. Use terraform import in the synthesized output directory to reconcile state files without triggering destructive replacements.

CLI: State Address Mapping & Import

# 1. Synthesize to generate the Terraform configuration
cdktf synth

# 2. Import existing resources into the synthesized Terraform state
terraform -chdir=cdktf.out/stacks/ import aws_s3_bucket.data 

# 3. Verify zero drift after import
terraform -chdir=cdktf.out/stacks/ plan -detailed-exitcode

When addressing provider-specific state mapping and ID translation, consult Terraform Provider Bridging. If your original HCL declared aliased or multi-region providers, replicate that topology with the patterns in Using Multiple Terraform Providers in One CDKTF Stack so imported resources resolve to the correct provider. A -detailed-exitcode of 0 from terraform plan confirms zero drift.

Validation Steps:

  • Run cdktf synth to generate updated JSON payloads
  • Execute terraform import for each legacy resource in the synthesized stack directory
  • Validate terraform plan -detailed-exitcode returns 0 (no changes)

Production Rollback & CI/CD Pipeline Handoff

Safe deployment requires automated rollback triggers. Configure your CI/CD runner to execute plan validation before any production deployment. Implement state snapshot restoration on failure to maintain infrastructure consistency.

import sys
import subprocess

def validate_drift(stack_name: str) -> int:
    """Programmatic drift validation for CI/CD integration.

    Returns 0 if no changes detected, 1 if drift found or plan failed.
    Uses terraform plan --detailed-exitcode: 0=no changes, 2=changes pending, 1=error.
    """
    cdktf_out_dir = f"cdktf.out/stacks/{stack_name}"
    result = subprocess.run(
        ["terraform", "-chdir", cdktf_out_dir, "plan", "-detailed-exitcode"],
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode == 0:
        print("State matches synthesized output: no changes pending.")
        return 0
    elif result.returncode == 2:
        print("Drift detected: changes are pending.", file=sys.stderr)
        return 1
    else:
        print(f"Plan failed:\n{result.stderr}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    stack = sys.argv[1] if len(sys.argv) > 1 else "MigratedStack"
    sys.exit(validate_drift(stack))

Establish pre-deploy validation hooks. Schedule post-deploy drift monitoring. Define explicit failure boundaries to prevent partial deployments.

Validation Steps:

  • Run cdktf synth then terraform plan before cdktf deploy
  • Implement automated state snapshot restore on pipeline failure
  • Schedule drift detection jobs for continuous compliance monitoring

Common Mistakes

  • Assuming cdktf convert yields production-ready code without manual type annotation.
  • Failing to pin provider versions in cdktf.json, causing silent synthesis drift.
  • Deploying before executing terraform import for existing resources, causing state divergence.
  • Ignoring Python typing boundaries, leading to runtime AttributeError on nested configs.
  • Skipping cdktf synth validation in CI/CD, resulting in undetected JSON payload errors.

FAQ

How do I preserve existing Terraform state during CDKTF conversion? Lock the remote backend. Export a state snapshot with terraform state pull. Run cdktf synth. Use terraform import in the synthesized output directory to map legacy addresses before deploying.

Does cdktf convert handle dynamic blocks and for_each correctly? It generates baseline equivalents. Complex loops require manual refactoring into native Python comprehensions with strict type hints. Dynamic blocks typically become Python if statements or loops over construct IDs.

How do I enforce Python 3.9+ typing for complex infrastructure variables? Use typing.Dict[str, Any], typing.Optional, and typing.List for heterogeneous inputs. Validate at instantiation with pydantic or isinstance checks to prevent runtime synthesis failures.

What is the safest rollback procedure if cdktf deploy fails? Halt the pipeline immediately. Restore the pre-migration state snapshot via terraform state push. Revert to the legacy branch until drift is reconciled. Run terraform plan to verify the restored state before re-attempting the migration.

Conclusion

The cdktf convert tool gets you 60-70% of the way through a migration—the rest is manual type annotation, construct refactoring, and state import. The most critical step is the terraform import phase: skipping it and deploying directly will either fail (if resources already exist and names conflict) or create duplicate resources (if CDKTF uses different naming). Budget time for this and validate zero drift before declaring the migration complete.