/** * The AWS carve-out resource table — the single source of truth for which AWS * Terraform types chant can carve, and how. * * Both the advisor (tier map / scoring) and `carve emit --state` derive from * this one list, so advise and emit cover exactly the same AWS types: advise * never ranks a resource emit cannot produce, and emit never claims a type * advise did not score. Adding a type here lights it up for both at once. * * Each entry maps the common, high-confidence Terraform attributes to their * CloudFormation property names (chant AWS constructors take CFN PascalCase * props). Attributes without a mapping here are preserved in a reference * comment on emit, never dropped — a curated seed, not a full TF↔CFN transform. */ /** A Terraform attribute → CloudFormation property mapping. */ type FieldSpec = string | { prop: string; transform: (v: unknown) => unknown }; export interface AwsCarveType { /** Terraform resource type, e.g. `aws_s3_bucket`. */ tfType: string; /** Native-spec map tier: 1 clean 1:1, 2 some reshaping, 3 heavy composite. */ tier: 1 | 2 | 3; /** CloudFormation type, e.g. `AWS::S3::Bucket`. */ nativeType: string; /** chant AWS lexicon constructor, e.g. `Bucket` (verified against generated exports). */ ctor: string; /** The HCL attribute carrying the physical name (for the live-import hint / graph identity). */ identityAttr?: string; /** Terraform attribute → CFN property mappings. */ fields: Record; /** Map the Terraform `tags` map to CloudFormation `Tags` list. */ tags?: boolean; } export const AWS_LEXICON_IMPORT = "@intentius/chant-lexicon-aws"; /** Parse a Terraform JSON-string attribute (IAM policy docs) into an object. */ const asJson = (v: unknown): unknown => { if (typeof v !== "string") return v; try { return JSON.parse(v); } catch { return v; } }; const json = (prop: string): FieldSpec => ({ prop, transform: asJson }); /** * Terraform state renders a nested block as a one-element list. Take that * entry (or the object itself, if the provider wrote it unwrapped). */ function firstBlock(value: unknown): Record | undefined { const candidate = Array.isArray(value) ? value[0] : value; return candidate && typeof candidate === "object" ? (candidate as Record) : undefined; } /** * A list of TF server-side-encryption `rule` blocks → the CFN `BucketEncryption` * property. Shared by the bucket's own (deprecated, still state-resident) * `server_side_encryption_configuration` block and by the modern * `aws_s3_bucket_server_side_encryption_configuration` sub-resource, whose * `rule` list has the same shape. */ function sseRulesToCfn(rules: unknown): unknown { if (!Array.isArray(rules)) return undefined; const cfnRules: Array> = []; for (const raw of rules) { if (!raw || typeof raw !== "object") continue; const rule = raw as Record; const out: Record = {}; const byDefault = firstBlock(rule.apply_server_side_encryption_by_default); const algorithm = byDefault?.sse_algorithm; if (typeof algorithm === "string" && algorithm) { const sse: Record = { SSEAlgorithm: algorithm }; const kmsKey = byDefault?.kms_master_key_id; if (typeof kmsKey === "string" && kmsKey) sse.KMSMasterKeyID = kmsKey; out.ServerSideEncryptionByDefault = sse; } if (typeof rule.bucket_key_enabled === "boolean") out.BucketKeyEnabled = rule.bucket_key_enabled; if (Object.keys(out).length) cfnRules.push(out); } return cfnRules.length ? { ServerSideEncryptionConfiguration: cfnRules } : undefined; } /** * The bucket's own deprecated `versioning` block, as state still carries it. * Only an enabled bucket says anything CloudFormation needs: a bucket that * never had versioning has no `VersioningConfiguration` at all, so `enabled = * false` maps to nothing and the block stays in the unmapped comment. */ function legacyVersioningToCfn(value: unknown): unknown { const block = firstBlock(value); return block?.enabled === true ? { Status: "Enabled" } : undefined; } export const AWS_CARVE_TYPES: AwsCarveType[] = [ // ── Storage & data ── { tfType: "aws_s3_bucket", tier: 1, nativeType: "AWS::S3::Bucket", ctor: "Bucket", identityAttr: "bucket", fields: { bucket: "BucketName", // The provider still resolves these two into the bucket's own state, even // when the config declares them through sub-resources (#1637). server_side_encryption_configuration: { prop: "BucketEncryption", transform: (v) => sseRulesToCfn(firstBlock(v)?.rule) }, versioning: { prop: "VersioningConfiguration", transform: legacyVersioningToCfn }, }, tags: true }, { tfType: "aws_dynamodb_table", tier: 2, nativeType: "AWS::DynamoDB::Table", ctor: "Table", identityAttr: "name", fields: { name: "TableName", billing_mode: "BillingMode" }, tags: true }, { tfType: "aws_efs_file_system", tier: 1, nativeType: "AWS::EFS::FileSystem", ctor: "EFSFileSystem", fields: { encrypted: "Encrypted", performance_mode: "PerformanceMode", throughput_mode: "ThroughputMode" }, tags: true }, // ── Messaging ── { tfType: "aws_sns_topic", tier: 1, nativeType: "AWS::SNS::Topic", ctor: "Topic", identityAttr: "name", fields: { name: "TopicName", display_name: "DisplayName", fifo_topic: "FifoTopic" }, tags: true }, { tfType: "aws_sqs_queue", tier: 1, nativeType: "AWS::SQS::Queue", ctor: "Queue", identityAttr: "name", fields: { name: "QueueName", visibility_timeout_seconds: "VisibilityTimeout", fifo_queue: "FifoQueue", delay_seconds: "DelaySeconds" }, tags: true }, // ── IAM ── { tfType: "aws_iam_role", tier: 2, nativeType: "AWS::IAM::Role", ctor: "Role", identityAttr: "name", fields: { name: "RoleName", assume_role_policy: json("AssumeRolePolicyDocument"), managed_policy_arns: "ManagedPolicyArns", description: "Description", path: "Path" }, tags: true }, { tfType: "aws_iam_policy", tier: 1, nativeType: "AWS::IAM::ManagedPolicy", ctor: "ManagedPolicy", identityAttr: "name", fields: { name: "ManagedPolicyName", policy: json("PolicyDocument"), description: "Description", path: "Path" } }, { tfType: "aws_iam_instance_profile", tier: 1, nativeType: "AWS::IAM::InstanceProfile", ctor: "InstanceProfile", identityAttr: "name", fields: { name: "InstanceProfileName", path: "Path", role: { prop: "Roles", transform: (v) => (v === undefined ? v : [v]) } } }, // ── Registry, keys, secrets, config ── { tfType: "aws_ecr_repository", tier: 1, nativeType: "AWS::ECR::Repository", ctor: "ECRRepository", identityAttr: "name", fields: { name: "RepositoryName", image_tag_mutability: "ImageTagMutability" }, tags: true }, { tfType: "aws_kms_key", tier: 2, nativeType: "AWS::KMS::Key", ctor: "KmsKey", fields: { description: "Description", enable_key_rotation: "EnableKeyRotation", deletion_window_in_days: "PendingWindowInDays", is_enabled: "Enabled" }, tags: true }, { tfType: "aws_secretsmanager_secret", tier: 1, nativeType: "AWS::SecretsManager::Secret", ctor: "Secret", identityAttr: "name", fields: { name: "Name", description: "Description", kms_key_id: "KmsKeyId" }, tags: true }, { tfType: "aws_ssm_parameter", tier: 1, nativeType: "AWS::SSM::Parameter", ctor: "SsmParameter", identityAttr: "name", fields: { name: "Name", type: "Type", value: "Value", tier: "Tier", description: "Description" } }, // ── Compute ── { tfType: "aws_lambda_function", tier: 2, nativeType: "AWS::Lambda::Function", ctor: "Function", identityAttr: "function_name", fields: { function_name: "FunctionName", runtime: "Runtime", handler: "Handler", memory_size: "MemorySize", timeout: "Timeout", description: "Description", architectures: "Architectures" }, tags: true }, { tfType: "aws_ecs_service", tier: 3, nativeType: "AWS::ECS::Service", ctor: "EcsService", identityAttr: "name", fields: { name: "ServiceName", cluster: "Cluster", desired_count: "DesiredCount", launch_type: "LaunchType", task_definition: "TaskDefinition" }, tags: true }, // ── Networking ── { tfType: "aws_vpc", tier: 1, nativeType: "AWS::EC2::VPC", ctor: "Vpc", fields: { cidr_block: "CidrBlock", enable_dns_support: "EnableDnsSupport", enable_dns_hostnames: "EnableDnsHostnames", instance_tenancy: "InstanceTenancy" }, tags: true }, { tfType: "aws_subnet", tier: 1, nativeType: "AWS::EC2::Subnet", ctor: "Subnet", fields: { vpc_id: "VpcId", cidr_block: "CidrBlock", availability_zone: "AvailabilityZone", map_public_ip_on_launch: "MapPublicIpOnLaunch" }, tags: true }, { tfType: "aws_security_group", tier: 2, nativeType: "AWS::EC2::SecurityGroup", ctor: "SecurityGroup", identityAttr: "name", fields: { description: "GroupDescription", name: "GroupName", vpc_id: "VpcId" }, tags: true }, { tfType: "aws_route_table", tier: 2, nativeType: "AWS::EC2::RouteTable", ctor: "RouteTable", fields: { vpc_id: "VpcId" }, tags: true }, { tfType: "aws_internet_gateway", tier: 1, nativeType: "AWS::EC2::InternetGateway", ctor: "InternetGateway", fields: {}, tags: true }, { tfType: "aws_eip", tier: 1, nativeType: "AWS::EC2::EIP", ctor: "EIP", fields: { domain: "Domain", instance: "InstanceId" }, tags: true }, { tfType: "aws_nat_gateway", tier: 2, nativeType: "AWS::EC2::NatGateway", ctor: "NatGateway", fields: { subnet_id: "SubnetId", allocation_id: "AllocationId", connectivity_type: "ConnectivityType" }, tags: true }, // ── DNS & observability ── { tfType: "aws_route53_zone", tier: 1, nativeType: "AWS::Route53::HostedZone", ctor: "HostedZone", identityAttr: "name", fields: { name: "Name", comment: "HostedZoneConfig" } }, { tfType: "aws_cloudwatch_log_group", tier: 1, nativeType: "AWS::Logs::LogGroup", ctor: "LogGroup", identityAttr: "name", fields: { name: "LogGroupName", retention_in_days: "RetentionInDays", kms_key_id: "KmsKeyId" }, tags: true }, { tfType: "aws_cloudwatch_metric_alarm", tier: 2, nativeType: "AWS::CloudWatch::Alarm", ctor: "Alarm", identityAttr: "alarm_name", fields: { alarm_name: "AlarmName", comparison_operator: "ComparisonOperator", metric_name: "MetricName", namespace: "Namespace", threshold: "Threshold", evaluation_periods: "EvaluationPeriods", period: "Period", statistic: "Statistic", alarm_description: "AlarmDescription" }, tags: true }, { tfType: "aws_cloudwatch_event_rule", tier: 2, nativeType: "AWS::Events::Rule", ctor: "EventRule", identityAttr: "name", fields: { name: "Name", schedule_expression: "ScheduleExpression", event_pattern: json("EventPattern"), description: "Description", state: "State" } }, // ── Compute (EC2 / ECS / ASG) ── { tfType: "aws_instance", tier: 2, nativeType: "AWS::EC2::Instance", ctor: "Instance", fields: { ami: "ImageId", instance_type: "InstanceType", subnet_id: "SubnetId", key_name: "KeyName", availability_zone: "AvailabilityZone", iam_instance_profile: "IamInstanceProfile" }, tags: true }, { tfType: "aws_launch_template", tier: 2, nativeType: "AWS::EC2::LaunchTemplate", ctor: "LaunchTemplate", identityAttr: "name", fields: { name: "LaunchTemplateName" }, tags: true }, { tfType: "aws_autoscaling_group", tier: 2, nativeType: "AWS::AutoScaling::AutoScalingGroup", ctor: "AutoScalingGroup", identityAttr: "name", fields: { name: "AutoScalingGroupName", min_size: "MinSize", max_size: "MaxSize", desired_capacity: "DesiredCapacity", vpc_zone_identifier: "VPCZoneIdentifier", health_check_type: "HealthCheckType" } }, { tfType: "aws_ecs_cluster", tier: 1, nativeType: "AWS::ECS::Cluster", ctor: "EcsCluster", identityAttr: "name", fields: { name: "ClusterName" }, tags: true }, { tfType: "aws_ecs_task_definition", tier: 3, nativeType: "AWS::ECS::TaskDefinition", ctor: "TaskDefinition", identityAttr: "family", fields: { family: "Family", cpu: "Cpu", memory: "Memory", network_mode: "NetworkMode", execution_role_arn: "ExecutionRoleArn", task_role_arn: "TaskRoleArn" }, tags: true }, { tfType: "aws_ebs_volume", tier: 1, nativeType: "AWS::EC2::Volume", ctor: "EC2Volume", fields: { availability_zone: "AvailabilityZone", size: "Size", type: "VolumeType", encrypted: "Encrypted", iops: "Iops" }, tags: true }, // ── Load balancing (ELBv2; aws_alb is a legacy alias) ── { tfType: "aws_lb", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::LoadBalancer", ctor: "LoadBalancer", identityAttr: "name", fields: { name: "Name", load_balancer_type: "Type", internal: { prop: "Scheme", transform: (v) => (v === true ? "internal" : v === false ? "internet-facing" : v) } }, tags: true }, { tfType: "aws_alb", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::LoadBalancer", ctor: "LoadBalancer", identityAttr: "name", fields: { name: "Name", load_balancer_type: "Type", internal: { prop: "Scheme", transform: (v) => (v === true ? "internal" : v === false ? "internet-facing" : v) } }, tags: true }, { tfType: "aws_lb_target_group", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::TargetGroup", ctor: "TargetGroup", identityAttr: "name", fields: { name: "Name", port: "Port", protocol: "Protocol", vpc_id: "VpcId", target_type: "TargetType" }, tags: true }, { tfType: "aws_lb_listener", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::Listener", ctor: "Listener", fields: { port: "Port", protocol: "Protocol", load_balancer_arn: "LoadBalancerArn" } }, // ── Databases & cache ── { tfType: "aws_db_instance", tier: 2, nativeType: "AWS::RDS::DBInstance", ctor: "DbInstance", identityAttr: "identifier", fields: { identifier: "DBInstanceIdentifier", engine: "Engine", engine_version: "EngineVersion", instance_class: "DBInstanceClass", allocated_storage: "AllocatedStorage", db_name: "DBName", multi_az: "MultiAZ", storage_encrypted: "StorageEncrypted" }, tags: true }, { tfType: "aws_db_subnet_group", tier: 1, nativeType: "AWS::RDS::DBSubnetGroup", ctor: "RDSDBSubnetGroup", identityAttr: "name", fields: { name: "DBSubnetGroupName", description: "DBSubnetGroupDescription", subnet_ids: "SubnetIds" }, tags: true }, { tfType: "aws_elasticache_cluster", tier: 2, nativeType: "AWS::ElastiCache::CacheCluster", ctor: "CacheCluster", identityAttr: "cluster_id", fields: { cluster_id: "ClusterName", engine: "Engine", node_type: "CacheNodeType", num_cache_nodes: "NumCacheNodes", engine_version: "EngineVersion" }, tags: true }, { tfType: "aws_elasticache_replication_group", tier: 2, nativeType: "AWS::ElastiCache::ReplicationGroup", ctor: "ReplicationGroup", identityAttr: "replication_group_id", fields: { replication_group_id: "ReplicationGroupId", description: "ReplicationGroupDescription", node_type: "CacheNodeType", engine: "Engine" }, tags: true }, // ── DNS, CDN & API ── { tfType: "aws_route53_record", tier: 2, nativeType: "AWS::Route53::RecordSet", ctor: "RecordSet", identityAttr: "name", fields: { name: "Name", type: "Type", ttl: "TTL", zone_id: "HostedZoneId", records: "ResourceRecords" } }, { tfType: "aws_api_gateway_rest_api", tier: 2, nativeType: "AWS::ApiGateway::RestApi", ctor: "RestApi", identityAttr: "name", fields: { name: "Name", description: "Description" }, tags: true }, { tfType: "aws_apigatewayv2_api", tier: 2, nativeType: "AWS::ApiGatewayV2::Api", ctor: "HttpApi", identityAttr: "name", fields: { name: "Name", protocol_type: "ProtocolType", description: "Description" }, tags: true }, // ── IAM (users/groups), certs & keys ── { tfType: "aws_iam_user", tier: 1, nativeType: "AWS::IAM::User", ctor: "IAMUser", identityAttr: "name", fields: { name: "UserName", path: "Path" }, tags: true }, { tfType: "aws_iam_group", tier: 1, nativeType: "AWS::IAM::Group", ctor: "IAMGroup", identityAttr: "name", fields: { name: "GroupName", path: "Path" } }, { tfType: "aws_acm_certificate", tier: 2, nativeType: "AWS::CertificateManager::Certificate", ctor: "AcmCertificate", identityAttr: "domain_name", fields: { domain_name: "DomainName", validation_method: "ValidationMethod", subject_alternative_names: "SubjectAlternativeNames" }, tags: true }, { tfType: "aws_kms_alias", tier: 1, nativeType: "AWS::KMS::Alias", ctor: "KMSAlias", identityAttr: "name", fields: { name: "AliasName", target_key_id: "TargetKeyId" } }, // ── Messaging & step functions ── { tfType: "aws_sns_topic_subscription", tier: 2, nativeType: "AWS::SNS::Subscription", ctor: "Subscription", fields: { protocol: "Protocol", endpoint: "Endpoint", topic_arn: "TopicArn" } }, { tfType: "aws_sfn_state_machine", tier: 3, nativeType: "AWS::StepFunctions::StateMachine", ctor: "StateMachine", identityAttr: "name", fields: { name: "StateMachineName", role_arn: "RoleArn", type: "StateMachineType" }, tags: true }, { tfType: "aws_sqs_queue_policy", tier: 2, nativeType: "AWS::SQS::QueuePolicy", ctor: "QueuePolicy", fields: { policy: json("PolicyDocument"), queue_url: { prop: "Queues", transform: (v) => (v === undefined ? v : [v]) } } }, { tfType: "aws_sns_topic_policy", tier: 2, nativeType: "AWS::SNS::TopicPolicy", ctor: "TopicPolicy", fields: { policy: json("PolicyDocument"), arn: { prop: "Topics", transform: (v) => (v === undefined ? v : [v]) } } }, // ── Streaming ── { tfType: "aws_kinesis_stream", tier: 1, nativeType: "AWS::Kinesis::Stream", ctor: "KinesisStream", identityAttr: "name", fields: { name: "Name", shard_count: "ShardCount", retention_period: "RetentionPeriodHours" }, tags: true }, { tfType: "aws_kinesis_firehose_delivery_stream", tier: 3, nativeType: "AWS::KinesisFirehose::DeliveryStream", ctor: "DeliveryStream", identityAttr: "name", fields: { name: "DeliveryStreamName" }, tags: true }, // ── EKS ── { tfType: "aws_eks_cluster", tier: 2, nativeType: "AWS::EKS::Cluster", ctor: "EKSCluster", identityAttr: "name", fields: { name: "Name", role_arn: "RoleArn", version: "Version" }, tags: true }, { tfType: "aws_eks_node_group", tier: 2, nativeType: "AWS::EKS::Nodegroup", ctor: "Nodegroup", identityAttr: "node_group_name", fields: { cluster_name: "ClusterName", node_group_name: "NodegroupName", node_role_arn: "NodeRole", subnet_ids: "Subnets", instance_types: "InstanceTypes", ami_type: "AmiType", capacity_type: "CapacityType", disk_size: "DiskSize" } }, // ── Lambda periphery ── { tfType: "aws_lambda_permission", tier: 1, nativeType: "AWS::Lambda::Permission", ctor: "Permission", fields: { action: "Action", function_name: "FunctionName", principal: "Principal", source_arn: "SourceArn", source_account: "SourceAccount" } }, { tfType: "aws_lambda_event_source_mapping", tier: 2, nativeType: "AWS::Lambda::EventSourceMapping", ctor: "EventSourceMapping", fields: { event_source_arn: "EventSourceArn", function_name: "FunctionName", batch_size: "BatchSize", enabled: "Enabled", starting_position: "StartingPosition", maximum_batching_window_in_seconds: "MaximumBatchingWindowInSeconds" } }, { tfType: "aws_lambda_alias", tier: 1, nativeType: "AWS::Lambda::Alias", ctor: "LambdaAlias", identityAttr: "name", fields: { name: "Name", function_name: "FunctionName", function_version: "FunctionVersion", description: "Description" } }, { tfType: "aws_lambda_layer_version", tier: 2, nativeType: "AWS::Lambda::LayerVersion", ctor: "LayerVersion", identityAttr: "layer_name", fields: { layer_name: "LayerName", description: "Description", compatible_runtimes: "CompatibleRuntimes", compatible_architectures: "CompatibleArchitectures", license_info: "LicenseInfo" } }, // ── Networking (endpoints, routes, peering, flow logs) ── { tfType: "aws_vpc_endpoint", tier: 2, nativeType: "AWS::EC2::VPCEndpoint", ctor: "VPCEndpoint", fields: { vpc_id: "VpcId", service_name: "ServiceName", vpc_endpoint_type: "VpcEndpointType", route_table_ids: "RouteTableIds", subnet_ids: "SubnetIds", security_group_ids: "SecurityGroupIds", private_dns_enabled: "PrivateDnsEnabled", policy: json("PolicyDocument") }, tags: true }, { tfType: "aws_route_table_association", tier: 2, nativeType: "AWS::EC2::SubnetRouteTableAssociation", ctor: "SubnetRouteTableAssociation", fields: { subnet_id: "SubnetId", route_table_id: "RouteTableId" } }, { tfType: "aws_route", tier: 2, nativeType: "AWS::EC2::Route", ctor: "EC2Route", fields: { route_table_id: "RouteTableId", destination_cidr_block: "DestinationCidrBlock", destination_ipv6_cidr_block: "DestinationIpv6CidrBlock", gateway_id: "GatewayId", nat_gateway_id: "NatGatewayId", transit_gateway_id: "TransitGatewayId", vpc_peering_connection_id: "VpcPeeringConnectionId", network_interface_id: "NetworkInterfaceId" } }, { tfType: "aws_egress_only_internet_gateway", tier: 1, nativeType: "AWS::EC2::EgressOnlyInternetGateway", ctor: "EgressOnlyInternetGateway", fields: { vpc_id: "VpcId" } }, { tfType: "aws_vpc_peering_connection", tier: 2, nativeType: "AWS::EC2::VPCPeeringConnection", ctor: "VPCPeeringConnection", fields: { vpc_id: "VpcId", peer_vpc_id: "PeerVpcId", peer_owner_id: "PeerOwnerId", peer_region: "PeerRegion" }, tags: true }, { tfType: "aws_flow_log", tier: 2, nativeType: "AWS::EC2::FlowLog", ctor: "FlowLog", fields: { traffic_type: "TrafficType", log_destination: "LogDestination", log_destination_type: "LogDestinationType", iam_role_arn: "DeliverLogsPermissionArn", log_group_name: "LogGroupName", max_aggregation_interval: "MaxAggregationInterval" }, tags: true }, { tfType: "aws_key_pair", tier: 1, nativeType: "AWS::EC2::KeyPair", ctor: "KeyPair", identityAttr: "key_name", fields: { key_name: "KeyName", public_key: "PublicKeyMaterial" }, tags: true }, // ── Audit & observability ── { tfType: "aws_cloudtrail", tier: 2, nativeType: "AWS::CloudTrail::Trail", ctor: "Trail", identityAttr: "name", fields: { name: "TrailName", s3_bucket_name: "S3BucketName", s3_key_prefix: "S3KeyPrefix", include_global_service_events: "IncludeGlobalServiceEvents", is_multi_region_trail: "IsMultiRegionTrail", enable_logging: "IsLogging", enable_log_file_validation: "EnableLogFileValidation", kms_key_id: "KMSKeyId", sns_topic_name: "SnsTopicName", cloud_watch_logs_group_arn: "CloudWatchLogsLogGroupArn", cloud_watch_logs_role_arn: "CloudWatchLogsRoleArn" }, tags: true }, { tfType: "aws_cloudwatch_dashboard", tier: 1, nativeType: "AWS::CloudWatch::Dashboard", ctor: "CwDashboard", identityAttr: "dashboard_name", fields: { dashboard_name: "DashboardName", dashboard_body: "DashboardBody" } }, // ── Databases & cache (groups, clusters) ── { tfType: "aws_elasticache_subnet_group", tier: 1, nativeType: "AWS::ElastiCache::SubnetGroup", ctor: "EcSubnetGroup", identityAttr: "name", fields: { name: "CacheSubnetGroupName", description: "Description", subnet_ids: "SubnetIds" }, tags: true }, { tfType: "aws_db_parameter_group", tier: 2, nativeType: "AWS::RDS::DBParameterGroup", ctor: "RDSDBParameterGroup", identityAttr: "name", fields: { name: "DBParameterGroupName", family: "Family", description: "Description" }, tags: true }, { tfType: "aws_rds_cluster", tier: 2, nativeType: "AWS::RDS::DBCluster", ctor: "DbCluster", identityAttr: "cluster_identifier", fields: { cluster_identifier: "DBClusterIdentifier", engine: "Engine", engine_version: "EngineVersion", database_name: "DatabaseName", master_username: "MasterUsername", backup_retention_period: "BackupRetentionPeriod", preferred_backup_window: "PreferredBackupWindow", storage_encrypted: "StorageEncrypted", kms_key_id: "KmsKeyId", port: "Port" }, tags: true }, // ── Load balancing & API stages ── { tfType: "aws_lb_listener_rule", tier: 3, nativeType: "AWS::ElasticLoadBalancingV2::ListenerRule", ctor: "ListenerRule", fields: { listener_arn: "ListenerArn", priority: "Priority" } }, { tfType: "aws_api_gateway_stage", tier: 2, nativeType: "AWS::ApiGateway::Stage", ctor: "ApigwStage", identityAttr: "stage_name", fields: { rest_api_id: "RestApiId", stage_name: "StageName", deployment_id: "DeploymentId", description: "Description" }, tags: true }, { tfType: "aws_api_gateway_deployment", tier: 2, nativeType: "AWS::ApiGateway::Deployment", ctor: "ApigwDeployment", fields: { rest_api_id: "RestApiId", description: "Description" } }, { tfType: "aws_apigatewayv2_stage", tier: 2, nativeType: "AWS::ApiGatewayV2::Stage", ctor: "Apigwv2Stage", identityAttr: "name", fields: { api_id: "ApiId", name: "StageName", auto_deploy: "AutoDeploy", description: "Description" } }, // ── Identity & scaling ── { tfType: "aws_cognito_user_pool", tier: 3, nativeType: "AWS::Cognito::UserPool", ctor: "UserPool", identityAttr: "name", fields: { name: "UserPoolName", mfa_configuration: "MfaConfiguration", deletion_protection: "DeletionProtection" } }, { tfType: "aws_appautoscaling_target", tier: 2, nativeType: "AWS::ApplicationAutoScaling::ScalableTarget", ctor: "ScalableTarget", fields: { max_capacity: "MaxCapacity", min_capacity: "MinCapacity", resource_id: "ResourceId", scalable_dimension: "ScalableDimension", service_namespace: "ServiceNamespace", role_arn: "RoleARN" } }, // ── EFS periphery ── { tfType: "aws_efs_mount_target", tier: 1, nativeType: "AWS::EFS::MountTarget", ctor: "EFSMountTarget", fields: { file_system_id: "FileSystemId", subnet_id: "SubnetId", ip_address: "IpAddress", security_groups: "SecurityGroups" } }, { tfType: "aws_efs_access_point", tier: 2, nativeType: "AWS::EFS::AccessPoint", ctor: "EFSAccessPoint", fields: { file_system_id: "FileSystemId" } }, ]; const BY_TYPE = new Map(AWS_CARVE_TYPES.map((t) => [t.tfType, t])); export function awsCarveType(tfType: string): AwsCarveType | undefined { return BY_TYPE.get(tfType); } /** * How a folded sub-resource (see `FOLDS_INTO`) joins its parent's emitted * properties (#1637). Terraform splits configuration the CloudFormation shape * keeps inside the parent resource, so the fold is not just a carve-set * membership claim: the sub-resource's attributes have to land in the parent's * props, or the emitted resource silently loses what the Terraform declared. */ export interface AwsFoldMapper { /** Sub-resource attributes this mapper reads. Anything else stays unmapped. */ consumes: string[]; /** The parent CFN properties this sub-resource contributes. */ map: (attrs: Record) => Record; } /** Identity and parent-link attributes: never content, never reported unmapped. */ const FOLD_LINK_ATTRS = new Set(["id", "arn", "bucket"]); export const AWS_FOLD_MAPPERS: Record = { aws_s3_bucket_versioning: { consumes: ["versioning_configuration"], map: (attrs) => { const status = firstBlock(attrs.versioning_configuration)?.status; // CFN takes Enabled/Suspended only; TF's third state ("Disabled", write-once // buckets) has no CloudFormation spelling and stays in the comment. return status === "Enabled" || status === "Suspended" ? { VersioningConfiguration: { Status: status } } : {}; }, }, aws_s3_bucket_public_access_block: { consumes: ["block_public_acls", "block_public_policy", "ignore_public_acls", "restrict_public_buckets"], map: (attrs) => { const config: Record = {}; const flags: Array<[string, string]> = [ ["block_public_acls", "BlockPublicAcls"], ["block_public_policy", "BlockPublicPolicy"], ["ignore_public_acls", "IgnorePublicAcls"], ["restrict_public_buckets", "RestrictPublicBuckets"], ]; for (const [tfAttr, prop] of flags) { if (typeof attrs[tfAttr] === "boolean") config[prop] = attrs[tfAttr]; } return Object.keys(config).length ? { PublicAccessBlockConfiguration: config } : {}; }, }, aws_s3_bucket_server_side_encryption_configuration: { consumes: ["rule"], map: (attrs) => { const encryption = sseRulesToCfn(attrs.rule); return encryption ? { BucketEncryption: encryption } : {}; }, }, }; /** * Apply a folded sub-resource's state attributes to its parent's properties. * * Returns the props it contributes plus the attributes that stay genuinely * unmappable (which the emitted source preserves in its reference comment). A * mapper that produced nothing consumes nothing — the caller reports the whole * sub-resource rather than claiming a fold that did not happen. `null` means * this sub-resource type has no fold mapping at all. */ export function applyAwsFold( tfType: string, attrs: Record, ): { props: Record; unmapped: Record } | null { const mapper = AWS_FOLD_MAPPERS[tfType]; if (!mapper) return null; const props = mapper.map(attrs); const consumed = new Set(Object.keys(props).length ? mapper.consumes : []); return { props, unmapped: unmappedFoldAttrs(attrs, consumed) }; } /** A folded sub-resource's attributes minus what was consumed and its parent link. */ export function unmappedFoldAttrs( attrs: Record, consumed: ReadonlySet = new Set(), ): Record { const rest: Record = {}; for (const [key, value] of Object.entries(attrs)) { if (consumed.has(key) || FOLD_LINK_ATTRS.has(key)) continue; rest[key] = value; } return rest; } /** TF `tags` map → CloudFormation `Tags` list of {Key, Value}. */ function tagsToCfn(tags: unknown): Array<{ Key: string; Value: unknown }> | undefined { if (!tags || typeof tags !== "object" || Array.isArray(tags)) return undefined; const entries = Object.entries(tags as Record); return entries.length ? entries.map(([Key, Value]) => ({ Key, Value })) : undefined; } /** * Apply an entry's field mappings to a resource's Terraform attributes, yielding * CloudFormation properties and the set of attribute keys that were consumed * (so the caller can report the rest as unmapped). */ export function applyAwsMapper( entry: AwsCarveType, attrs: Record, ): { props: Record; mappedKeys: string[] } { const props: Record = {}; const mappedKeys: string[] = ["id", "arn"]; // identity/computed attrs, ignored not dropped for (const [tfAttr, spec] of Object.entries(entry.fields)) { const value = attrs[tfAttr]; if (value === undefined) continue; if (typeof spec === "string") { props[spec] = value; } else { // A transform that declines (undefined) has mapped nothing — the attribute // stays in the unmapped report rather than being claimed and dropped. const t = spec.transform(value); if (t === undefined) continue; props[spec.prop] = t; } mappedKeys.push(tfAttr); } if (entry.tags) { const tags = tagsToCfn(attrs.tags); if (tags) props.Tags = tags; mappedKeys.push("tags"); } return { props, mappedKeys }; }