Skip to content

xpkg build: GoTemplate function input kind field buried after large block scalar due to JSON key reordering #344

Description

@ereddy-wiser

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 <layer>.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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions