Skip to content

[APIE-848] Fix for schema_registry_cluster drift during migration#947

Open
Cynthia Qin (cqin-confluent) wants to merge 6 commits intomasterfrom
APIE-852
Open

[APIE-848] Fix for schema_registry_cluster drift during migration#947
Cynthia Qin (cqin-confluent) wants to merge 6 commits intomasterfrom
APIE-852

Conversation

@cqin-confluent
Copy link
Member

@cqin-confluent Cynthia Qin (cqin-confluent) commented Feb 20, 2026

Release Notes

New Features

  • [Briefly describe new features introduced in this PR].

Bug Fixes

Examples

  • [Briefly describe any Terraform configuration example updates in this PR].

Checklist

  • I can successfully build and use a custom Terraform provider binary for Confluent.
  • I have verified my PR with real Confluent Cloud resources in a pre-prod or production environment, or both.
  • I have attached manual Terraform verification results or screenshots in the Test & Review section below.
  • I have included appropriate Terraform acceptance or unit tests for any new resource, data source, or functionality.
  • I have included appropriate Terraform live testing for any new resource, data source, or functionality.
  • I have included a testing thread with main.tf file in #terraform-provider-development-testing.
  • I have included rate limit/load testing results.
  • I confirm that this PR introduces no breaking changes or backward compatibility issues.
  • I have updated the corresponding documentation and include relevant examples for this PR.
  • I have indicated the potential customer impact if something goes wrong in the Blast Radius section below.
  • I have put checkmarks below confirming that the feature associated with this PR is enabled in:
    • Confluent Cloud prod
    • Confluent Cloud stag
    • Check this box if the feature is enabled for certain organizations only

What

There is a Terraform issue that can cause drift when migrating the provider type from Option 1 to Option 2 for the confluent_schema_registry_cluster_config resource.

Specifically, both rest_endpoint and the schema_registry_cluster block are marked ForceNew in existing Terraform logic, which causes Terraform to incorrectly plan a recreation during the migration even though it’s the same underlying cluster.

The fix is is to suppress the diff during migration, while still preserving the current behavior where user-initiated changes to these fields should still trigger ForceNew

Blast Radius

This is a bug fix so no existing workflow should be impacted.

References

APIE-852: Migrate provider type (from #Option 1 to #Option2)

Test & Review

Integration test passes

❯ TF_ACC=1 go test ./... -v -timeout 5m -run='TestAccSchemaRegistryClusterCompatibilityLevel'
... 
--- PASS: TestAccSchemaRegistryClusterCompatibilityLevel (5.44s)
PASS
ok      github.com/confluentinc/terraform-provider-confluent/internal/provider  15.070s

manual test of Option 1 -> Option 2 migration (reproduce the drift)

❯ terraform plan
confluent_schema_registry_cluster_config.example: Refreshing state... [id=lsrc-p8gn62]
...
  # confluent_schema_registry_cluster_config.example must be replaced
-/+ resource "confluent_schema_registry_cluster_config" "example" {
      + compatibility_group = (known after apply)
      ~ id                  = "lsrc-p8gn62" -> (known after apply)
      ~ normalize           = false -> (known after apply)
      - rest_endpoint       = "https://psrc-em82q.us-east-2.aws.confluent.cloud" -> null # forces replacement
        # (1 unchanged attribute hidden)

      - credentials {
          - key    = (sensitive value) -> null
          - secret = (sensitive value) -> null
        }

      - schema_registry_cluster { # forces replacement
          - id = "lsrc-p8gn62" -> null # forces replacement
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

manual test when it’s the same underlying cluster(after the fix)

❯ terraform plan
...
confluent_schema_registry_cluster_config.example: Refreshing state... [id=lsrc-p8gn62]

No changes. Your infrastructure matches the configuration.

manual test when user-initiated changes to these fields should still trigger ForceNew (after the fix)

❯ terraform plan
... 
  # confluent_schema_registry_cluster_config.example must be replaced
-/+ resource "confluent_schema_registry_cluster_config" "example" {
      + compatibility_group = (known after apply)
      ~ id                  = "lsrc-p8gn62" -> (known after apply)
      ~ normalize           = false -> (known after apply)
      - rest_endpoint       = "https://psrc-em82q.us-east-2.aws.confluent.cloud" -> null # forces replacement
        # (1 unchanged attribute hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Copilot AI review requested due to automatic review settings February 20, 2026 20:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds drift suppression logic to handle migration scenarios for the confluent_schema_registry_cluster_config resource. Specifically, it addresses drift detection issues when users migrate from resource-level configuration (Option 1) to provider-level configuration (Option 2) for Schema Registry cluster settings.

Changes:

  • Added suppressSchemaRegistryClusterConfigMigrationDiff function to suppress diffs during migration from resource-level to provider-level configuration
  • Integrated the suppression function into the CustomizeDiff sequence for the schema registry cluster config resource

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}
}

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff suppression function only handles rest_endpoint and schema_registry_cluster, but it doesn't suppress the credentials block during migration. When migrating from resource-level configuration (Option 1) to provider-level configuration (Option 2), users would typically remove the credentials block from the resource, which could cause an unwanted diff if the old credentials match the provider's credentials. Consider adding similar logic to suppress the credentials diff when the old credentials match the provider's schema_registry_api_key and schema_registry_api_secret.

Suggested change
// Verify the old credentials match the provider's credentials and new param is empty/null, then suppress the diff
if diff.HasChange(paramCredentials) {
oldCredentialsBlock, newCredentialsBlock := diff.GetChange(paramCredentials)
oldCredentialsList, oldOk := oldCredentialsBlock.([]interface{})
newCredentialsList, newOk := newCredentialsBlock.([]interface{})
if oldOk && len(oldCredentialsList) > 0 && (!newOk || len(newCredentialsList) == 0) {
oldCredentialsMap, ok := oldCredentialsList[0].(map[string]interface{})
if !ok {
return nil
}
oldApiKey, okKey := oldCredentialsMap[paramKey].(string)
oldApiSecret, okSecret := oldCredentialsMap[paramSecret].(string)
if !okKey || !okSecret || oldApiKey == "" || oldApiSecret == "" {
return nil
}
if oldApiKey == client.schemaRegistryApiKey && oldApiSecret == client.schemaRegistryApiSecret {
if err := diff.SetNew(paramCredentials, oldCredentialsBlock); err != nil {
return fmt.Errorf("error suppressing credentials diff: %s", createDescriptiveError(err))
}
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +292 to +340
func suppressSchemaRegistryClusterConfigMigrationDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
client := meta.(*Client)

// Supress iff provider has Schema Registry metadata set (Option 2)
if !client.isSchemaRegistryMetadataSet {
return nil
}

// Verify the old rest_endpoint matches the provider's rest_endpoint and new param is empty/null, then suppress the diff
if diff.HasChange(paramRestEndpoint) {
oldRestEndpoint, newRestEndpoint := diff.GetChange(paramRestEndpoint)
oldRestEndpointStr, oldOk := oldRestEndpoint.(string)
newRestEndpointStr, newOk := newRestEndpoint.(string)

if oldOk && oldRestEndpointStr != "" && (!newOk || newRestEndpointStr == "") {
if oldRestEndpointStr == client.schemaRegistryRestEndpoint {
if err := diff.SetNew(paramRestEndpoint, oldRestEndpointStr); err != nil {
return fmt.Errorf("error suppressing rest_endpoint diff: %s", createDescriptiveError(err))
}
}
}
}

// Verify the old cluster ID matches the provider's cluster ID and new param is empty/null, then suppress the diff
if diff.HasChange(paramSchemaRegistryCluster) {
oldClusterBlock, newClusterBlock := diff.GetChange(paramSchemaRegistryCluster)
oldClusterList, oldOk := oldClusterBlock.([]interface{})
newClusterList, newOk := newClusterBlock.([]interface{})

if oldOk && len(oldClusterList) > 0 && (!newOk || len(newClusterList) == 0) {
oldClusterMap, ok := oldClusterList[0].(map[string]interface{})
if !ok {
return nil
}
oldClusterId, ok := oldClusterMap[paramId].(string)
if !ok || oldClusterId == "" {
return nil
}

if oldClusterId == client.schemaRegistryClusterId {
if err := diff.SetNew(paramSchemaRegistryCluster, oldClusterBlock); err != nil {
return fmt.Errorf("error suppressing schema_registry_cluster diff: %s", createDescriptiveError(err))
}
}
}
}

return nil
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests for the new suppressSchemaRegistryClusterConfigMigrationDiff function. Consider adding a test case that verifies the migration scenario from resource-level configuration to provider-level configuration, ensuring that diffs are properly suppressed when the configurations match.

Copilot uses AI. Check for mistakes.
func suppressSchemaRegistryClusterConfigMigrationDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
client := meta.(*Client)

// Supress iff provider has Schema Registry metadata set (Option 2)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a spelling error in the comment. "Supress" should be "Suppress".

Suggested change
// Supress iff provider has Schema Registry metadata set (Option 2)
// Suppress iff provider has Schema Registry metadata set (Option 2)

Copilot uses AI. Check for mistakes.
},
Schema: map[string]*schema.Schema{
paramSchemaRegistryCluster: schemaRegistryClusterBlockSchema(),
paramSchemaRegistryCluster: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated quick question: why did we stop using schemaRegistryClusterBlockSchema()?

Copy link
Member Author

@cqin-confluent Cynthia Qin (cqin-confluent) Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schemaRegistryClusterBlockSchema() has a static ForceNew. So I added a separate function schemaRegistryClusterConfigForceNewCustomDiff instead, that applies custom ForceNew logic to prevent TF force recreation when migrating resources within the same cluster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants