From 7caefa16cd3629711db2bf1bf402ef323139e4e3 Mon Sep 17 00:00:00 2001 From: Chaitanya Maili Date: Sat, 13 Jun 2026 22:29:27 +0530 Subject: [PATCH] fix(render): support repeating --required-resources and --extra-resources The flags were declared as `string` instead of `[]string`, so Kong's CLI parser silently overwrote the value on each repetition, keeping only the last occurrence. Resources from all earlier flags were dropped with no error or warning. Change both flags to `[]string` and loop over the slice in Run(), merging resources from every provided path into a single slice. The deprecated --extra-resources flag in `render xr` is fixed in the same way. Fixes https://github.com/crossplane/cli/issues/90 Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Chaitanya Maili --- cmd/crossplane/render/op/cmd.go | 9 ++++---- cmd/crossplane/render/op/cmd_test.go | 17 +++++++++++++- cmd/crossplane/render/xr/cmd.go | 21 ++++++++--------- cmd/crossplane/render/xr/cmd_test.go | 34 +++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/cmd/crossplane/render/op/cmd.go b/cmd/crossplane/render/op/cmd.go index 04e6bc93..3b72d274 100644 --- a/cmd/crossplane/render/op/cmd.go +++ b/cmd/crossplane/render/op/cmd.go @@ -72,7 +72,7 @@ type Cmd struct { IncludeContext bool `help:"Include the context in the rendered output as a resource of kind: Context." short:"c"` IncludeFullOperation bool `help:"Include a direct copy of the input Operation's spec and metadata fields in the rendered output." short:"o"` IncludeFunctionResults bool `help:"Include informational and warning messages from functions in the rendered output as resources of kind: Result." short:"r"` - RequiredResources string `help:"A YAML file or directory of YAML files specifying required resources to pass to the function pipeline." placeholder:"PATH" predictor:"yaml_file_or_directory" short:"e" type:"path"` + RequiredResources []string `help:"A YAML file or directory of YAML files specifying required resources to pass to the function pipeline. Provide multiple files by repeating the argument." placeholder:"PATH" predictor:"yaml_file_or_directory" short:"e" type:"path"` RequiredSchemas string `help:"A directory of JSON files specifying OpenAPI schemas to pass to the function pipeline." placeholder:"DIR" predictor:"directory" type:"path"` WatchedResource string `help:"A YAML file specifying the watched resource for WatchOperation rendering. The resource is also added to required resources." placeholder:"PATH" predictor:"yaml_file" short:"w" type:"existingfile"` @@ -113,11 +113,12 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte // Load required resources rrs := []unstructured.Unstructured{} - if c.RequiredResources != "" { - rrs, err = render.LoadRequiredResources(c.fs, c.RequiredResources) + for _, path := range c.RequiredResources { + loaded, err := render.LoadRequiredResources(c.fs, path) if err != nil { - return errors.Wrapf(err, "cannot load required resources from %q", c.RequiredResources) + return errors.Wrapf(err, "cannot load required resources from %q", path) } + rrs = append(rrs, loaded...) } // Load required schemas diff --git a/cmd/crossplane/render/op/cmd_test.go b/cmd/crossplane/render/op/cmd_test.go index f17ec0c7..f88e79e7 100644 --- a/cmd/crossplane/render/op/cmd_test.go +++ b/cmd/crossplane/render/op/cmd_test.go @@ -188,13 +188,28 @@ func TestCmdRun(t *testing.T) { cmd: Cmd{ Operation: "operation.yaml", Functions: "functions.yaml", - RequiredResources: "missing.yaml", + RequiredResources: []string{"missing.yaml"}, Timeout: time.Minute, fs: newTestFS(nil), }, }, want: want{err: cmpopts.AnyError}, }, + "MultipleRequiredResourcesFirstMissing": { + reason: "An error loading the first of multiple --required-resources files should propagate, not be silently dropped.", + args: args{ + cmd: Cmd{ + Operation: "operation.yaml", + Functions: "functions.yaml", + RequiredResources: []string{"missing.yaml", "resources-b.yaml"}, + Timeout: time.Minute, + fs: newTestFS(map[string]string{ + "resources-b.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: resource-b\n", + }), + }, + }, + want: want{err: cmpopts.AnyError}, + }, "LoadRequiredSchemasError": { reason: "Missing required schemas directory should return a wrapped load error.", args: args{ diff --git a/cmd/crossplane/render/xr/cmd.go b/cmd/crossplane/render/xr/cmd.go index b7543a6c..10a45963 100644 --- a/cmd/crossplane/render/xr/cmd.go +++ b/cmd/crossplane/render/xr/cmd.go @@ -77,8 +77,8 @@ type Cmd struct { IncludeFunctionResults bool `help:"Include informational and warning messages from Functions in the rendered output as resources of kind: Result." short:"r"` IncludeFullXR bool `help:"Include a direct copy of the input XR's spec and metadata fields in the rendered output." short:"x"` ObservedResources string `help:"A YAML file or directory of YAML files specifying the observed state of composed resources." placeholder:"PATH" predictor:"yaml_file_or_directory" short:"o" type:"path"` - ExtraResources string `help:"A YAML file or directory of YAML files specifying required resources (deprecated, use --required-resources)." placeholder:"PATH" predictor:"yaml_file_or_directory" type:"path"` - RequiredResources string `help:"A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline." placeholder:"PATH" predictor:"yaml_file_or_directory" short:"e" type:"path"` + ExtraResources []string `help:"A YAML file or directory of YAML files specifying required resources (deprecated, use --required-resources). Provide multiple files by repeating the argument." placeholder:"PATH" predictor:"yaml_file_or_directory" type:"path"` + RequiredResources []string `help:"A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline. Provide multiple files by repeating the argument." placeholder:"PATH" predictor:"yaml_file_or_directory" short:"e" type:"path"` RequiredSchemas string `help:"A directory of JSON files specifying OpenAPI v3 schemas (from kubectl get --raw /openapi/v3/)." placeholder:"DIR" predictor:"directory" short:"s" type:"path"` IncludeContext bool `help:"Include the context in the rendered output as a resource of kind: Context." short:"c"` FunctionCredentials string `help:"A YAML file or directory of YAML files specifying credentials to use for Functions to render the XR." placeholder:"PATH" predictor:"yaml_file_or_directory" type:"path"` @@ -194,21 +194,20 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte } rrs := []unstructured.Unstructured{} - if c.RequiredResources != "" { - rrs, err = render.LoadRequiredResources(c.fs, c.RequiredResources) + for _, path := range c.RequiredResources { + loaded, err := render.LoadRequiredResources(c.fs, path) if err != nil { - return errors.Wrapf(err, "cannot load required resources from %q", c.RequiredResources) + return errors.Wrapf(err, "cannot load required resources from %q", path) } + rrs = append(rrs, loaded...) } - if c.ExtraResources != "" { - ers, err := render.LoadRequiredResources(c.fs, c.ExtraResources) + for _, path := range c.ExtraResources { + loaded, err := render.LoadRequiredResources(c.fs, path) if err != nil { - return errors.Wrapf(err, "cannot load extra resources from %q", c.ExtraResources) + return errors.Wrapf(err, "cannot load extra resources from %q", path) } - - // Merge extra resources into required resources. - rrs = append(rrs, ers...) + rrs = append(rrs, loaded...) } // Load required schemas diff --git a/cmd/crossplane/render/xr/cmd_test.go b/cmd/crossplane/render/xr/cmd_test.go index d57cc14a..ec8494ee 100644 --- a/cmd/crossplane/render/xr/cmd_test.go +++ b/cmd/crossplane/render/xr/cmd_test.go @@ -348,13 +348,45 @@ func TestCmdRun(t *testing.T) { CompositeResource: "xr.yaml", Composition: "composition.yaml", Functions: "functions.yaml", - RequiredResources: "missing.yaml", + RequiredResources: []string{"missing.yaml"}, Timeout: time.Minute, fs: newTestFS(nil), }, }, want: want{err: cmpopts.AnyError}, }, + "MultipleRequiredResourcesFirstMissing": { + reason: "An error loading the first of multiple --required-resources files should propagate, not be silently dropped.", + args: args{ + cmd: Cmd{ + CompositeResource: "xr.yaml", + Composition: "composition.yaml", + Functions: "functions.yaml", + RequiredResources: []string{"missing.yaml", "resources-b.yaml"}, + Timeout: time.Minute, + fs: newTestFS(map[string]string{ + "resources-b.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: resource-b\n", + }), + }, + }, + want: want{err: cmpopts.AnyError}, + }, + "MultipleExtraResourcesFirstMissing": { + reason: "An error loading the first of multiple --extra-resources files should propagate, not be silently dropped.", + args: args{ + cmd: Cmd{ + CompositeResource: "xr.yaml", + Composition: "composition.yaml", + Functions: "functions.yaml", + ExtraResources: []string{"missing.yaml", "resources-b.yaml"}, + Timeout: time.Minute, + fs: newTestFS(map[string]string{ + "resources-b.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: resource-b\n", + }), + }, + }, + want: want{err: cmpopts.AnyError}, + }, "LoadRequiredSchemasError": { reason: "Missing required schemas directory should return a wrapped load error.", args: args{