Provider returns label arrays in non-deterministic order causing inconsistent result errors #140

Closed
opened 2026-04-05 16:18:56 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @listellm on 1/23/2026

Summary

Provider returns label arrays in non-deterministic order, causing "Provider produced inconsistent result after apply" errors and perpetual drift detection.

Problem Description

When updating a resource with labels (e.g., oneuptime_probe), the provider successfully applies the changes but fails Terraform's round-trip validation because the API returns labels in a different order than specified in the configuration.

Symptoms

  • Error: Provider produced inconsistent result after apply
  • Labels[0] and labels[1] swap positions between apply and read
  • Resource is marked as tainted on every apply
  • Terraform tries to recreate the resource unnecessarily

Root Cause Analysis

Provider Code Location

Terraform/terraform-provider-oneuptime/internal/provider/resource_probe.go

Lines 352-377 (Create), 659-684 (Read), 1024-1049 (Update):

if val, ok := dataMap["labels"].([]interface{}); ok {
    // Convert API response list to Terraform list
    var listItems []attr.Value
    for _, item := range val {
        if itemMap, ok := item.(map[string]interface{}); ok {
            if id, ok := itemMap["_id"].(string); ok {
                listItems = append(listItems, types.StringValue(id))
            }
        }
    }
    data.Labels = types.ListValueMust(types.StringType, listItems)
}

Issue: The provider appends labels in whatever order the API returns them, without any sorting or normalization.

API Behavior

The OneUptime API does not guarantee label order preservation. Labels are likely returned in:

  • Database insertion order
  • Internal ID order
  • Non-deterministic order

Terraform Expectation

Terraform compares list values by position:

  • labels[0] in config must equal labels[0] from API
  • labels[1] in config must equal labels[1] from API
  • etc.

Steps to Reproduce

  1. Create a probe with multiple labels:
resource "oneuptime_probe" "example" {
  name         = "example-probe"
  project_id   = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  key          = "example-key"
  probe_version = "9.4.7"
  labels = [
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # cloud:aws
    "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", # environment:dev
    "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz", # region:eu-central-1
    "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", # server:cluster-01
  ]
}
  1. Run terraform apply
  2. Observe error:
Error: Provider produced inconsistent result after apply

When applying changes to oneuptime_probe.example, provider
"provider[\"registry.terraform.io/oneuptime/oneuptime\"]" produced an
unexpected new value: .labels[0]: was
cty.StringVal("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"), but now
cty.StringVal("yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy").
  1. Check state: Labels exist but in different order
  2. Run terraform apply again: Same error repeats

Expected Behavior

  • Labels should be returned in the same order as sent
  • OR labels should be sorted consistently (e.g., alphabetically by ID)
  • No drift should be detected when label content is identical

Actual Behavior

  • Labels are returned in API-determined order
  • Order differs from configuration order
  • Terraform detects drift on every apply
  • Resource is unnecessarily recreated

Impact

  • Severity: High
  • Affects: All resources with label arrays (oneuptime_probe, oneuptime_monitor, oneuptime_status_page, etc.)
  • Workaround: Lifecycle ignore_changes = [labels] (not ideal, loses drift detection)

Proposed Fix

Sort label arrays after reading from API to ensure deterministic order:

if val, ok := dataMap["labels"].([]interface{}); ok {
    var listItems []attr.Value
    var labelIDs []string

    // Extract all label IDs
    for _, item := range val {
        if itemMap, ok := item.(map[string]interface{}); ok {
            if id, ok := itemMap["_id"].(string); ok {
                labelIDs = append(labelIDs, id)
            }
        }
    }

    // Sort alphabetically for consistent ordering
    sort.Strings(labelIDs)

    // Convert to Terraform values
    for _, id := range labelIDs {
        listItems = append(listItems, types.StringValue(id))
    }

    data.Labels = types.ListValueMust(types.StringType, listItems)
}

Option 2: Use TypeSet Instead of TypeList

Change schema to use types.SetType which is order-independent:

"labels": schema.SetAttribute{
    ElementType: types.StringType,
    // ...
}

Recommendation: Option 1 (sorting) is safer as it maintains backwards compatibility with existing state files.

  • #2228 - Provider returned wrapper objects instead of plain strings (fixed in v9.4.7)
  • #2230 - ResourceGenerator complex object regression (fixed in v9.4.7)

All three issues share the same root category: round-trip consistency failures between provider CREATE/UPDATE and READ operations.

Environment

  • Provider Version: 9.4.7
  • Terraform Version: 1.12.0
  • Affected Resources: oneuptime_probe, oneuptime_monitor, oneuptime_status_page, and any resource with label arrays

Additional Context

