Skip to content

crossplane render --observed-resources drops observed resources when the file also contains the XR #47

@LorenzBischof

Description

@LorenzBischof

Note

This issue was written and debugged with claude-code

What happened?

When the --observed-resources file contains the XR (alongside the composed resources), all of the observed composed resources are silently dropped, the function pipeline runs with an empty observed state and the output is wrong, with no error.

This is easy to hit because crossplane render always emits the XR as the first document, so the common "render once, feed the output back as observed for a second render" workflow includes the XR by default. The same inputs worked on the v2.2 CLI.

Cause: the observed file contains a copy of the XR, which gets loaded into the render engine's in-memory store keyed by GVK + namespace + name, after the real XR — so the UID-less observed copy overwrites the engine's XR. With the XR's UID now empty, the reconciler keeps only composed resources whose controller ownerReference.UID matches the XR's UID, and drops all of them. v2.2 didn't run observed resources through this ownership check, so the extra XR document was harmless there.

How can we reproduce it?

Any composition with a function pipeline that composes at least one resource:

crossplane render xr.yaml composition.yaml functions.yaml > observed.yaml
crossplane render xr.yaml composition.yaml functions.yaml --observed-resources observed.yaml

The second render's functions see no observed resources. Removing the XR document from observed.yaml (e.g. yq 'select(.kind != "<XR-kind>")') makes it work again.

Suggested fix

The XR is already supplied as the positional argument, so a copy of it in --observed-resources is redundant. Skipping it in cmd/crossplane/render/convert.go (BuildCompositeRequest, before converting observed resources) fixes it without any engine change:

if o.GroupVersionKind() == in.CompositeResource.GroupVersionKind() &&
    o.GetNamespace() == in.CompositeResource.GetNamespace() &&
    o.GetName() == in.CompositeResource.GetName() {
    continue
}

Happy to send a PR.

What environment did it happen in?

  • Crossplane CLI version: v2.3.0 and v2.3.1 (v2.2.1 is fine)
  • Platform (e.g., linux/amd64): linux/amd64
  • Crossplane version (if applicable): render engine v2.3.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions