Skip to content

Synthesized RawConfig uses null for unset blocks where Terraform core produces empty lists, panicking SDKv2 provider code #729

Description

@duizabojul

What happened?

After bumping our GCP provider (a fork of provider-upjet-gcp) to terraform-provider-google-beta 7.43, every google_container_node_pool create that doesn't set kubelet_config fails with:

async create failed: recovered from panic: value is null

The stack trace ends in the terraform provider:

github.com/hashicorp/go-cty/cty.Value.LengthInt
    go-cty@v1.5.0/cty/value_ops.go:997
github.com/hashicorp/terraform-provider-google-beta/google-beta/services/container.expandNodeConfig
    google-beta/services/container/node_config.go:1912
github.com/hashicorp/terraform-provider-google-beta/google-beta/services/container.expandNodePool

That line is hashicorp's "cpu_cfs_quota fix", present since roughly google-beta 7.6-7.10: it reads d.GetRawConfig() and calls LengthInt() on node_config[0].kubelet_config without a null check.

This looks like the remaining gap after #319 and #685: those made sure RawConfig/RawPlan are always populated, using the value built by schema.JSONMapToStateValue(params, schemaBlock) (pkg/controller/external_tfpluginsdk.go:267). The problem is the shape of that value: with ctyjson decoding, any block missing from params comes out as null. Terraform core never hands providers that shape — unset list/set blocks decode to empty collections — so provider code feels free to call LengthInt()/Index() on them without IsNull() checks, and hashicorp has no reason to accept that as a bug since it's unreachable through terraform itself. These "was it actually set in config" checks via GetRawConfig are getting more common in the hashicorp providers, so I'd expect more of these over time.

FWIW provider-upjet-gcp main is affected too: it pins terraform-provider-google e40b7568, which has the same unguarded code (it's also in the v7.39.0 the Makefile references), so NodePool creates without kubeletConfig should panic there as well, on both the google and google-beta flavors.

How can we reproduce it?

On a provider built against terraform-provider-google(-beta) >= ~7.10, create a NodePool MR with nodeConfig set but no nodeConfig.kubeletConfig. Every reconcile panics as above. Adding kubeletConfig to the spec makes the same create go through, which confirms where it comes from.

Possible fix

Make the synthesized RawConfig match what Terraform core produces: empty lists/sets instead of null for unset block attributes, in the same place #685 already normalizes things. That would fix this whole class of panic for every provider rather than chasing individual guards in provider forks. Happy to send a PR if that direction sounds right.

Environment

  • upjet v2.4.1-0.20260728103920-4f6e6e10dff2
  • terraform-plugin-sdk v2.40.1
  • terraform-provider-google-beta 7.43 (c1a4c311)
  • crossplane-runtime v2.3.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions