NTC Core Network Migration (v1 → v2)
This guide covers the migration of terraform-aws-ntc-core-network from v1.x to v2.x.
Overview of Changes
v2.0.0 introduces major architectural improvements to simplify multi-region network configurations:
Key Changes
| Feature | v1.x | v2.x |
|---|---|---|
| Provider Architecture | AWS Provider v5 with multiple aliases | AWS Provider v6 with region attribute |
| Regional Configuration | Separate module per region + provider alias | Single module with transit_gateways array |
| Multi-Region Support | Multiple module calls + provider aliases | Single module with regional configuration arrays |
| Peering Management | Separate peering submodule per connection | Integrated peering_requests in transit gateway config |
| Custom Routes | Separate custom-routes submodule per region | Single custom-routes submodule with region attribute per route |
| Module Complexity | Multiple module calls per region | Two module calls for all regions |
| Resource Keys | Simple keys (e.g., [0], ["tgw-core-rtb-hub"]) | Composite keys (e.g., ["tgw-core-frankfurt"], ["tgw-core-frankfurt/tgw-core-rtb-hub"]) |
Before & After Comparison
- v1.x (Multiple Modules)
- v2.x (Single Module)
# Frankfurt region module
module "ntc_core_network_frankfurt" {
source = "github.com/...terraform-aws-ntc-core-network?ref=1.2.1"
customer_managed_prefix_lists = local.customer_managed_prefix_lists
transit_gateway = {
name = "tgw-core-frankfurt"
amazon_side_asn = 64512
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
ram_share_principals = [local.workloads_ou_id]
}
transit_gateway_flow_log_destinations = [
{
destination_type = "s3"
destination_arn = local.log_bucket_arn
}
]
providers = {
aws = aws.euc1
}
}
# Zurich region module
module "ntc_core_network_zurich" {
source = "github.com/...terraform-aws-ntc-core-network?ref=1.2.1"
customer_managed_prefix_lists = local.customer_managed_prefix_lists
transit_gateway = {
name = "tgw-core-zurich"
amazon_side_asn = 64513
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
ram_share_principals = [local.workloads_ou_id]
}
transit_gateway_flow_log_destinations = [
{
destination_type = "s3"
destination_arn = local.log_bucket_arn
}
]
providers = {
aws = aws.euc2
}
}
# Peering module (creator side)
module "ntc_core_network_zurich_peering" {
source = "github.com/...terraform-aws-ntc-core-network//modules/peering?ref=1.2.1"
create_transit_gateway_peering_association = true
transit_gateway_peering_association_with_route_table_id =
module.ntc_core_network_zurich.transit_gateway_route_table_ids["tgw-core-rtb-hub"]
transit_gateway_create_peerings = {
requester_transit_gateway_name = module.ntc_core_network_zurich.transit_gateway_name
requester_transit_gateway_id = module.ntc_core_network_zurich.transit_gateway_id
accepter_transit_gateways = [
module.ntc_core_network_frankfurt.transit_gateway_peering_info_for_creator
]
}
providers = {
aws = aws.euc2
}
}
# Peering module (accepter side)
module "ntc_core_network_frankfurt_peering" {
source = "github.com/...terraform-aws-ntc-core-network//modules/peering?ref=1.2.1"
create_transit_gateway_peering_association = true
transit_gateway_peering_association_with_route_table_id =
module.ntc_core_network_frankfurt.transit_gateway_route_table_ids["tgw-core-rtb-hub"]
transit_gateway_accept_peerings = [
module.ntc_core_network_zurich_peering.transit_gateway_peering_info_for_accepter["tgw-core-frankfurt"]
]
providers = {
aws = aws.euc1
}
}
# Custom routes module
module "ntc_core_network_frankfurt_custom_routes" {
source = "github.com/...terraform-aws-ntc-core-network//modules/custom-routes?ref=1.2.1"
transit_gateway_custom_routes = [
{
route_identifier = "spoke_prod_default_route_inspection_vpc"
route_table_id = module.ntc_core_network_frankfurt.transit_gateway_route_table_ids["tgw-core-rtb-spoke-prod"]
attachment_id = module.ntc_vpc_inspection.transit_gateway_vpc_attachement_id
destination_type = "cidr_block"
destination = "0.0.0.0/0"
}
]
providers = {
aws = aws.euc1
}
}
# Single unified module
module "ntc_core_network" {
source = "github.com/...terraform-aws-ntc-core-network?ref=2.0.0"
# Customer managed prefix lists with regions array
customer_managed_prefix_lists = [
{
regions = ["eu-central-1", "eu-central-2"]
name = "cidrs-aws-global"
entries = [{ cidr = "10.0.0.0/8" }]
ram_share_principals = [local.workloads_ou_id]
}
]
# All transit gateways in a single array
transit_gateways = [
{
region = "eu-central-1"
name = "tgw-core-frankfurt"
amazon_side_asn = 64512
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
ram_share_principals = [local.workloads_ou_id]
# Flow logs per transit gateway
flow_log_destinations = [
{
destination_type = "s3"
destination_arn = local.log_bucket_arn
}
]
# Peering requests integrated into TGW config
peering_requests = [
{
peer_tgw_name = "tgw-core-zurich"
peer_region = "eu-central-2"
association_route_table_name = "tgw-core-rtb-hub"
peer_association_route_table_name = "tgw-core-rtb-hub"
}
]
},
{
region = "eu-central-2"
name = "tgw-core-zurich"
amazon_side_asn = 64513
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
ram_share_principals = [local.workloads_ou_id]
flow_log_destinations = [
{
destination_type = "s3"
destination_arn = local.log_bucket_arn
}
]
}
]
# Direct Connect with region specified
direct_connect = {
dx_gateways = [
{ name = "dx-gateway", amazon_side_asn = 65500 }
]
transit_gateway_associations = [
{
region = "eu-central-1"
dx_gateway_name = "dx-gateway"
transit_gateway_name = "tgw-core-frankfurt"
transit_gateway_association_with_route_table_name = "tgw-core-rtb-onprem"
transit_gateway_propagation_to_route_table_names = ["tgw-core-rtb-hub"]
allowed_prefixes = ["10.100.0.0/16"]
}
]
dx_dedicated_connections = [
{
region = "eu-central-1"
name = "dx-con-frankfurt"
bandwidth_in_gpbs = 1
location_name = "Equinix FR5, Frankfurt, DEU"
# ... virtual_interfaces configuration
}
]
}
# VPN with region specified
virtual_private_network = {
customer_gateways = [
{
region = "eu-central-1"
name = "i7_frankfurt"
customer_side_asn = 65000
ip_address = "1.2.3.4"
}
]
vpn_connections = [
{
region = "eu-central-1"
name = "i7_frankfurt_vpn1"
customer_gateway_name = "i7_frankfurt"
transit_gateway_name = "tgw-core-frankfurt"
transit_gateway_association_with_route_table_name = "tgw-core-rtb-onprem"
transit_gateway_propagation_to_route_table_names = ["tgw-core-rtb-hub"]
}
]
}
}
# Custom routes module (optional, if used) - consolidated for all regions
module "ntc_core_network_custom_routes" {
source = "github.com/...terraform-aws-ntc-core-network//modules/custom-routes?ref=2.0.0"
transit_gateway_custom_routes = [
{
region = "eu-central-1"
route_identifier = "spoke_prod_default_route_inspection_vpc_frankfurt"
# Updated reference to new output structure
route_table_id = module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-frankfurt"]["tgw-core-rtb-spoke-prod"]
attachment_id = module.ntc_vpc_inspection_frankfurt.transit_gateway_vpc_attachement_id
destination_type = "cidr_block"
destination = "0.0.0.0/0"
},
{
region = "eu-central-2"
route_identifier = "spoke_prod_default_route_inspection_vpc_zurich"
route_table_id = module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-zurich"]["tgw-core-rtb-spoke-prod"]
attachment_id = module.ntc_vpc_inspection_zurich.transit_gateway_vpc_attachement_id
destination_type = "cidr_block"
destination = "0.0.0.0/0"
}
]
}
Breaking Changes
The following changes require a migration to avoid resource recreation:
- Module Structure: Regional modules consolidated into single module with arrays
- Resource Paths: All resources now use composite keys with region/TGW name prefixes
- Peering Management: Separate peering modules removed, integrated into transit gateway config
- Provider Architecture: Multi-provider aliases replaced with single provider and
regionattributes - Prefix Lists: Now require
regionsarray instead of being region-specific
Migration Steps
Step 1: Update Provider Configuration
Update terraform.required_providers to use AWS Provider v6:
- v1.x Provider
- v2.x Provider
terraform {
required_version = "~> 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-central-1"
alias = "euc1"
}
provider "aws" {
region = "eu-central-2"
alias = "euc2"
}
terraform {
required_version = "~> 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
# Single provider - regions specified in module configuration
provider "aws" {
region = "eu-central-1"
}
Step 2: Consolidate Module Configuration
Extract configurations from regional modules and consolidate into single module:
- v1.x Configuration
- v2.x Configuration
# Separate module per region
module "ntc_core_network_frankfurt" {
source = "github.com/...?ref=1.2.1"
customer_managed_prefix_lists = local.customer_managed_prefix_lists
transit_gateway = {
name = "tgw-core-frankfurt"
amazon_side_asn = 64512
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
}
providers = { aws = aws.euc1 }
}
module "ntc_core_network_zurich" {
source = "github.com/...?ref=1.2.1"
customer_managed_prefix_lists = local.customer_managed_prefix_lists
transit_gateway = {
name = "tgw-core-zurich"
amazon_side_asn = 64513
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
}
providers = { aws = aws.euc2 }
}
# Peering modules
module "ntc_core_network_zurich_peering" { ... }
module "ntc_core_network_frankfurt_peering" { ... }
# Custom routes modules (one per region)
module "ntc_core_network_frankfurt_custom_routes" { ... }
module "ntc_core_network_zurich_custom_routes" { ... }
# Single consolidated module
module "ntc_core_network" {
source = "github.com/...?ref=2.0.0"
# Prefix lists with regions array
customer_managed_prefix_lists = [
{
regions = ["eu-central-1", "eu-central-2"]
name = "cidrs-aws-global"
entries = local.cidr_entries
ram_share_principals = [local.workloads_ou_id]
}
]
# All transit gateways in array
transit_gateways = [
{
region = "eu-central-1"
name = "tgw-core-frankfurt"
amazon_side_asn = 64512
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
flow_log_destinations = [
{ destination_type = "s3", destination_arn = "..." }
]
# Peering integrated here
peering_requests = [
{
peer_tgw_name = "tgw-core-zurich"
peer_region = "eu-central-2"
association_route_table_name = "tgw-core-rtb-hub"
peer_association_route_table_name = "tgw-core-rtb-hub"
}
]
},
{
region = "eu-central-2"
name = "tgw-core-zurich"
amazon_side_asn = 64513
route_table_names = ["tgw-core-rtb-hub", "tgw-core-rtb-spoke-prod"]
flow_log_destinations = [
{ destination_type = "s3", destination_arn = "..." }
]
}
]
# Direct Connect with regions
direct_connect = {
dx_gateways = [...]
transit_gateway_associations = [
{ region = "eu-central-1", ... },
{ region = "eu-central-2", ... }
]
dx_dedicated_connections = [
{ region = "eu-central-1", ... }
]
}
# VPN with regions
virtual_private_network = {
customer_gateways = [
{ region = "eu-central-1", ... },
{ region = "eu-central-2", ... }
]
vpn_connections = [
{ region = "eu-central-1", transit_gateway_name = "tgw-core-frankfurt", ... },
{ region = "eu-central-2", transit_gateway_name = "tgw-core-zurich", ... }
]
}
}
# Custom routes module (if used) - consolidated for all regions
module "ntc_core_network_custom_routes" {
source = "github.com/...terraform-aws-ntc-core-network//modules/custom-routes?ref=2.0.0"
transit_gateway_custom_routes = [
{
region = "eu-central-1"
route_identifier = "spoke_prod_default_route_inspection_vpc_frankfurt"
# Updated output reference to use new composite key structure
route_table_id = module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-frankfurt"]["tgw-core-rtb-spoke-prod"]
attachment_id = module.ntc_vpc_inspection_frankfurt.transit_gateway_vpc_attachement_id
destination_type = "cidr_block"
destination = "0.0.0.0/0"
},
{
region = "eu-central-2"
route_identifier = "spoke_prod_default_route_inspection_vpc_zurich"
route_table_id = module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-zurich"]["tgw-core-rtb-spoke-prod"]
attachment_id = module.ntc_vpc_inspection_zurich.transit_gateway_vpc_attachement_id
destination_type = "cidr_block"
destination = "0.0.0.0/0"
}
]
}
Step 3: Add State Migration Blocks
Create a state_migrations.tf file to migrate resources without recreating them.
You'll need moved blocks for each region and each transit gateway. The examples below show Frankfurt and Zurich. Adjust the module names, region identifiers, and resource names to match your configuration.
# =====================================================================================================================
# STATE MIGRATIONS - v1.x to v2.x
# =====================================================================================================================
# These moved blocks ensure Terraform state is updated without recreating resources
# Run: terraform plan - should show only "moved" operations (no creates/destroys)
# After successful migration, this file can be deleted
# -------------------------------------------------------------------------------------------------------------------
# CORE NETWORK - FRANKFURT (eu-central-1)
# -------------------------------------------------------------------------------------------------------------------
# Transit Gateway
moved {
from = module.ntc_core_network_frankfurt.aws_ec2_transit_gateway.ntc_core[0]
to = module.ntc_core_network.aws_ec2_transit_gateway.ntc_core["tgw-core-frankfurt"]
}
# Transit Gateway Route Tables
moved {
from = module.ntc_core_network_frankfurt.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-rtb-hub"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-frankfurt/tgw-core-rtb-hub"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-rtb-spoke-prod"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-frankfurt/tgw-core-rtb-spoke-prod"]
}
# Add moved blocks for all other route tables: spoke-dev, spoke-int, onprem, etc.
# RAM Resource Sharing
moved {
from = module.ntc_core_network_frankfurt.aws_ram_resource_share.ntc_tgw_share[0]
to = module.ntc_core_network.aws_ram_resource_share.ntc_tgw_share["tgw-core-frankfurt"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_ram_resource_association.ntc_tgw_share[0]
to = module.ntc_core_network.aws_ram_resource_association.ntc_tgw_share["tgw-core-frankfurt"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_ram_principal_association.ntc_tgw_share["ou-xxxxx-yyyyyyyy"]
to = module.ntc_core_network.aws_ram_principal_association.ntc_tgw_share["tgw-core-frankfurt/ou-xxxxx-yyyyyyyy"]
}
# Flow Logs
moved {
from = module.ntc_core_network_frankfurt.module.flow_logs["s3"].aws_flow_log.ntc_flow_logs
to = module.ntc_core_network.module.flow_logs["tgw-core-frankfurt/s3"].aws_flow_log.ntc_flow_logs
}
# Direct Connect (if configured)
moved {
from = module.ntc_core_network_frankfurt.aws_dx_gateway.ntc_dx["dx-gateway"]
to = module.ntc_core_network.aws_dx_gateway.ntc_dx["dx-gateway"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_dx_lag.ntc_dx["dx-con-frankfurt"]
to = module.ntc_core_network.aws_dx_lag.ntc_dx["eu-central-1/dx-con-frankfurt"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_dx_connection.ntc_dx["dx-con-frankfurt/1"]
to = module.ntc_core_network.aws_dx_connection.ntc_dx["eu-central-1/dx-con-frankfurt/1"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_dx_gateway_association.ntc_dx["tgw-core-frankfurt/dx-gateway"]
to = module.ntc_core_network.aws_dx_gateway_association.ntc_dx["eu-central-1/tgw-core-frankfurt/dx-gateway"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_ec2_transit_gateway_route_table_association.ntc_dx["tgw-core-frankfurt/dx-gateway"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table_association.ntc_dx["eu-central-1/tgw-core-frankfurt/dx-gateway"]
}
# VPN (if configured)
moved {
from = module.ntc_core_network_frankfurt.aws_customer_gateway.ntc_vpn["i7_frankfurt"]
to = module.ntc_core_network.aws_customer_gateway.ntc_vpn["eu-central-1/i7_frankfurt"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_vpn_connection.ntc_vpn["i7_frankfurt_vpn1"]
to = module.ntc_core_network.aws_vpn_connection.ntc_vpn["eu-central-1/i7_frankfurt_vpn1"]
}
moved {
from = module.ntc_core_network_frankfurt.aws_ec2_transit_gateway_route_table_propagation.ntc_vpn["i7_frankfurt_vpn1/tgw-core-rtb-hub"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table_propagation.ntc_vpn["eu-central-1/i7_frankfurt_vpn1/tgw-core-rtb-hub"]
}
# Add moved blocks for all VPN route table propagations
# -------------------------------------------------------------------------------------------------------------------
# CORE NETWORK - ZURICH (eu-central-2)
# -------------------------------------------------------------------------------------------------------------------
# Transit Gateway
moved {
from = module.ntc_core_network_zurich.aws_ec2_transit_gateway.ntc_core[0]
to = module.ntc_core_network.aws_ec2_transit_gateway.ntc_core["tgw-core-zurich"]
}
# Transit Gateway Route Tables
moved {
from = module.ntc_core_network_zurich.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-rtb-hub"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-zurich/tgw-core-rtb-hub"]
}
moved {
from = module.ntc_core_network_zurich.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-rtb-spoke-prod"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table.ntc_core["tgw-core-zurich/tgw-core-rtb-spoke-prod"]
}
# Add moved blocks for all other route tables
# RAM Resource Sharing
moved {
from = module.ntc_core_network_zurich.aws_ram_resource_share.ntc_tgw_share[0]
to = module.ntc_core_network.aws_ram_resource_share.ntc_tgw_share["tgw-core-zurich"]
}
moved {
from = module.ntc_core_network_zurich.aws_ram_resource_association.ntc_tgw_share[0]
to = module.ntc_core_network.aws_ram_resource_association.ntc_tgw_share["tgw-core-zurich"]
}
moved {
from = module.ntc_core_network_zurich.aws_ram_principal_association.ntc_tgw_share["ou-xxxxx-yyyyyyyy"]
to = module.ntc_core_network.aws_ram_principal_association.ntc_tgw_share["tgw-core-zurich/ou-xxxxx-yyyyyyyy"]
}
# Flow Logs
moved {
from = module.ntc_core_network_zurich.module.flow_logs["s3"].aws_flow_log.ntc_flow_logs
to = module.ntc_core_network.module.flow_logs["tgw-core-zurich/s3"].aws_flow_log.ntc_flow_logs
}
# -------------------------------------------------------------------------------------------------------------------
# CORE NETWORK - PEERING (FRANKFURT <-> ZURICH)
# -------------------------------------------------------------------------------------------------------------------
# Peering Attachment (creator side)
moved {
from = module.ntc_core_network_zurich_peering.aws_ec2_transit_gateway_peering_attachment.ntc_peering["tgw-core-frankfurt"]
to = module.ntc_core_network.aws_ec2_transit_gateway_peering_attachment.ntc_peering["tgw-core-frankfurt:tgw-core-zurich"]
}
moved {
from = module.ntc_core_network_zurich_peering.aws_ec2_transit_gateway_route_table_association.ntc_peering_creator["tgw-core-frankfurt"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table_association.ntc_peering_creator["tgw-core-frankfurt:tgw-core-zurich"]
}
# Peering Attachment (accepter side)
moved {
from = module.ntc_core_network_frankfurt_peering.aws_ec2_transit_gateway_peering_attachment_accepter.ntc_peering["tgw-core-zurich"]
to = module.ntc_core_network.aws_ec2_transit_gateway_peering_attachment_accepter.ntc_peering["tgw-core-frankfurt:tgw-core-zurich"]
}
moved {
from = module.ntc_core_network_frankfurt_peering.aws_ec2_transit_gateway_route_table_association.ntc_peering_acceptor["tgw-core-zurich"]
to = module.ntc_core_network.aws_ec2_transit_gateway_route_table_association.ntc_peering_acceptor["tgw-core-frankfurt:tgw-core-zurich"]
}
Step 4: Remove Old Module Definitions
After consolidating the configuration, remove the old module calls:
# DELETE THESE:
# module "ntc_core_network_frankfurt" { ... }
# module "ntc_core_network_zurich" { ... }
# module "ntc_core_network_frankfurt_peering" { ... }
# module "ntc_core_network_zurich_peering" { ... }
# CONSOLIDATE CUSTOM ROUTES (if used):
# Merge multiple regional custom routes modules into a single module call
# module "ntc_core_network_frankfurt_custom_routes" { ... } <- DELETE
# module "ntc_core_network_zurich_custom_routes" { ... } <- DELETE
# Create single consolidated module "ntc_core_network_custom_routes" with:
# - Add 'region' attribute to each route
# - Update output references to use new composite keys
# - Example: module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-frankfurt"]["rtb-name"]
Step 5: Update Output References
Update any references to old module outputs:
- v1.x Output References
- v2.x Output References
# Old references
transit_gateway_id = module.ntc_core_network_frankfurt.transit_gateway_id
route_table_id = module.ntc_core_network_frankfurt.transit_gateway_route_table_ids["tgw-core-rtb-hub"]
dx_gateway_id = module.ntc_core_network_frankfurt.dx_gateway_ids_by_name["dx-gateway"]
# New references
transit_gateway_id = module.ntc_core_network.transit_gateway_ids_by_name["tgw-core-frankfurt"]
route_table_id = module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-frankfurt"]["tgw-core-rtb-hub"]
dx_gateway_id = module.ntc_core_network.dx_gateway_ids_by_name["dx-gateway"]
Step 6: Initialize and Plan
# Upgrade to AWS Provider v6
terraform init -upgrade
# Review the plan
terraform plan
Expected plan output:
- ✅ Many
movedoperations (state migrations) - ✅ Some in-place updates (configuration changes)
- ✅ Possible additions for prefix lists in new regions
- ✅ In-place updates for custom routes module output references (if used)
- ❌ NO deletions or recreations of TGWs, route tables, flow logs, DX, VPN, RAM shares, or custom routes
If you see unexpected deletions or recreations, do NOT apply! Review your configuration and state migration blocks carefully.
Step 7: Apply Migration
terraform apply
Monitor the output. The migration should complete with only moved operations and minor updates.
Step 8: Clean Up
After successful migration:
# Remove the state migration file (optional, but recommended after verification)
rm state_migrations.tf
# Remove old provider aliases if no longer needed
# (Only remove if other modules don't use them)
# Delete old .tf.old backup files
rm ntc_core_network_frankfurt.tf.old ntc_core_network_zurich.tf.old
# Commit your changes
git add .
git commit -m "Migrate ntc-core-network from v1.x to v2.x"
Configuration Changes Summary
Parameter Structure Changes
| v1.x Parameter | v2.x Parameter | Notes |
|---|---|---|
transit_gateway = { } | transit_gateways = [ ] | Now an array of objects with region parameter |
transit_gateway_flow_log_destinations | flow_log_destinations (in each TGW) | Now nested within each transit gateway config |
| (separate peering modules) | peering_requests = [ ] (in each TGW) | Peering integrated into TGW config |
transit_gateway_custom_routes (per region module) | transit_gateway_custom_routes (with region per route) | Consolidated with region attribute, updated output references |
customer_managed_prefix_lists (per module) | customer_managed_prefix_lists (with regions array) | Now specify target regions explicitly |
direct_connect = { } (per module) | direct_connect = { } (with region in each item) | Consolidated with region parameters |
virtual_private_network = { } (per module) | virtual_private_network = { } (with region in each item) | Consolidated with region parameters |
New Features in v2.x
- Multi-Region in Single Module: Define all regions in one module call
- Integrated Peering: Peering requests configured directly in transit gateway definitions
- Explicit Region Parameters: All resources specify their target region
- Composite Resource Keys: Better organization with hierarchical keys
- Cleaner Output Structure: Nested maps for regional resources
Troubleshooting
Issue: Resources Being Recreated
Symptom: terraform plan shows deletions and recreations of transit gateways, route tables, or other core resources
Solution:
- Check your
movedblocks match your v1.x module names exactly - Use
terraform state list | grep ntc_core_networkto see current resource addresses - Verify composite keys format:
["tgw-name/resource-name"]for route tables,["region/resource-name"]for DX/VPN - Ensure you've created
movedblocks for all regions and all resources
Issue: Provider Configuration Errors
Symptom: "provider configuration not present" errors or "No configuration found for provider"
Solution:
- Ensure AWS Provider v6 is installed (
terraform init -upgrade) - Verify your
required_providersblock hasversion = "~> 6.0" - Remove old provider aliases from v1.x (unless other modules still need them)
- Confirm the module no longer has
providers = { aws = aws.alias }blocks
Issue: Peering Connection Shows as To Be Destroyed
Symptom: Peering attachments show as destroy/create instead of moved
Solution:
- Check the peering key format:
["requester-tgw:accepter-tgw"](e.g.,["tgw-core-frankfurt:tgw-core-zurich"]) - Verify which module was the "creator" (requester) vs "accepter" in v1.x
- The creator module's peering attachment should map to
aws_ec2_transit_gateway_peering_attachment - The accepter module's peering attachment should map to
aws_ec2_transit_gateway_peering_attachment_accepter
Issue: Custom Routes Module Configuration
Symptom: Custom routes module fails with "Invalid index" or similar errors, or multiple regional custom routes modules exist
Solution: The custom-routes submodule is still available in v2, but has two key changes:
- Add
regionattribute: Each route now requires aregionparameter (e.g.,region = "eu-central-1") - Update module output references: Change from regional to consolidated module outputs
- Old:
module.ntc_core_network_frankfurt.transit_gateway_route_table_ids["tgw-core-rtb-spoke-prod"] - New:
module.ntc_core_network.transit_gateway_route_table_ids["tgw-core-frankfurt"]["tgw-core-rtb-spoke-prod"]
- Old:
- Consolidate regional modules: Merge multiple regional custom routes modules (e.g.,
ntc_core_network_frankfurt_custom_routes,ntc_core_network_zurich_custom_routes) into a single module call - No state migration needed for custom routes - only configuration updates
Issue: Direct Connect/VPN Resources Not Found
Symptom: Error about DX or VPN resources not being found after migration
Solution:
- Verify the region prefix in resource keys:
["eu-central-1/resource-name"] - Check that connection names match between v1.x and v2.x
- For DX gateway associations, ensure the key format is:
["region/tgw-name/dx-gateway-name"] - Review the new configuration structure with explicit
regionparameters
Issue: Prefix Lists Recreated Across Regions
Symptom: Prefix lists being destroyed in some regions and created in others
Solution:
- Prefix lists now use a
regionsarray - ensure all target regions are specified - The key format changed from region-specific to name-based with regional deployment
- If you had different prefix lists per region in v1.x, you may need multiple prefix list definitions in v2.x with different
regionsarrays
Issue: Flow Logs Resource Path Changed
Symptom: Flow logs show as moved but to incorrect path
Solution: The flow logs path includes the TGW name and destination type now:
- v1.x:
module.ntc_core_network_frankfurt.module.flow_logs["s3"] - v2.x:
module.ntc_core_network.module.flow_logs["tgw-core-frankfurt/s3"]
Ensure your moved blocks use the correct composite key format.
Need Help?
- 📚 Review the main migration guide
- 🐛 Check common issues
- 📖 See the module README for v2.x documentation