From 8b193e85f2def87646e9b636d4fa1c3b1d342a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Fern=C3=A1ndez?= <7312236+fernandezcuesta@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:13:55 +0200 Subject: [PATCH] fix: loaded XRD must honor the schmea MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com> --- cmd/crossplane/render/xr/cmd.go | 9 ++++ cmd/crossplane/render/xr/cmd_test.go | 46 +++++++++++++++++++ .../render/xr/testdata/cmd/xrd.yaml | 22 +++++++++ 3 files changed, 77 insertions(+) create mode 100644 cmd/crossplane/render/xr/testdata/cmd/xrd.yaml diff --git a/cmd/crossplane/render/xr/cmd.go b/cmd/crossplane/render/xr/cmd.go index b7543a6c..79265cfe 100644 --- a/cmd/crossplane/render/xr/cmd.go +++ b/cmd/crossplane/render/xr/cmd.go @@ -31,6 +31,7 @@ import ( "github.com/spf13/afero" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/kube-openapi/pkg/spec3" @@ -166,6 +167,7 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte return errors.Wrap(err, "cannot apply function annotation overrides") } + var xrdUnstructured *unstructured.Unstructured if c.XRD != "" { xrd, err := render.LoadXRD(c.fs, c.XRD) if err != nil { @@ -175,6 +177,12 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte if err := xrpkg.ApplyXRDDefaults(xr.GetUnstructured(), xrd); err != nil { return errors.Wrapf(err, "cannot apply XRD defaults to XR %q", xr.GetName()) } + + obj, err := kruntime.DefaultUnstructuredConverter.ToUnstructured(xrd) + if err != nil { + return errors.Wrapf(err, "cannot convert XRD %q to unstructured", xrd.GetName()) + } + xrdUnstructured = &unstructured.Unstructured{Object: obj} } fcreds := []corev1.Secret{} @@ -283,6 +291,7 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte RequiredResources: rrs, RequiredSchemas: rsc, FunctionCredentials: fcreds, + XRD: xrdUnstructured, } req, err := render.BuildCompositeRequest(in) if err != nil { diff --git a/cmd/crossplane/render/xr/cmd_test.go b/cmd/crossplane/render/xr/cmd_test.go index d57cc14a..94b1d0ee 100644 --- a/cmd/crossplane/render/xr/cmd_test.go +++ b/cmd/crossplane/render/xr/cmd_test.go @@ -78,6 +78,9 @@ var includeFunctionResultsOutput string //go:embed testdata/cmd/output/include-full-xr.yaml var includeFullXROutput string +//go:embed testdata/cmd/xrd.yaml +var xrdYAML string + func newEngineFunc(engine render.Engine) func(*render.EngineFlags, logging.Logger) render.Engine { return func(*render.EngineFlags, logging.Logger) render.Engine { return engine @@ -470,6 +473,49 @@ func TestCmdRun(t *testing.T) { }, want: want{err: cmpopts.AnyError}, }, + "XRDPassedToEngine": { + reason: "When --xrd is set, the XRD should be forwarded to the render engine so it can determine the composite schema (Legacy vs Modern).", + args: args{ + cmd: Cmd{ + CompositeResource: "xr.yaml", + Composition: "composition.yaml", + Functions: "functions.yaml", + XRD: "xrd.yaml", + Timeout: time.Minute, + fs: newTestFS(map[string]string{"xrd.yaml": xrdYAML}), + newEngine: newEngineFunc(&render.MockEngine{ + MockRender: func(_ context.Context, req *renderv1alpha1.RenderRequest) (*renderv1alpha1.RenderResponse, error) { + if req.GetComposite().GetCompositeResourceDefinition() == nil { + t.Error("expected render request to contain the XRD, got nil") + } + return &renderv1alpha1.RenderResponse{ + Output: &renderv1alpha1.RenderResponse_Composite{ + Composite: &renderv1alpha1.CompositeOutput{ + CompositeResource: fillResourceRefs(t, req.GetComposite().GetCompositeResource()), + ComposedResources: []*structpb.Struct{ + mustNewStruct(t, map[string]any{ + "apiVersion": "example.org/v1alpha1", + "kind": "ComposedResource", + "metadata": map[string]any{ + "name": "composed-foo", + "annotations": map[string]any{ + "crossplane.io/composition-resource-name": "composed-foo", + }, + }, + "spec": map[string]any{"coolField": "composed!"}, + }), + }, + }, + }, + }, nil + }, + }), + }, + }, + want: want{ + stdout: successOutput, + }, + }, "IncludeFullXR": { reason: "With --include-full-xr, the rendered XR is merged into the input XR so the input's spec.fromXR survives alongside any updated fields.", args: args{ diff --git a/cmd/crossplane/render/xr/testdata/cmd/xrd.yaml b/cmd/crossplane/render/xr/testdata/cmd/xrd.yaml new file mode 100644 index 00000000..1a558c09 --- /dev/null +++ b/cmd/crossplane/render/xr/testdata/cmd/xrd.yaml @@ -0,0 +1,22 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xnopresources.nop.example.org +spec: + group: nop.example.org + names: + kind: XNopResource + plural: xnopresources + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + coolField: + type: string