xpkg build: GoTemplate function input kind field buried after large block scalar due to JSON key reordering
- Dominant language
- Go
- Stars
- 19
- Forks
- 31
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 52
Description
## 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:
1. Source YAML is parsed → stored as \`*v1.Composition\` with \`Input.Raw\` = JSON bytes
2. \`sigs.k8s.io/yaml.YAMLToJSON()\` converts the YAML map to JSON with **alphabetically sorted keys**
3. Original YAML field order: \`apiVersion → kind → source → inline\`
4. 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
1. Create a Composition with a \`function-go-templating\` step whose \`inline.template\` is >100 lines
2. Run \`crossplane xpkg build\`
3. Extract \`package.yaml\` from the built \`.xpkg\`:
```
tar xf package.xpkg
tar xzf .tar.gz
```
4. Search for \`kind: GoTemplate\` — it is present but appears **after** the entire template block, not next to \`apiVersion:\`
5. 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
- crossplane/crossplane#7819 — Crossplane v2.2.0 added the \`kind\` validation requirement that surfaces this bug
Contributor guide
Assessment
This issue has not been assessed yet.