The issue occurs because:

  1. CREATE/UPDATE sends labels as [{\"_id\": \"uuid1\"}, {\"_id\": \"uuid2\"}]
  2. API stores labels in database
  3. READ returns labels as [{\"_id\": \"uuid2\"}, {\"_id\": \"uuid1\"}] (different order)
  4. Provider doesn't normalize order
  5. Terraform detects drift due to positional mismatch
*Originally created by @listellm on 1/23/2026* ## Summary Provider returns label arrays in non-deterministic order, causing "Provider produced inconsistent result after apply" errors and perpetual drift detection. ## Problem Description When updating a resource with labels (e.g., `oneuptime_probe`), the provider successfully applies the changes but fails Terraform's round-trip validation because the API returns labels in a different order than specified in the configuration. ## Symptoms - Error: `Provider produced inconsistent result after apply` - Labels[0] and labels[1] swap positions between apply and read - Resource is marked as tainted on every apply - Terraform tries to recreate the resource unnecessarily ## Root Cause Analysis ### Provider Code Location `Terraform/terraform-provider-oneuptime/internal/provider/resource_probe.go` Lines 352-377 (Create), 659-684 (Read), 1024-1049 (Update): ```go if val, ok := dataMap["labels"].([]interface{}); ok { // Convert API response list to Terraform list var listItems []attr.Value for _, item := range val { if itemMap, ok := item.(map[string]interface{}); ok { if id, ok := itemMap["_id"].(string); ok { listItems = append(listItems, types.StringValue(id)) } } } data.Labels = types.ListValueMust(types.StringType, listItems) } ``` **Issue**: The provider appends labels in whatever order the API returns them, without any sorting or normalization. ### API Behavior The OneUptime API does not guarantee label order preservation. Labels are likely returned in: - Database insertion order - Internal ID order - Non-deterministic order ### Terraform Expectation Terraform compares list values by position: - `labels[0]` in config must equal `labels[0]` from API - `labels[1]` in config must equal `labels[1]` from API - etc. ## Steps to Reproduce 1. Create a probe with multiple labels: ```hcl resource "oneuptime_probe" "example" { name = "example-probe" project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" key = "example-key" probe_version = "9.4.7" labels = [ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # cloud:aws "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", # environment:dev "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz", # region:eu-central-1 "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", # server:cluster-01 ] } ``` 2. Run `terraform apply` 3. Observe error: ``` Error: Provider produced inconsistent result after apply When applying changes to oneuptime_probe.example, provider "provider[\"registry.terraform.io/oneuptime/oneuptime\"]" produced an unexpected new value: .labels[0]: was cty.StringVal("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"), but now cty.StringVal("yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"). ``` 4. Check state: Labels exist but in different order 5. Run `terraform apply` again: Same error repeats ## Expected Behavior - Labels should be returned in the same order as sent - OR labels should be sorted consistently (e.g., alphabetically by ID) - No drift should be detected when label content is identical ## Actual Behavior - Labels are returned in API-determined order - Order differs from configuration order - Terraform detects drift on every apply - Resource is unnecessarily recreated ## Impact - **Severity**: High - **Affects**: All resources with label arrays (`oneuptime_probe`, `oneuptime_monitor`, `oneuptime_status_page`, etc.) - **Workaround**: Lifecycle `ignore_changes = [labels]` (not ideal, loses drift detection) ## Proposed Fix ### Option 1: Sort in Provider (Recommended) Sort label arrays after reading from API to ensure deterministic order: ```go if val, ok := dataMap["labels"].([]interface{}); ok { var listItems []attr.Value var labelIDs []string // Extract all label IDs for _, item := range val { if itemMap, ok := item.(map[string]interface{}); ok { if id, ok := itemMap["_id"].(string); ok { labelIDs = append(labelIDs, id) } } } // Sort alphabetically for consistent ordering sort.Strings(labelIDs) // Convert to Terraform values for _, id := range labelIDs { listItems = append(listItems, types.StringValue(id)) } data.Labels = types.ListValueMust(types.StringType, listItems) } ``` ### Option 2: Use TypeSet Instead of TypeList Change schema to use `types.SetType` which is order-independent: ```go "labels": schema.SetAttribute{ ElementType: types.StringType, // ... } ``` **Recommendation**: Option 1 (sorting) is safer as it maintains backwards compatibility with existing state files. ## Related Issues - #2228 - Provider returned wrapper objects instead of plain strings (fixed in v9.4.7) - #2230 - ResourceGenerator complex object regression (fixed in v9.4.7) All three issues share the same root category: **round-trip consistency failures** between provider CREATE/UPDATE and READ operations. ## Environment - **Provider Version**: 9.4.7 - **Terraform Version**: 1.12.0 - **Affected Resources**: `oneuptime_probe`, `oneuptime_monitor`, `oneuptime_status_page`, and any resource with label arrays ## Additional Context The issue occurs because: 1. CREATE/UPDATE sends labels as `[{\"_id\": \"uuid1\"}, {\"_id\": \"uuid2\"}]` 2. API stores labels in database 3. READ returns labels as `[{\"_id\": \"uuid2\"}, {\"_id\": \"uuid1\"}]` (different order) 4. Provider doesn't normalize order 5. Terraform detects drift due to positional mismatch
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/oneuptime#140