ApplyAPIConverters does not flatten NestingModeSingle (SchemaTypeObject) blocks in generated examples
- Dominant language
- Go
- Stars
- 481
- Forks
- 131
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 11
Description
## Summary
`ApplyAPIConverters` (added in #538) correctly flattens single-element arrays for `TypeList`/`TypeSet` with `MaxItems=1` fields in generated example manifests. However, it does not handle `SchemaTypeObject` fields (Terraform plugin framework's `NestingModeSingle` blocks), which also need flattening.
### High-level example
Terraform examples represent single nested blocks as arrays:
```yaml
spec:
forProvider:
metadata:
- uid: example-id
```
Reference resolution must initially retain that shape because references can use indexed paths such as `metadata[0].uid`. After references are resolved, the generated Crossplane manifest should flatten fields whose schema identifies them as single objects:
```yaml
spec:
forProvider:
metadata:
uid: example-id
```
The original in-memory manifest must remain array-shaped while examples are processed so other examples can still resolve indexed references. Only the copy serialized to YAML should be flattened.
## Background
The HCL-to-JSON scraper always wraps Terraform blocks in arrays per HCL spec. For `NestingModeSingle` blocks, upjet maps these to `SchemaTypeObject` (ValueType 9) and generates CRD types as embedded objects (pointer-to-struct), not slices. But the generated example manifests retain the array syntax from the scraper, producing invalid YAML that fails with "unknown field" errors when applied.
## Why this doesn't affect AWS/GCP/Azure providers
Those providers use the older SDK v2 `TypeList + MaxItems=1` pattern. The `SingletonListEmbedder` + `ApplyAPIConverters` pipeline handles that correctly because:
1. `SingletonListEmbedder` registers conversion paths with `[*]` wildcards for `TypeList`/`TypeSet` fields
2. `conversion.Convert` uses `fieldpath.Pave` to traverse these paths and flatten arrays
For `SchemaTypeObject` fields, `SingletonListEmbedder` correctly skips them (they're already objects in the CRD). But the traverser does NOT add `[*]` wildcards to `SchemaTypeObject` paths, so `conversion.Convert` fails with "not an object" when trying to traverse the arrays that remain in the scraped examples.
## Who is affected
Any upjet-based provider whose Terraform provider uses the plugin framework `NestingModeSingle` pattern instead of the older `TypeList + MaxItems=1` pattern. The `grafana/crossplane-provider-grafana` provider is the first to hit this (grafana/crossplane-provider-grafana#549).
## Workaround
We added a post-processing step in our generator that uses upjet's schema traverser to collect CRD paths for `SchemaTypeObject` fields, then walks generated example YAML files and flattens single-element arrays at those paths (grafana/crossplane-provider-grafana#557).
## Expected behavior
`ApplyAPIConverters` (or a related utility) should also handle `SchemaTypeObject` fields by flattening single-element arrays in generated examples to plain objects, matching the CRD's pointer-to-struct type.
## Related
- #538 - Added `ApplyAPIConverters` for `TypeList`/`TypeSet` with `MaxItems=1`
- #593 - `NestingModeSingle` Computed flag
- #345 - Wrong types in generated examples
Contributor guide
Assessment
This issue has not been assessed yet.