Skip to main content

NTC Organizations Migration (v1 → v2)

This guide covers the migration of terraform-aws-ntc-organizations from v1.x to v2.x.

Overview of Changes

v2.0.0 introduces significant architectural improvements to simplify multi-region configurations:

Key Changes

Featurev1.xv2.x
Provider ArchitectureAWS Provider v5AWS Provider v6 with region attribute
Admin DelegationsSeparate module calls per regionIntegrated into root module
Service Quota TemplatesSeparate moduleIntegrated into root module
Multi-Region SupportMultiple provider aliases + module callsSingle module with regions configuration
Module Complexity3+ module calls neededSingle module call

Before & After Comparison

Previous approach required 3 separate module configurations:

# 1. Main organizations module
module "ntc_organizations" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations?ref=1.6.0"
# Configuration...
}

# 2. Admin delegations for eu-central-1
module "ntc_delegated_admins_euc1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.6.0"
delegated_administrators = [...]
providers = { aws = aws.euc1 }
}

# 3. Admin delegations for eu-central-2
module "ntc_delegated_admins_euc2" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.6.0"
delegated_administrators = [...]
providers = { aws = aws.euc2 }
}

# 4. Admin delegations for us-east-1
module "ntc_delegated_admins_use1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.6.0"
delegated_administrators = [...]
providers = { aws = aws.use1 }
}

Migration Steps

Step 1: Upgrade AWS Provider to v6 (Keep NTC at v1.x)

Why Upgrade the Provider First?

Upgrade the AWS provider to v6 before upgrading NTC modules. This writes the new region attribute into the Terraform state for all existing resources. Without this intermediate step, resources managed through non-default (aliased) providers may be flagged for recreation during the NTC v2 upgrade.

Update only the provider version constraint. Keep all existing provider aliases and NTC module versions unchanged.

main.tf
terraform {
required_version = ">= 1.3.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # ← Updated from ~> 5.0
}
}
}

# Keep all existing provider aliases unchanged
provider "aws" {
region = "eu-central-1"
}

provider "aws" {
alias = "euc1"
region = "eu-central-1"
}

provider "aws" {
alias = "euc2"
region = "eu-central-2"
}

provider "aws" {
alias = "use1"
region = "us-east-1"
}

Initialize and apply:

terraform init -upgrade
terraform plan
# Expected: in-place updates adding the region attribute — NO recreations
terraform apply

Step 2: Simplify Provider Configuration

After the provider upgrade is applied, simplify the provider configuration for NTC v2:

main.tf
terraform {
required_version = ">= 1.5.7"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}

provider "aws" {
region = "eu-central-1"
}

# Regional providers still needed for some exceptions (where region attribute isn't supported)
provider "aws" {
alias = "use1"
region = "us-east-1"
}

Step 3: Consolidate Module Configuration

2a. Extract Admin Delegations Configuration

If you have separate admin delegation modules, consolidate them:

# In ntc_organizations_admin_delegations.tf
locals {
global_delegated_administrators = [
{
service_principal = "config.amazonaws.com"
admin_account_id = local.security_account_id
},
{
service_principal = "access-analyzer.amazonaws.com"
admin_account_id = local.security_account_id
},
{
service_principal = "backup.amazonaws.com"
admin_account_id = local.backup_account_id
},
]

regional_delegated_administrators = [
{
service_principal = "securityhub.amazonaws.com"
admin_account_id = local.security_account_id
},
{
service_principal = "guardduty.amazonaws.com"
admin_account_id = local.security_account_id
},
{
service_principal = "inspector2.amazonaws.com"
admin_account_id = local.security_account_id
}
]
}

module "ntc_delegated_admins_euc1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.5.0"
delegated_administrators = concat(local.global_delegated_administrators, local.regional_delegated_administrators)
providers = { aws = aws.euc1 }
}

module "ntc_delegated_admins_euc2" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.5.0"
delegated_administrators = local.regional_delegated_administrators
providers = { aws = aws.euc2 }
}

module "ntc_delegated_admins_use1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations//modules/regional-admin-delegations?ref=1.5.0"
delegated_administrators = local.regional_delegated_administrators
providers = { aws = aws.use1 }
}

2b. Update Service Quota Templates (if used)

If you were using service quota templates, they're now part of the main module:

module "ntc_organizations" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-organizations?ref=2.0.0"

region = "eu-central-1"

# ... other configuration ...

# Service quota templates integrated into main module
service_quota_templates = [
{
regions = ["us-east-1"]
quota_name = "Managed policies per role"
service_code = "iam"
new_value = 20 # Default: 10, Increased for complex permission models
}
]
}

Step 4: Add State Migration Blocks

Create a state_migrations.tf file to migrate resources without recreating them:

state_migrations.tf
# ---------------------------------------------------------------------------------------------------------------------
# ¦ 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

# -------------------------------------------------------------------------------------------------------------------
# Global Admin Delegations
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_delegated_admins_euc1.aws_organizations_delegated_administrator.ntc_delegated_admin["access-analyzer.amazonaws.com"]
to = module.ntc_organizations.module.admin_delegations.aws_organizations_delegated_administrator.ntc_delegated_admin["access-analyzer.amazonaws.com"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_organizations_delegated_administrator.ntc_delegated_admin["backup.amazonaws.com"]
to = module.ntc_organizations.module.admin_delegations.aws_organizations_delegated_administrator.ntc_delegated_admin["backup.amazonaws.com"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_organizations_delegated_administrator.ntc_delegated_admin["config.amazonaws.com"]
to = module.ntc_organizations.module.admin_delegations.aws_organizations_delegated_administrator.ntc_delegated_admin["config.amazonaws.com"]
}

# -------------------------------------------------------------------------------------------------------------------
# Regional Admin Delegations - eu-central-1
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_delegated_admins_euc1.aws_guardduty_detector.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_detector.ntc_guardduty["eu-central-1"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_guardduty_organization_admin_account.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_organization_admin_account.ntc_guardduty["eu-central-1"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_inspector2_delegated_admin_account.ntc_inspector[0]
to = module.ntc_organizations.module.admin_delegations.aws_inspector2_delegated_admin_account.ntc_inspector["eu-central-1"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_securityhub_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_account.ntc_security_hub["eu-central-1"]
}

moved {
from = module.ntc_delegated_admins_euc1.aws_securityhub_organization_admin_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_organization_admin_account.ntc_security_hub["eu-central-1"]
}

# -------------------------------------------------------------------------------------------------------------------
# Regional Admin Delegations - eu-central-2
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_delegated_admins_euc2.aws_guardduty_detector.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_detector.ntc_guardduty["eu-central-2"]
}

moved {
from = module.ntc_delegated_admins_euc2.aws_guardduty_organization_admin_account.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_organization_admin_account.ntc_guardduty["eu-central-2"]
}

moved {
from = module.ntc_delegated_admins_euc2.aws_inspector2_delegated_admin_account.ntc_inspector[0]
to = module.ntc_organizations.module.admin_delegations.aws_inspector2_delegated_admin_account.ntc_inspector["eu-central-2"]
}

moved {
from = module.ntc_delegated_admins_euc2.aws_securityhub_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_account.ntc_security_hub["eu-central-2"]
}

moved {
from = module.ntc_delegated_admins_euc2.aws_securityhub_organization_admin_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_organization_admin_account.ntc_security_hub["eu-central-2"]
}

# -------------------------------------------------------------------------------------------------------------------
# Regional Admin Delegations - us-east-1
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_delegated_admins_use1.aws_guardduty_detector.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_detector.ntc_guardduty["us-east-1"]
}

moved {
from = module.ntc_delegated_admins_use1.aws_guardduty_organization_admin_account.ntc_guardduty[0]
to = module.ntc_organizations.module.admin_delegations.aws_guardduty_organization_admin_account.ntc_guardduty["us-east-1"]
}

moved {
from = module.ntc_delegated_admins_use1.aws_inspector2_delegated_admin_account.ntc_inspector[0]
to = module.ntc_organizations.module.admin_delegations.aws_inspector2_delegated_admin_account.ntc_inspector["us-east-1"]
}

moved {
from = module.ntc_delegated_admins_use1.aws_securityhub_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_account.ntc_security_hub["us-east-1"]
}

moved {
from = module.ntc_delegated_admins_use1.aws_securityhub_organization_admin_account.ntc_security_hub[0]
to = module.ntc_organizations.module.admin_delegations.aws_securityhub_organization_admin_account.ntc_security_hub["us-east-1"]
}
State Migration Notes
  • These moved blocks tell Terraform to update its state without recreating resources
  • After successful migration (successful apply), you can delete state_migrations.tf
  • If you have different regions, adjust the region names accordingly

Step 5: Remove Old Definitions

After consolidating the configuration, remove the Admin Delegation and Service Quota Template module calls.

Step 6: Initialize and Plan

# Upgrade to AWS Provider v6
terraform init -upgrade

# Review the plan
terraform plan

Expected plan output:

  • ✅ Many moved operations (state migrations)
  • ✅ Some in-place updates (configuration changes)
  • NO resource deletions or recreations

If you see 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 without errors.

Step 8: Clean Up

After successful migration:

# Remove the state migration file (optional, but recommended)
rm state_migrations.tf

# Commit your changes
git add .
git commit -m "Migrate ntc-organizations from v1.x to v2.x"

Complete Example

See a complete before/after example in the GitHub repository:

Troubleshooting

Issue: Resources Being Recreated

Symptom: terraform plan shows deletions and recreations

Solution:

  1. Check your moved blocks match your existing state exactly
  2. Verify resource names haven't changed
  3. Use terraform state list to see current resource addresses

Issue: Provider Configuration Errors

Symptom: "provider configuration not present" errors

Solution:

  1. Ensure AWS Provider v6 is installed (terraform init -upgrade)
  2. Verify your required_providers block has version = "~> 6.0"

Issue: Region Not Supported

Symptom: "region not available" errors in ESC

Solution:

  1. For ESC, ensure you're using supported regions
  2. Check region codes match your partition (e.g., eu-central-1 vs eusc-de-east-1)

Need Help?