Skip to content

[CNSL-2431] remove plan noise from metadata-only cluster updates - #362

Open
devlaneyr wants to merge 1 commit into
cockroachdb:mainfrom
devlaneyr:cnsl-2431-metadata-pin
Open

[CNSL-2431] remove plan noise from metadata-only cluster updates#362
devlaneyr wants to merge 1 commit into
cockroachdb:mainfrom
devlaneyr:cnsl-2431-metadata-pin

Conversation

@devlaneyr

Copy link
Copy Markdown
Contributor

Terraform marks every Computed attribute the config leaves unset as "known after apply", so changing a single label on a cockroach_cluster planned a dozen unrelated attributes as pending changes.

A prior commit e66893f added UseStateForUnknown to some of them. That modifier copies prior state into the plan unconditionally, so it only fits attributes that never change on an update. The rest do change on some updates, and stayed noisy on all of them.

Whether those attributes can change depends on what else the update is doing, which a per-attribute modifier can't see. This adds a resource-level check over the whole plan: when every difference between plan and state is confined to metadata that doesn't start a cluster operation, the remaining unknowns are filled in from prior state. Otherwise the plan is left alone. Only unknowns are ever replaced, so a plan can get quieter but never hide a change.

Commit checklist

  • Changelog
  • Doc gen (make generate)
  • Integration test(s)
  • Acceptance test(s)
  • Example(s)

Terraform marks every Computed attribute the config leaves unset as "known
after apply", so changing a single label on a cockroach_cluster planned a
dozen unrelated attributes as pending changes.

A prior commit e66893f added UseStateForUnknown to some of them. That
modifier copies prior state into the plan unconditionally, so it only fits
attributes that never change on an update. The rest do change on some
updates, and stayed noisy on all of them.

Whether those attributes can change depends on what else the update is
doing, which a per-attribute modifier can't see. This adds a resource-level
check over the whole plan: when every difference between plan and state is
confined to metadata that doesn't start a cluster operation, the remaining
unknowns are filled in from prior state. Otherwise the plan is left alone.
Only unknowns are ever replaced, so a plan can get quieter but never hide a
change.
@devlaneyr

Copy link
Copy Markdown
Contributor Author

I reproduced by using the main.tf file:

terraform {
    required_providers {
      cockroach = { source = "cockroachdb/cockroach" }
    }
  }

  variable "environment" {
    type    = string
    default = "dev"
  }

  resource "cockroach_cluster" "repro" {
    name           = "cnsl-2431"
    cloud_provider = "GCP"
    plan           = "STANDARD"
    serverless     = { usage_limits = { provisioned_virtual_cpus = 2 } }
    regions        = [{ name = "us-central1" }]
    labels         = { repro = "one" }
  }

which I performed terraform apply on.

I then modified the labels to:

labels = { repro = "two" }

which on main still yields noisy output from terraform plan as follows (I've redacted potentially sensitive fields):

 # cockroach_cluster.repro will be updated in-place
  ~ resource "cockroach_cluster" "repro" {
      ~ full_version      = "redacted" -> (known after apply)
        id                = "redacted"
      ~ labels            = {
          ~ "repro" = "one" -> "two"
        }
        name              = "cnsl-2431"
      ~ operation_status  = "UNSPECIFIED" -> (known after apply)
      ~ regions           = [
          ~ {
              + machine_type         = (known after apply)
                name                 = "us-central1"
              + num_virtual_cpus     = (known after apply)
              ~ primary              = true -> (known after apply)
                # (6 unchanged attributes hidden)
            },
        ]
      ~ serverless        = {
          + with_empty_ip_allowlist = (known after apply)
            # (3 unchanged attributes hidden)
        }
      ~ state             = "CREATED" -> (known after apply)
      ~ upgrade_status    = "FINALIZED" -> (known after apply)
        # (8 unchanged attributes hidden)
    }

After the updates in this commit the same terraform plan yields:

 # cockroach_cluster.repro will be updated in-place
  ~ resource "cockroach_cluster" "repro" {
        id                = "redacted"
      ~ labels            = {
          ~ "repro" = "one" -> "two"
        }
        name              = "cnsl-2431"
        # (14 unchanged attributes hidden)
    }

Nothing is marked known after apply anymore, so the plan shows only the label change.

@devlaneyr
devlaneyr requested review from fantapop and linhcrl August 11, 2026 21:48
@devlaneyr

Copy link
Copy Markdown
Contributor Author

@fantapop This is a follow-up PR to (#358)

Can you confirm that the fields in metadataOnlyAttributes truly are metadata only?

"labels": {},
"delete_protection": {},
"parent_id": {},
"backup_config": {},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are some weird rules governing backup_config. It can only be updated once without a support ticket. See here: https://github.com/cockroachdb/terraform-provider-cockroach/blob/3440b333e20a638ac142fe5a2dc1ee73040c2229/docs/guides/updating-backup-retention.md

I'm wondering if this might cause a problem if you're doing something like using state for unknown and expecting the update to work. It's worth testing at least.

Comment on lines +1974 to +1976
// Only resolve unknowns for a cluster in the CREATED state. Anything else
// isn't settled, and the apply would return different values than the plan
// promised, failing with "inconsistent result after apply".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm slightly surprised by this. Here are the states:

message ClusterState {
  enum Type {
    UNSPECIFIED = 0;
    CREATING = 1;
    CREATED = 2;
    CREATION_FAILED = 3;
    DELETED = 4;
    // An exclusive operation is being performed on this cluster.
    // Other operations should not proceed if they did not set a cluster into the LOCKED state.
    LOCKED = 5;
  }

It seems plausible we wouldn't allow updates while in CREATING, CREATION_FAILED or DELETED. I'm unclear about LOCKED. I think we might still allow certain metadata updates in which case this would be fine. This might be worth digging into a bit more.

Comment on lines +1981 to +1984
if opStatus := state.OperationStatus.ValueString(); IsKnown(state.OperationStatus) &&
opStatus != "" && opStatus != string(client.CLUSTERSTATUSTYPE_UNSPECIFIED) {
return
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Here again, it seems like we're not allowing updates during long running operations but I think that might actually be fine? Although sometimes terraform might be polling for a long running operation to finish such as during an update.

Comment on lines +1988 to +1989
// Can't prove the update is inert, so leave the plan alone.
tflog.Trace(ctx, "skipping metadata-only unknown resolution", map[string]any{"error": err.Error()})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree with the Trace level logging here but it's probably worth a comment about why we would log this error at trace rather than error.

}
}

resolved, err := tftypes.Transform(planRaw, func(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's probably worth a comment above here about what this is doing. It's pretty incoherent (at least to me)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It also might be worth trying to tuck some of this logic that's not specific to the cluster_resource into a separate file or package to keep it out of this file. the cluster_resource file is already large.

if !configIsNullAt(configRaw, attrPath) {
return val, nil
}
stateValI, _, err := tftypes.WalkAttributePath(stateRaw, attrPath)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what does this var name mean? Why the "I" at the end? Normally i would expect that to be short for "index" but I see we're casting it to the state value below.

Comment thread CHANGELOG.md
`delete_protection`, `dedicated.memory_gib`, `dedicated.disk_iops`, and the
`regions` block's `ui_dns`, `private_endpoint_dns`, and `s3_vpc_endpoint_id` are
no longer reported as "known after apply" on updates that don't change them.
- A `cockroach_cluster` update that changes only `labels`, `delete_protection`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Changelog entries should be placed on top like a stack.

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.

2 participants