What problem are you facing?
PR #326 introduces a self-contained workaround in cmd/diff/diffprocessor/render_engine.go to support multi-composition rendering in a single xr invocation (e.g. diffing a GitOps directory whose XRs resolve to different Composition objects with overlapping-but-not-identical function pipelines).
The workaround:
- Calls
engine.Setup exactly once on the first render. Upstream's dockerRenderEngine.Setup creates a fresh Docker network and stamps the first batch of functions with the render.AnnotationKeyRuntimeDockerNetwork annotation.
- Captures the network name back off the annotated functions into an
EngineRenderFn.networkName field.
- On every subsequent render, manually applies the captured network annotation to any function we haven't already started (mimicking upstream's unexported
injectNetworkAnnotation) before calling StartFunctionRuntimes.
- Tracks started function names in a
startedNames set and accumulates returned *FunctionAddresses in fnAddrsList so Cleanup can stop them all.
This works but couples us to internal state of dockerRenderEngine (the network name) via an annotation side-channel. The fix belongs upstream — we're carrying the workaround as a stop-gap until then.
How could Crossplane help solve your problem?
Wait for upstream crossplane/cli#96 to land a clean API (either an idempotent Setup or a new Engine.AnnotateFunctions), then delete the workaround.
Dependency map / ordering
crossplane/cli#96 (this issue's upstream peer)
│
├── lands in a release (e.g. v2.4.x)
│
└── crossplane-diff: bump go.mod to that release
│
└── crossplane-diff: this issue — delete the workaround
crossplane/cli#65 (don't-overwrite + pre-set network) and crossplane-contrib/crossplane-diff#255 (CROSSPLANE_DIFF_DOCKER_NETWORK env var) are independent of this work and don't need to land first; they coexist with our current workaround.
What to delete when unwinding
In cmd/diff/diffprocessor/render_engine.go:
EngineRenderFn.networkName field
EngineRenderFn.startedNames field (potentially — if upstream gives us a way to dedup naturally, e.g. idempotent StartFunctionRuntimes)
EngineRenderFn.fnAddrsList slice (if upstream lets us merge into a single accumulating *FunctionAddresses, otherwise keep)
firstNetworkAnnotation helper
applyNetworkAnnotation helper
- The "Multi-composition note" docstring on
EngineRenderFn
- The annotate-on-subsequent-render branch in
Render
Replace with whichever shape upstream lands:
- If Option A (idempotent
Setup): just call engine.Setup(ctx, newFns) every render. Keep fnAddrsList accumulation if StartFunctionRuntimes still returns separate *FunctionAddresses per call.
- If Option B (
Engine.AnnotateFunctions): call engine.Setup(ctx, fns) once, engine.AnnotateFunctions(newFns) thereafter, then StartFunctionRuntimes.
In cmd/diff/diffprocessor/render_engine_test.go:
- Keep
TestEngineRenderFn_MultiCompositionFunctionSet, TestEngineRenderFn_PreservesExistingNetworkAnnotation, and TestEngineRenderFn_CleanupStopsAllFunctionAddresses — their assertions are about caller-visible behavior and should hold against the new implementation. Update mocks if the upstream MockEngine shape changes.
Acceptance
🤖 Generated with Claude Code
What problem are you facing?
PR #326 introduces a self-contained workaround in
cmd/diff/diffprocessor/render_engine.goto support multi-composition rendering in a singlexrinvocation (e.g. diffing a GitOps directory whose XRs resolve to differentCompositionobjects with overlapping-but-not-identical function pipelines).The workaround:
engine.Setupexactly once on the first render. Upstream'sdockerRenderEngine.Setupcreates a fresh Docker network and stamps the first batch of functions with therender.AnnotationKeyRuntimeDockerNetworkannotation.EngineRenderFn.networkNamefield.injectNetworkAnnotation) before callingStartFunctionRuntimes.startedNamesset and accumulates returned*FunctionAddressesinfnAddrsListsoCleanupcan stop them all.This works but couples us to internal state of
dockerRenderEngine(the network name) via an annotation side-channel. The fix belongs upstream — we're carrying the workaround as a stop-gap until then.How could Crossplane help solve your problem?
Wait for upstream crossplane/cli#96 to land a clean API (either an idempotent
Setupor a newEngine.AnnotateFunctions), then delete the workaround.Dependency map / ordering
crossplane/cli#65 (don't-overwrite + pre-set network) and crossplane-contrib/crossplane-diff#255 (
CROSSPLANE_DIFF_DOCKER_NETWORKenv var) are independent of this work and don't need to land first; they coexist with our current workaround.What to delete when unwinding
In
cmd/diff/diffprocessor/render_engine.go:EngineRenderFn.networkNamefieldEngineRenderFn.startedNamesfield (potentially — if upstream gives us a way to dedup naturally, e.g. idempotentStartFunctionRuntimes)EngineRenderFn.fnAddrsListslice (if upstream lets us merge into a single accumulating*FunctionAddresses, otherwise keep)firstNetworkAnnotationhelperapplyNetworkAnnotationhelperEngineRenderFnRenderReplace with whichever shape upstream lands:
Setup): just callengine.Setup(ctx, newFns)every render. KeepfnAddrsListaccumulation ifStartFunctionRuntimesstill returns separate*FunctionAddressesper call.Engine.AnnotateFunctions): callengine.Setup(ctx, fns)once,engine.AnnotateFunctions(newFns)thereafter, thenStartFunctionRuntimes.In
cmd/diff/diffprocessor/render_engine_test.go:TestEngineRenderFn_MultiCompositionFunctionSet,TestEngineRenderFn_PreservesExistingNetworkAnnotation, andTestEngineRenderFn_CleanupStopsAllFunctionAddresses— their assertions are about caller-visible behavior and should hold against the new implementation. Update mocks if the upstreamMockEngineshape changes.Acceptance
EngineRenderFnno longer carries the captured-network workaround state or helpers..requirements/20260609T220505Z_multi_composition_render/REQUIREMENTS.mdarchived or updated to reflect the unwind.🤖 Generated with Claude Code