Skip to content

feat: allow composition render subcommand read from configuration pkg file - #252

Open
fernandezcuesta wants to merge 2 commits into
crossplane:mainfrom
fernandezcuesta:251--composition-render-from-pkgmeta
Open

feat: allow composition render subcommand read from configuration pkg file#252
fernandezcuesta wants to merge 2 commits into
crossplane:mainfrom
fernandezcuesta:251--composition-render-from-pkgmeta

Conversation

@fernandezcuesta

Copy link
Copy Markdown
Contributor

Description of your changes

Allow functions to be read directly from a configuration file (crossplane.yaml dependsOn).

Fixes #251

I have:

Need help with this checklist? See the cheat sheet.

@fernandezcuesta
fernandezcuesta force-pushed the 251--composition-render-from-pkgmeta branch from 6f7a72c to f61fb12 Compare August 6, 2026 15:20
…kage metadata file

Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com>
@fernandezcuesta
fernandezcuesta force-pushed the 251--composition-render-from-pkgmeta branch from f61fb12 to 27f0d48 Compare August 6, 2026 15:22
Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com>
@fernandezcuesta
fernandezcuesta force-pushed the 251--composition-render-from-pkgmeta branch from f300a4d to ac7dc77 Compare August 6, 2026 15:31
@fernandezcuesta
fernandezcuesta marked this pull request as ready for review August 6, 2026 15:31
@fernandezcuesta
fernandezcuesta requested review from a team, jcogilvie and tampakrap as code owners August 6, 2026 15:31
@fernandezcuesta
fernandezcuesta requested review from haarchri and removed request for a team August 6, 2026 15:31
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The render command now loads Function dependencies from crossplane.yaml when no project file exists. It validates Configuration metadata, resolves Function packages, exposes --pkg-meta-file, and documents the new rendering workflow.

Changes

Configuration Function Resolution

Layer / File(s) Summary
Parse and resolve Configuration dependencies
internal/xpkg/configuration.go, internal/xpkg/configuration_test.go
Configuration metadata parsing validates YAML, API version, and kind. Function dependencies receive version constraints and resolve into package objects. Table-driven tests cover parsing and resolution cases.
Integrate Configuration loading into render
cmd/crossplane/render/xr/cmd.go, cmd/crossplane/render/xr/help/render.md
The render command adds PkgMetaFile, loads Functions when no project file exists, and reports contextual errors. The help text and example describe Configuration metadata usage and --pkg-meta-file.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RenderCommand
  participant ConfigurationMetadata
  participant Resolver
  RenderCommand->>ConfigurationMetadata: load crossplane.yaml
  ConfigurationMetadata-->>RenderCommand: return validated dependencies
  RenderCommand->>Resolver: resolve Function dependencies
  Resolver-->>RenderCommand: return Function packages
  RenderCommand->>RenderCommand: render Composition without functions file
Loading

