crossplane / crossplane/upjet

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

Open
#729 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
481
Forks
131
Avg merge
2d 1h
Merged PRs (30d)
11

Description

### 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"](https://github.com/hashicorp/terraform-provider-google/issues/15767), 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

Contributor guide

Open the contributing guide

Research direction

Start at pkg/controller/external_tfpluginsdk.go:267 and trace schema.JSONMapToStateValue, focusing on how unset block attributes are represented during ctyjson decoding. Reproduce a NodePool create with nodeConfig but no kubeletConfig against the affected Google provider versions. Done means synthesized RawConfig uses empty lists or sets for unset blocks and the reproduction no longer panics.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.