terraform-provider-upgrade
Terraform Provider Upgrade Skill
This skill provides a comprehensive workflow for safely upgrading Terraform providers with automatic resource migration, breaking change detection, and proper state management.
When to Use This Skill
- Upgrading provider versions (especially major versions)
- Handling removed or deprecated resources
- Migrating between resource types (e.g.,
azurerm_sql_*→azurerm_mssql_*) - Detecting and resolving breaking changes
- Replacing deprecated provider properties
- Managing Terraform state during upgrades
Determining Breaking vs Non-Breaking Changes
Version Change Analysis
Use MCP tools to understand the scope:
# Get current and latest versions
get_latest_provider_version(namespace="hashicorp", name="azurerm")
# Current: 3.117.0, Latest: 4.51.0 → Major version change (likely breaking)
# Current: 3.117.0, Latest: 3.118.0 → Minor version change (likely non-breaking)
# Current: 3.117.0, Latest: 3.117.1 → Patch version (non-breaking)
# Get upgrade guide to confirm
resolveProviderDocID(...serviceSlug="4.0-upgrade-guide", providerDataType="guides"...)
getProviderDocs(providerDocID="<id>")
Non-Breaking Changes (Auto-Apply)
Characteristics:
- Minor or patch version updates (e.g.,
3.117.0→3.118.0or3.117.1) - No removed resources
- No required argument changes
- Backward-compatible deprecations with drop-in replacements
- New optional arguments or bug fixes
Action:
- Update version constraints in
versions.tf - Apply backward-compatible deprecation replacements if any
- Commit changes without detailed documentation
- Brief commit message:
chore: upgrade azurerm provider to v3.118.0
Breaking Changes (Apply + Document)
Characteristics:
- Major version updates (e.g.,
3.x→4.x) - Removed resources requiring code migration
- Required argument changes (renames, type changes)
- Default value changes affecting behavior
- Authentication or provider configuration changes
Action:
- Apply ALL code changes (resource migrations, moved blocks, argument updates)
- Create comprehensive documentation at repository root:
TERRAFORM_UPGRADE_BREAKING_CHANGES.md - Document what was done (not what needs to be done)
- Detailed commit message referencing documentation
Using MCP Tools to Identify Breaking Changes
Key indicators from upgrade guide:
- Sections titled "Removed Resources", "Breaking Changes", "Behavior Changes"
- Resources listed as "removed" or "superseded by"
- Arguments marked as "renamed", "removed", or "type changed"
- Default value changes that affect existing behavior
Core Workflow
1. Inventory Current State
Objective: Find all provider references and document current versions
# Search for all Terraform files
find . -name "*.tf"
# Search for provider version constraints
grep -r "required_providers" --include="*.tf"
grep -r "version.*=" --include="versions.tf"
Document:
- Current provider versions across all modules/environments
- Location of all provider version constraints
- Environments using the provider
2. Check Latest Versions
Use MCP Tool:
get_latest_provider_version(namespace="hashicorp", name="azurerm")
Compare:
- Current version:
3.117.1 - Latest version:
4.51.0 - Gap: Major version upgrade (3.x → 4.x)
3. Research Breaking Changes
Step 1: Find Upgrade Guide
resolveProviderDocID(
providerNamespace="hashicorp",
providerName="azurerm",
serviceSlug="4.0-upgrade-guide",
providerDataType="guides",
providerVersion="latest"
)
Step 2: Get Documentation
getProviderDocs(providerDocID="<id-from-previous-call>")
Analyze upgrade guide for:
- ✅ Removed resources (resources that no longer exist)
- ✅ Deprecated resources (warnings only)
- ✅ Breaking argument changes (required fields, renamed fields, type changes)
- ✅ New provider features (changes to features {} block)
- ✅ Authentication changes
4. Scan Codebase for Removed Resources
Critical Step: Search for usage of removed resources
# Example: Search for removed SQL resources in Azure v4.0
grep -r "azurerm_sql_server" --include="*.tf"
grep -r "azurerm_sql_firewall_rule" --include="*.tf"
grep -r "azurerm_sql_virtual_network_rule" --include="*.tf"
Document findings:
- Which files use removed resources
- Which environments are affected
- Dependencies between resources
5. Validate Argument Changes
For each removed resource found:
Step 1: Get old resource documentation
resolveProviderDocID(
providerNamespace="hashicorp",
providerName="azurerm",
serviceSlug="sql_server",
providerDataType="resources",
providerVersion="3.117.1"
)
getProviderDocs(providerDocID="<id>")
Step 2: Get new resource documentation
resolveProviderDocID(
providerNamespace="hashicorp",
providerName="azurerm",
serviceSlug="mssql_server",
providerDataType="resources",
providerVersion="latest"
)
getProviderDocs(providerDocID="<id>")
Step 3: Compare schemas
- Required arguments (new required fields?)
- Renamed arguments (e.g.,
administrator_login→admin_login) - Type changes (name → ID references)
- Removed arguments
- New arguments with defaults
Step 4: Check default values
- Document if new resource has different defaults than old resource
- Example: New resource might enable features by default that old resource didn't
6. Apply Code Migrations
Update resource types:
# Before
resource "azurerm_sql_server" "sql_server" {
name = "example-sqlserver"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
version = "12.0"
administrator_login = "sqladmin"
administrator_login_password = var.sql_password
}
# After
resource "azurerm_mssql_server" "sql_server" {
name = "example-sqlserver"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
version = "12.0"
administrator_login = "sqladmin"
administrator_login_password = var.sql_password
}
Add moved block for state migration:
moved {
from = azurerm_sql_server.sql_server
to = azurerm_mssql_server.sql_server
}
Update argument changes:
# Firewall rule arguments changed from name-based to ID-based
# Before (v3.x)
resource "azurerm_sql_firewall_rule" "allow_azure" {
name = "allow-azure-services"
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_sql_server.sql_server.name
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}
# After (v4.x)
resource "azurerm_mssql_firewall_rule" "allow_azure" {
name = "allow-azure-services"
server_id = azurerm_mssql_server.sql_server.id # Changed from name to ID
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}
moved {
from = azurerm_sql_firewall_rule.allow_azure
to = azurerm_mssql_firewall_rule.allow_azure
}
Update dependent resources:
# Search for resources that reference the migrated resource
grep -r "azurerm_sql_server.sql_server" --include="*.tf"
Update references to use correct attributes (e.g., .id instead of .name where needed).
Replace deprecated provider properties:
# Before
provider "azurerm" {
features {}
skip_provider_registration = true # Deprecated
}
# After
provider "azurerm" {
features {}
resource_provider_registrations = "none" # Modern equivalent
}
Update version constraints:
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.51.0" # Updated from 3.117.1
}
}
}
7. Document Changes
Only create documentation for breaking changes.
Create TERRAFORM_UPGRADE_BREAKING_CHANGES.md at repository root (not in .github/ or subdirectories):
File Location:
your-terraform-repo/
├── TERRAFORM_UPGRADE_BREAKING_CHANGES.md ← Place here
├── infra/
├── .github/
└── README.md
# Terraform Provider Upgrade: AzureRM v3.117.1 → v4.51.0
**Date:** 2026-01-27
## Summary
Upgraded HashiCorp AzureRM provider from v3.117.1 to v4.51.0. This major version upgrade included automatic migration of removed SQL resources to their modern MSSQL equivalents.
## What Changed
- Updated `required_providers` version constraint to `4.51.0`
- Migrated removed resources: `azurerm_sql_*` → `azurerm_mssql_*`
- Replaced deprecated `skip_provider_registration` with `resource_provider_registrations`
- Updated argument references from name-based to ID-based
## Breaking Changes Handled
### ✅ Removed Resources Migrated
**1. azurerm_sql_server → azurerm_mssql_server**
- **Files Modified:** `infra/modules/database/main.tf`
- **Changes Applied:**
- Updated resource type
- Added `moved` block for automatic state migration
- All arguments remain compatible
- **Argument Mappings:** No changes required (schema compatible)
- **Default Values:** No new default value changes
- **Documentation:** [azurerm_mssql_server](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mssql_server)
**2. azurerm_sql_firewall_rule → azurerm_mssql_firewall_rule**
- **Files Modified:** `infra/modules/database/main.tf`
- **Changes Applied:**
- Updated resource type
- Changed `server_name` argument to `server_id`
- Updated reference from `.name` to `.id`
- Added `moved` block for automatic state migration
- **Argument Mappings:**
- `resource_group_name` (removed) + `server_name` → `server_id`
- Now uses: `azurerm_mssql_server.sql_server.id`
- **Default Values:** No new default value changes
- **Documentation:** [azurerm_mssql_firewall_rule](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mssql_firewall_rule)
**State Migration:** Terraform will automatically migrate state using `moved` blocks on next plan/apply.
## Notes
- All changes are backward compatible with existing state
- `moved` blocks enable automatic state migration
- No manual `terraform state mv` commands required
- Provider block retained with updated `resource_provider_registrations` argument
## Next Steps
1. **Commit these changes** to a feature branch
2. **Run your Terraform workflow** via CI/CD pipeline to validate
3. **Review plan output** to confirm state migrations
4. **Verify no unexpected changes** before merging
## References
- [AzureRM Provider 4.0 Upgrade Guide](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/4.0-upgrade-guide)
- [AzureRM Provider v4.51.0 Release Notes](https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v4.51.0)
- [Terraform Moved Blocks](https://developer.hashicorp.com/terraform/language/modules/develop/refactoring)
- [Resource Provider Registrations](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/4.0-upgrade-guide#resource-provider-registrations)
Best Practices
✅ DO:
- Distinguish upgrade types - Check if major (breaking) vs minor/patch (non-breaking)
- Auto-apply non-breaking - Minor/patch updates don't need detailed documentation
- Document breaking only - Create TERRAFORM_UPGRADE_BREAKING_CHANGES.md for major versions
- Place docs at root - Repository root for visibility, not in subdirectories
- Use moved blocks for resource type changes (automatic state migration)
- Validate arguments against official docs for both old and new resources
- Check default values - document if new resource has different defaults
- Update dependent resources that reference migrated resources
- Document what was done - show code changes applied
- Include resource documentation links for transparency
- Use pipeline validation - let CI/CD handle terraform commands
❌ DON'T:
- Document non-breaking changes - Minor/patch versions don't need TERRAFORM_UPGRADE_BREAKING_CHANGES.md
- Provide manual migration steps - code should handle everything
- Assume arguments stayed the same - always validate schemas
- Forget dependent resources - search and update references
- Miss attribute changes - update
.nameto.idwhere needed - Remove provider blocks - only update deprecated arguments
- Suggest terraform commands - users validate via pipeline
Workflow Examples
Example 1: Non-Breaking Upgrade (Minor Version)
Scenario: Upgrading AzureRM from 3.117.0 to 3.118.0
# 1. Check version
get_latest_provider_version(namespace="hashicorp", name="azurerm")
# Result: 3.118.0 (minor version bump)
# 2. Check for breaking changes
resolveProviderDocID(...serviceSlug="3.118.0", providerDataType="guides"...)
getProviderDocs(providerDocID="<id>")
# Review: No removed resources, no breaking changes
# 3. Update version
# versions.tf: version = "3.118.0"
# 4. Commit
# Message: "chore: upgrade azurerm provider to v3.118.0"
# NO TERRAFORM_UPGRADE_BREAKING_CHANGES.md needed
Example 2: Breaking Upgrade (Major Version)
Scenario: Upgrading AzureRM from 3.117.0 to 4.51.0
# 1. Check version
get_latest_provider_version(namespace="hashicorp", name="azurerm")
# Result: 4.51.0 (major version bump)
# 2. Get upgrade guide
resolveProviderDocID(...serviceSlug="4.0-upgrade-guide"...)
getProviderDocs(providerDocID="<id>")
# Review: azurerm_sql_* resources removed
# 3. Scan codebase
grep -r "azurerm_sql_server" --include="*.tf"
# 4. Migrate resources (fetch old + new docs)
# 5. Add moved blocks
# 6. Update arguments
# 7. Create TERRAFORM_UPGRADE_BREAKING_CHANGES.md at repository root
# 8. Commit with detailed message referencing documentation
Best Practices
✅ DO:
- Use moved blocks for resource type changes (automatic state migration)
- Validate arguments against official docs for both old and new resources
- Check default values - document if new resource has different defaults
- Update dependent resources that reference migrated resources
- Document what was done - show code changes applied
- Include resource documentation links for transparency
- Use pipeline validation - let CI/CD handle terraform commands
❌ DON'T:
- Provide manual migration steps - code should handle everything
- Assume arguments stayed the same - always validate schemas
- Forget dependent resources - search and update references
- Miss attribute changes - update
.nameto.idwhere needed - Remove provider blocks - only update deprecated arguments
- Suggest terraform commands - users validate via pipeline
Additional Resources
For detailed examples, common patterns, and troubleshooting, see the reference guide.