Describe the bug
`crossplane xpkg build` reorders fields in embedded `RawExtension` objects alphabetically (via `sigs.k8s.io/yaml.YAMLToJSON()`) during YAML serialization. For a `function-go-templating` pipeline step input, this buries `kind: GoTemplate` after the entire `inline.template` block scalar — which can be hundreds or thousands of lines long.
Some YAML parsers and the Crossplane package reader fail to correctly handle a `kind` field that appears after a very large literal block scalar, causing installed ConfigurationRevisions to fail with:
spec.pipeline[N].input.kind: Required value
This became visible with Crossplane v2.2.0, which added strict validation requiring `kind` in all pipeline step inputs. See crossplane/crossplane#7819.
Root cause
The `encode()` function in `crossplane-runtime/pkg/xpkg/build.go` re-serializes all package objects through the Kubernetes JSON serializer:
- Source YAML is parsed → stored as `*v1.Composition` with `Input.Raw` = JSON bytes
- `sigs.k8s.io/yaml.YAMLToJSON()` converts the YAML map to JSON with alphabetically sorted keys
- Original YAML field order: `apiVersion → kind → source → inline`
- JSON (and final YAML) field order: `apiVersion → inline → kind → source`
Result in `package.yaml`:
```yaml
input:
apiVersion: gotemplating.fn.crossplane.io/v1beta1
inline:
template: |
{{- ... hundreds of lines of go template ... }}
kind: GoTemplate # ← buried after the block scalar
source: Inline
```
Steps to reproduce
- Create a Composition with a `function-go-templating` step whose `inline.template` is >100 lines
- Run `crossplane xpkg build`
- Extract `package.yaml` from the built `.xpkg`:
tar xf package.xpkg
tar xzf <layer>.tar.gz
- Search for `kind: GoTemplate` — it is present but appears after the entire template block, not next to `apiVersion:`
- Install the package on a Crossplane v2.2.0+ cluster → ConfigurationRevision fails
Expected behaviour
`kind: GoTemplate` appears adjacent to `apiVersion: gotemplating.fn.crossplane.io/v1beta1` in the serialized `package.yaml`, matching the source file and ensuring reliable parsing.
Suggested fix
In `crossplane-runtime/pkg/xpkg/build.go`, modify `encode()` to re-order keys in `RawExtension.Raw` JSON so that TypeMeta fields (`apiVersion`, `kind`) appear first before emitting. Concretely: after marshaling each object to JSON, walk any embedded `RawExtension` fields and produce a new JSON object with `apiVersion`/`kind` promoted to the front.
An alternative is to add a normalization pass in the `xpkg build` command (`cmd/crossplane/xpkg/build.go`) after `c.builder.Build()` — extract `package.yaml` from the built image, re-insert `kind`/`apiVersion` immediately after each other in embedded resource blocks, and rebuild the layer.
Workaround
Post-process the built `.xpkg` to insert `kind: GoTemplate` immediately after each `apiVersion: gotemplating.fn.crossplane.io/v1beta1` line. This is what the affected package repo currently does as a stop-gap.
Related
Describe the bug
`crossplane xpkg build` reorders fields in embedded `RawExtension` objects alphabetically (via `sigs.k8s.io/yaml.YAMLToJSON()`) during YAML serialization. For a `function-go-templating` pipeline step input, this buries `kind: GoTemplate` after the entire `inline.template` block scalar — which can be hundreds or thousands of lines long.
Some YAML parsers and the Crossplane package reader fail to correctly handle a `kind` field that appears after a very large literal block scalar, causing installed ConfigurationRevisions to fail with:
This became visible with Crossplane v2.2.0, which added strict validation requiring `kind` in all pipeline step inputs. See crossplane/crossplane#7819.
Root cause
The `encode()` function in `crossplane-runtime/pkg/xpkg/build.go` re-serializes all package objects through the Kubernetes JSON serializer:
Result in `package.yaml`:
```yaml
input:
apiVersion: gotemplating.fn.crossplane.io/v1beta1
inline:
template: |
{{- ... hundreds of lines of go template ... }}
kind: GoTemplate # ← buried after the block scalar
source: Inline
```
Steps to reproduce
Expected behaviour
`kind: GoTemplate` appears adjacent to `apiVersion: gotemplating.fn.crossplane.io/v1beta1` in the serialized `package.yaml`, matching the source file and ensuring reliable parsing.
Suggested fix
In `crossplane-runtime/pkg/xpkg/build.go`, modify `encode()` to re-order keys in `RawExtension.Raw` JSON so that TypeMeta fields (`apiVersion`, `kind`) appear first before emitting. Concretely: after marshaling each object to JSON, walk any embedded `RawExtension` fields and produce a new JSON object with `apiVersion`/`kind` promoted to the front.
An alternative is to add a normalization pass in the `xpkg build` command (`cmd/crossplane/xpkg/build.go`) after `c.builder.Build()` — extract `package.yaml` from the built image, re-insert `kind`/`apiVersion` immediately after each other in embedded resource blocks, and rebuild the layer.
Workaround
Post-process the built `.xpkg` to insert `kind: GoTemplate` immediately after each `apiVersion: gotemplating.fn.crossplane.io/v1beta1` line. This is what the affected package repo currently does as a stop-gap.
Related