Suggested reviewers: adamwg


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Feature Gate Requirement ❌ Error The PR adds automatic crossplane.yaml function resolution to GA composition render, but the new fallback has no feature flag or maturity gate in the changed code. Add a configuration-backed feature flag and check it before loadFunctionsFromConfiguration, or place the behavior behind an existing experimental maturity gate.
Title check ⚠️ Warning The title clearly describes the change but exceeds the 72-character limit by two characters. Shorten the title to 72 characters or fewer while preserving its description of configuration package support.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description explains configuration-based function loading, references issue #251, and records the added tests.
Linked Issues check ✅ Passed The changes implement issue #251 by reading function dependencies from crossplane.yaml and allowing the separate function file to be omitted.
Out of Scope Changes check ✅ Passed The code, documentation, and tests are directly related to configuration package function resolution for composition rendering.
Breaking Changes ✅ Passed No files under apis/** changed. cmd/** preserves existing public fields and flags; it adds optional --pkg-meta-file and extends fallback behavior without removing behavior.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/crossplane/render/xr/cmd.go`:
- Around line 89-92: Update the Kong help text for the positional Functions
argument in the XR render command to state that it is optional when either a
project file or Configuration metadata file supplies Function dependencies;
leave the surrounding flags unchanged.
- Around line 408-410: Update the project-file check in
cmd/crossplane/render/xr/cmd.go:408-410 to fall back to Configuration only when
os.IsNotExist(err) is true; wrap and return all other os.Stat errors. Apply the
same not-found-only condition at cmd/crossplane/render/xr/cmd.go:504-506 before
returning the Functions-argument error, and add coverage for a non-not-found
error.

In `@internal/xpkg/configuration_test.go`:
- Around line 35-93: Update the ParseConfiguration table-driven test to use args
and want structs, replacing expectErr with want.err and expected configuration
fields as needed. Compare the returned error against want.err using cmp.Diff
with cmpopts.EquateErrors(), while preserving the existing valid-name assertion
through the expected result structure.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: da74dba2-d730-424c-bbb5-ec0c00c92b71

📥 Commits

Reviewing files that changed from the base of the PR and between d267815 and ac7dc77.

📒 Files selected for processing (4)
  • cmd/crossplane/render/xr/cmd.go
  • cmd/crossplane/render/xr/help/render.md
  • internal/xpkg/configuration.go
  • internal/xpkg/configuration_test.go

Comment on lines +89 to +92
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
MaxConcurrency uint `default:"8" help:"Maximum concurrency for building embedded functions."`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to the project file. Optional." optional:"" predictor:"yaml_file" short:"f" type:"path"`
PkgMetaFile string `default:"crossplane.yaml" help:"Path to a package metadata file (crossplane.yaml). Used as fallback when no project file is found." name:"pkg-meta-file" optional:"" predictor:"yaml_file" type:"path"`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to the project file. Optional." optional:"" predictor:"yaml_file" short:"f" type:"path"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the positional argument help.

The Functions help text says that the argument is optional only in a project. It is also optional when the Configuration metadata file supplies Function dependencies. Update the Kong help text to describe both cases.

As per path instructions, “Review CLI commands for proper flag handling, help text, and error messages.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/render/xr/cmd.go` around lines 89 - 92, Update the Kong help
text for the positional Functions argument in the XR render command to state
that it is optional when either a project file or Configuration metadata file
supplies Function dependencies; leave the surrounding flags unchanged.

Source: Path instructions

Comment on lines 408 to 410
if _, err := os.Stat(projFilePath); err != nil {
return nil, errors.New("functions argument is required when not in a project")
return c.loadFunctionsFromConfiguration(ctx, log)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle non-not-found file errors before fallback.

os.Stat can return permission and I/O errors. The project-file branch treats these errors as a missing project and starts Configuration fallback. The Configuration branch then hides its own access failure with a Functions-argument error.

Only use fallback behavior when os.IsNotExist(err) is true. Return a wrapped access error for every other error. Add coverage for a non-not-found error.

  • cmd/crossplane/render/xr/cmd.go#L408-L410: fall back to Configuration only after a not-found project-file error.
  • cmd/crossplane/render/xr/cmd.go#L504-L506: return the Functions-argument error only after a not-found Configuration-file error.
Proposed fix
 if _, err := os.Stat(projFilePath); err != nil {
+  if !os.IsNotExist(err) {
+    return nil, errors.Wrapf(err, "cannot access project file %q", projFilePath)
+  }
   return c.loadFunctionsFromConfiguration(ctx, log)
 }

 if _, err := os.Stat(cfgFilePath); err != nil {
+  if !os.IsNotExist(err) {
+    return nil, errors.Wrapf(err, "cannot access configuration file %q", cfgFilePath)
+  }
   return nil, errors.New("functions argument is required when not in a project or configuration")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := os.Stat(projFilePath); err != nil {
return nil, errors.New("functions argument is required when not in a project")
return c.loadFunctionsFromConfiguration(ctx, log)
}
if _, err := os.Stat(projFilePath); err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "cannot access project file %q", projFilePath)
}
return c.loadFunctionsFromConfiguration(ctx, log)
}
Suggested change
if _, err := os.Stat(projFilePath); err != nil {
return nil, errors.New("functions argument is required when not in a project")
return c.loadFunctionsFromConfiguration(ctx, log)
}
if _, err := os.Stat(cfgFilePath); err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrapf(err, "cannot access configuration file %q", cfgFilePath)
}
return nil, errors.New("functions argument is required when not in a project or configuration")
}
📍 Affects 1 file
  • cmd/crossplane/render/xr/cmd.go#L408-L410 (this comment)
  • cmd/crossplane/render/xr/cmd.go#L504-L506
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/render/xr/cmd.go` around lines 408 - 410, Update the
project-file check in cmd/crossplane/render/xr/cmd.go:408-410 to fall back to
Configuration only when os.IsNotExist(err) is true; wrap and return all other
os.Stat errors. Apply the same not-found-only condition at
cmd/crossplane/render/xr/cmd.go:504-506 before returning the Functions-argument
error, and add coverage for a non-not-found error.

Comment on lines +35 to +93
tests := []struct {
name string
content string
expectErr bool
}{
{
name: "ValidConfiguration",
content: `
apiVersion: meta.pkg.crossplane.io/v1
kind: Configuration
metadata:
name: my-config
spec:
dependsOn:
- function: ghcr.io/example/function-a
version: "v1.0.0"
`,
},
{
name: "WrongAPIVersion",
content: "apiVersion: wrong.api/v1\nkind: Configuration\nspec: {}",
expectErr: true,
},
{
name: "WrongKind",
content: "apiVersion: meta.pkg.crossplane.io/v1\nkind: Provider\nspec: {}",
expectErr: true,
},
{
name: "InvalidYAML",
content: "not: valid: yaml: [",
expectErr: true,
},
{
name: "FileNotFound",
expectErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

fs := afero.NewMemMapFs()
if tt.content != "" {
if err := afero.WriteFile(fs, "/crossplane.yaml", []byte(tt.content), os.ModePerm); err != nil {
t.Fatal(err)
}
}

cfg, err := ParseConfiguration(fs, "/crossplane.yaml")
if (err != nil) != tt.expectErr {
t.Fatalf("ParseConfiguration() error = %v, expectErr %v", err, tt.expectErr)
}
if err == nil && cfg.Name != "my-config" {
t.Errorf("name = %q, want %q", cfg.Name, "my-config")
}
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 'cmpopts\.EquateErrors|cmp\.Diff' --glob '*_test.go'
rg -n 'github.com/google/go-cmp' go.mod

Repository: crossplane/cli

Length of output: 152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file =="
fd -a 'internal/xpkg/configuration_test.go' . || true

if [ -f internal/xpkg/configuration_test.go ]; then
  echo "== file outline =="
  ast-grep outline internal/xpkg/configuration_test.go --view expanded || true
  echo "== relevant lines =="
  cat -n internal/xpkg/configuration_test.go | sed -n '1,180p'
fi

echo "== go-cmp in module files =="
rg -n 'go-cmp|cmpopts\.EquateErrors|cmp\.Diff' --glob '*_test.go' --glob 'go.mod' --glob 'go.sum' . || true

echo "== current diff stat/name =="
git diff --stat || true
git diff -- internal/xpkg/configuration_test.go 2>/dev/null | sed -n '1,220p' || true

Repository: crossplane/cli

Length of output: 40017


Use the required args and want test structure.

ParseConfiguration covers error cases with expectErr, but the test should compare want.err against the returned error with cmp.Diff and cmpopts.EquateErrors(). ResolveConfigurationFunctions already uses args/want and cmp.Diff, and it has no returned errors, so no error comparison is needed there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/xpkg/configuration_test.go` around lines 35 - 93, Update the
ParseConfiguration table-driven test to use args and want structs, replacing
expectErr with want.err and expected configuration fields as needed. Compare the
returned error against want.err using cmp.Diff with cmpopts.EquateErrors(),
while preserving the existing valid-name assertion through the expected result
structure.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Omit function file in composition render when crossplane.yaml exists

1 participant