crossplane / crossplane/upjet

ApplyAPIConverters does not flatten NestingModeSingle (SchemaTypeObject) blocks in generated examples

Open
#656 1 comment 1 reaction 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.