name: tf-resource-migration description: Migrate existing Terraform resources from SDK provider (sdkprovider) to Plugin Framework. Use when converting legacy resources, ensuring state compatibility, or when the user asks to port/migrate existing resources. allowed-tools: Read, Grep, Glob, Bash, Edit, Write, Task
Terraform Resource Migration Skill
Migrate existing SDK-based resources to Plugin Framework while maintaining state compatibility and behavior parity.
Overview
This skill guides migration of resources from:
- Source:
internal/sdkprovider/(terraform-plugin-sdk/v2) - Target:
internal/plugin/(terraform-plugin-framework) with YAML-generated code
Key Challenge: Preserve exact behavior and state compatibility so users don't experience breaking changes.
Prerequisites: This skill builds on tf-resource-generator. For YAML syntax, generation commands, and testing patterns, see that skill.
When to Use This Skill
Use this skill when:
- Migrating existing
aiven_*resources from SDK to Plugin Framework - User says: "migrate", "convert", "port", "move to Plugin Framework"
- Modernizing legacy resources
Use tf-resource-generator skill when:
- Creating brand new resources from scratch
- Adding resources that don't exist yet
Migration Workflow
1. Analyze the Existing SDK Resource
Find the SDK resource:
# Find resource file
find internal/sdkprovider -name "*resource_*.go" | grep -i "resource_name"
# Find data source file
find internal/sdkprovider -name "*datasource_*.go" | grep -i "resource_name"
Read and document:
- Schema definition (all fields, types, attributes)
- CRUD functions (Create, Read, Update, Delete)
- Custom logic and transformations
- State upgrade functions (if any)
- Existing tests (critical for parity)
2. Identify API Operations
Check what API operations the SDK resource uses:
# Search for API client calls in the resource
grep -A 5 "client\." internal/sdkprovider/service/resource_name.go
# IMPORTANT: Also check the data source — it may use a different API operation
grep -A 5 "client\." internal/sdkprovider/service/resource_name_data_source.go
Then find corresponding OpenAPI operation IDs. See tf-resource-generator for OpenAPI search patterns.
Determine clientHandler: The clientHandler YAML value is the Go package name under github.com/aiven/go-client-codegen/handler/. Find it by searching for the operation ID in the module cache:
grep -r "OperationID" $(go env GOMODCACHE)/github.com/aiven/go-client-codegen*/handler/
For example, ServiceFlinkCreateApplication lives in handler/flinkapplication/, so clientHandler: flinkapplication.
3. Map SDK Schema to YAML
SDK Type → YAML Type
| SDK Type | YAML Type |
|----------|-----------|
| schema.TypeString | type: string |
| schema.TypeInt | type: integer |
| schema.TypeFloat | type: number |
| schema.TypeBool | type: boolean |
| schema.TypeList | type: arrayOrdered |
| schema.TypeSet | type: array (or arrayOrdered for performance) |
| schema.TypeMap | additionalProperties: {type: string} |
SDK Attributes → YAML Attributes
| SDK Attribute | YAML Attribute |
|---------------|----------------|
| Required: true | required: true |
| Optional: true | optional: true |
| Computed: true | computed: true |
| Sensitive: true | sensitive: true |
| ForceNew: true | forceNew: true |
| ConflictsWith: [] | conflictsWith: [] |
| ExactlyOneOf: [] | exactlyOneOf: [] |
Nested Blocks
SDK Set of objects:
"tags": {
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {Type: schema.TypeString},
"value": {Type: schema.TypeString},
},
},
}
YAML (use arrayOrdered for performance):
schema:
tags:
type: arrayOrdered
items:
type: object
properties:
key:
type: string
value:
type: string
4. Preserve ID Structure
CRITICAL: The ID format MUST stay the same for state compatibility.
Find the ID format in SDK code:
# Look for ResourceData.SetId calls
grep -A 2 "SetId" internal/sdkprovider/service/resource_name.go
# Look for ID builder functions
grep -B 5 "buildResourceID\|parseResourceID" internal/sdkprovider/service/resource_name.go
Common ID patterns:
- Single field:
project - Composite:
project/service_name/database_name
Set in YAML:
idAttributeComposed: [project, service_name, database_name]
5. Handle Custom Logic
Identify in SDK code:
StateUpgraders→ May need state upgrader in Plugin FrameworkCustomizeDiff→ UseplanModifier: true- Flatten/Expand functions → Use
expandModifier: true/flattenModifier: true
For modifier implementation details, see tf-resource-generator skill.
6. Create YAML Definition
Create definitions/resource_name.yml. For complete YAML syntax reference, see tf-resource-generator skill.
Focus on migration-specific concerns:
- Match all SDK schema fields exactly
- Preserve ID structure
- Copy descriptions from SDK resource
7. Generate and Build
task generate
task build
task lint
8. State Compatibility Verification
CRITICAL: Ensure state is compatible between SDK and Plugin Framework versions.
Check schema version in SDK:
grep -A 3 "SchemaVersion" internal/sdkprovider/service/resource_name.go
If SDK has SchemaVersion > 0, you MUST handle state upgrades.
9. Backward Compatibility Testing
CRITICAL: Test that existing state from SDK version works with Plugin Framework version.
Use acc.BackwardCompatibilitySteps() helper:
func TestAccAivenResource_backwardCompat(t *testing.T) {
resourceName := "aiven_resource_name.test"
projectName := acc.ProjectName()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
Steps: acc.BackwardCompatibilitySteps(t, acc.BackwardCompatConfig{
TFConfig: testAccResourceConfig(projectName),
OldProviderVersion: "4.47.0", // Check CHANGELOG.md for latest
Checks: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "project", projectName),
// Add all key attribute checks
),
}),
})
}
Find the latest version:
head -20 CHANGELOG.md
What this test does:
- Creates resource with OLD SDK provider version
- Applies with NEW Plugin Framework version
- Verifies state is compatible and attributes match
Example: See internal/plugin/service/mysql/database/database_test.go
10. Parity Testing
CRITICAL: Verify behavior matches SDK resource exactly.
Find SDK tests:
ls internal/sdkprovider/service/*resource_name*_test.go
Ensure Plugin Framework tests cover:
- All CRUD operations from SDK tests
- Edge cases
- Error handling
- Import functionality
- Special field behaviors
State Compatibility Checklist
Before marking migration complete:
- [ ] Resource ID format is identical
- [ ] All schema fields are present (no removals)
- [ ] Field types match exactly
- [ ] Computed fields work the same way
- [ ] Default values match
- [ ] Required/Optional flags match
- [ ] ForceNew behavior matches
- [ ] Import works with existing IDs
- [ ] Existing state can be used without migration
- [ ] Backward compatibility test added using
acc.BackwardCompatibilitySteps() - [ ] All SDK test scenarios pass with Plugin version
- [ ] Changelog entry added to
CHANGELOG.md
Common Migration Issues
| Issue | Solution |
|-------|----------|
| ID format changed accidentally | Verify idAttributeComposed matches SDK's ID builder |
| Set ordering causes diffs | Use arrayOrdered instead of array |
| Computed field becomes required | Keep as computed: true if API provides it |
| Custom validation lost | Implement in custom modifier or use schema validation |
| State upgrade needed | Implement state upgrader in Plugin Framework |
| DiffSuppressFunc behavior | Implement plan modifier for custom diff logic |
| Data source lookup key differs from resource ID | Override DataSourceOptions.Read and DataSourceOptions.Schema via init() (see below) |
| DiffSuppressFunc behavior | Use planModifier: true for custom diff logic |
| Renamed ID field missing in old state | Use planModifier: true to extract from composite ID (see tf-resource-generator skill) |
| Read fails with 404 after migration | Likely a renamed ID field is empty — use planModifier to populate it before the API call |
Data Source Lookup Key Differs from Resource ID
The generator derives the data source schema from idAttributeComposed — ID fields become Required, everything else becomes Computed. This works when the data source looks up by the same fields as the resource ID.
Problem: The SDK data source may look up by a different field (e.g., name) than what's in the resource ID (e.g., application_id). The generator has no way to express "the resource ID uses application_id, but the data source should require name."
Detection: Always read the SDK data source implementation. If it uses a List endpoint and filters by a field that's NOT in idAttributeComposed, you need a custom override.
Solution: Create a non-generated file that overrides DataSourceOptions via init():
- Override
DataSourceOptions.Schema— wrap the generateddatasourceSchema()and swap only the attributes that differ (don't duplicate the whole schema) - Override
DataSourceOptions.Read— use the List endpoint to find by name, then Get by ID for full details
Key points:
- Wrap the generated
datasourceSchema()— don't duplicate it. Only override the attributes that differ. - The read function uses List + filter by name, then Get by ID for full details (same pattern the SDK data source used).
- This preserves backward compatibility: existing configs using
namekeep working.
Reference: See internal/plugin/service/flink/application/application.go for a complete implementation.
Migration-Specific Commands
# Find SDK resource
find internal/sdkprovider -name "*resource_*.go" | grep -i "name"
# Analyze SDK schema
grep -A 20 "Schema:" internal/sdkprovider/service/resource.go
# Find SDK ID format
grep -A 2 "SetId" internal/sdkprovider/service/resource.go
# Compare implementations
diff internal/sdkprovider/service/resource.go internal/plugin/service/resource/zz_resource.go
# Run backward compatibility test
task test-acc -- -run TestAccAivenResource_backwardCompat
Key Principles
- State compatibility first - Users should not need to recreate resources
- Preserve exact behavior - Match SDK resource behavior precisely
- Test thoroughly - All SDK test scenarios must pass with Plugin version
- Remove SDK version - Once verified, delete SDK resource to avoid maintenance burden
After Migration
Once all tests pass and state compatibility is verified:
- Remove SDK resource - Delete from
internal/sdkprovider/and remove provider registration - Update documentation - Ensure docs reflect the Plugin Framework version
- Add migration notes if needed - Document any unavoidable behavioral differences
- Add changelog entry - Add record to
CHANGELOG.mdunder the unreleased section:
- Migrate `aiven_resource_name` to the Plugin Framework
Do not maintain both versions - this creates maintenance burden and user confusion.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.