Skip to content

observed.composite.resource.spec missing fields on first reconcile causes silent resource drops #579

Description

@johncarpio-sumo

Bug Description

When using observed.composite.resource.spec in a Go template, fields that are present in the XR may not be available on the first reconcile cycle. This causes {{- with .fieldName }} blocks to silently skip, resulting in composed resources never being created.

Root Cause

On the first reconcile after XR creation, observed.composite.resource.spec may not contain all user-specified fields. The desired.composite.resource.spec always has the full spec. When combined with resources that fail quickly (triggering the circuit breaker), the template never gets a second chance to execute with the now-populated observed state.

Environment

  • function-go-templating: v0.11.0
  • Crossplane: v1.19.x (latest stable)
  • Cluster: Kind (local development)

Steps to Reproduce

  1. Create a Composition with an inline template that uses {{- with .observed.composite.resource.spec -}} and accesses multiple sub-objects via {{- with .service }}, {{- with .autoscaling }}, etc.
  2. Include a resource that will fail immediately (e.g., AppAutoscaling Policy that depends on a Target that doesn't exist yet)
  3. Create an XR with all fields populated

Expected Behavior

All with blocks should execute on every reconcile since the XR spec is fully populated.

Actual Behavior

  • First reconcile: some with blocks don't execute because observed.composite.resource.spec is missing those fields
  • The failing resource triggers the circuit breaker
  • Subsequent reconciles are throttled — the template never re-executes with the complete observed state
  • Result: some composed resources are never created

Evidence

  • Decoder test: yaml.NewYAMLOrJSONDecoder correctly decodes 6+ documents from well-formed YAML
  • convertToMap uses protojson.Marshal(req) with default options — proto3 omits zero/empty fields
  • When the same template uses only 3 resources (no failing ones), all fields are available on the second reconcile (circuit breaker doesn't activate)
  • Switching from observed to desired composite spec resolves the issue completely

Workaround

Use desired with fallback to observed:

{{- $spec := (.desired.composite.resource.spec | default .observed.composite.resource.spec) -}}
{{- with $spec -}}
  ...
{{- end -}}

This ensures the full user-specified spec is always available regardless of reconcile timing.

Recommendation

Consider documenting that desired.composite.resource.spec should be preferred over observed.composite.resource.spec for resource creation templates, since desired always reflects the user's intent while observed may lag behind. Alternatively, the function could pre-merge desired and observed composite state before template execution.

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