Skip to content

feat(claimtoxr): Add new subcommand resource convert claim-to-xr#8

Closed
tampakrap wants to merge 27 commits into
crossplane:mainfrom
tampakrap:theo/feat_claimtoxr
Closed

feat(claimtoxr): Add new subcommand resource convert claim-to-xr#8
tampakrap wants to merge 27 commits into
crossplane:mainfrom
tampakrap:theo/feat_claimtoxr

Conversation

@tampakrap
Copy link
Copy Markdown
Collaborator

Description of your changes

Introduce a Claim to XR converter: crossplane resource convert claim-to-xr. Similarly to other converter functions/tools, it takes a Claim either on stdin or as file argument and generates an equivalent XR YAML.

The parent crossplane resource convert is marked as maturity "alpha", which applies to the subtree as well.

This new tool can be used together with the render command as shown in the example below:

crossplane render <(crossplane resource convert claim-to-xr claim.yaml) composition.yaml functions.yaml

The library function ConvertClaimToXR can be used by downstream tools (eg crossplane-diff) that rely on Claim to XR conversion.

The tool (and as a result the converter function, via the Options struct) is configurable and provides the following flags:

  • --direct: omit spec.claimRef and the equivalent labels.
  • --name: specify an explicit XR name, skipping either the random suffix in the non-direct mode or the Claim name fallback in direct mode.
  • --kind: override the derived "X" default.
  • --gen-uid: populate metadata.uid with a fresh random UUID

I didn't create new docs issue/PR, we can use crossplane/docs#1088

I have:

Need help with this checklist? See the cheat sheet.

adamwg and others added 27 commits April 17, 2026 15:50
This commit adds build infrastructure for the standalone CLI repository, with a
stub cmd/crossplane. We'll add the existing crank code from crossplane in a
subsequent commit.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
This is all copied from crossplane/crossplane and updated to remove the parts we
don't need (e.g., pushing container images).

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
This commit contains the current `cmd/crank` from c/c and the supporting
`internal/docker` package, with imports updated as necessary.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
The CLI's version will not be guaranteed to match a Crossplane version going
forward, so we can't use the CLI's version number as the default image tag for
render. Use the `stable` tag instead, so that we always use the latest stable
Crossplane.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
We'll fill out more content in the README later, but bootstrap it for now.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
We don't actually have any fuzz tests yet, and this job relies on some
configuration existing in the google oss-fuzz repository.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Copy Crossplane's coderabbit config as a starting point and remove irrelevant
parts.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Historically, we've introduced new commands in the `crossplane alpha` or
`crossplane beta` trees and moved them as they matured. This makes it awkward to
introduce new commands in existing trees, since the tree gets split across
maturity levels, and also breaks users when commands mature since their
invocation changes.

Move all the existing alpha and beta commands to the top level (or into the
right tree, in the case of `crossplane render op`). Start indicating maturity
using kong tags. Alpha and beta features are hidden from help by default but can
be enabled by configuration and have a maturity indicator added to their help
automatically.

Make `crossplane render xr` the default subcommand for `crossplane render` so
that existing render users are not broken by this change.

Fixes crossplane/crossplane#7309

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Now that we have a config file, we need commands to manage it. Add basic
`crossplane config view` and `crossplane config set` commands so we can view and
update the config.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
This repository has additional maintainers beyond the
@crossplane/crossplane-maintainers group.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
The protos in this repository are vendored from crossplane/crossplane, so no
need to lint them or push them to the Buf schema registry. This avoids the need
for a `BUF_TOKEN` secret in this repo.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Add coderabbit config and clean up configuration
As proposed in the DevEx design doc, reorganize our command tree so that it's
noun-centric. Leave `crossplane render` as an alias for `crossplane xr render`
since that command is GA and we don't want to break any users/scripts. Allow
alpha and beta commands to move without aliases.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Show only the top-level nouns in the help. The user can use the `--help` flag on
any of these commands to see the subcommands. This will help keep our help tidy
and readable as we expand functionality.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
In keeping with how Crossplane handles pre-GA features, enable beta features by
default (and have a config option to disable them), but leave alpha features
disabled by default (with a config option to enable).

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Reorganize the command tree and introduce config
Export the entire nixpkgs-unstable rather than just its go package so that our
nix setup can use arbitrary packages from unstable as needed.

Matches the change in crossplane/crossplane commit 744190edf.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Upcoming developer experience changes require golang 1.26. Do the update
separately and resolve new lint issues so that we don't mix the changes into the
bigger DevEx PR.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
We use the function protobufs in the function we implement for injecting and
extracting context during render. When this code lived in crossplane/crossplane
it made sense to use the protobufs from there, but now that we're separate it
would be better to use the versions in function-sdk-go like a normal function.

This removes our dependency on crossplane/crossplane, which is nice.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Use function protobufs from function-sdk-go
Introduce a Claim to XR converter: `crossplane resource convert
claim-to-xr`. Similarly to other converter functions/tools, it takes a
Claim either on stdin or as file argument and generates an equivalent XR
YAML.

The parent `crossplane resource convert` is marked as maturity "alpha",
which applies to the subtree as well.

This new tool can be used together with the render command as shown in
the example below:

```bash
crossplane render <(crossplane resource convert claim-to-xr claim.yaml) composition.yaml functions.yaml
```

The library function ConvertClaimToXR can be used by downstream tools
(eg crossplane-diff) that rely on Claim to XR conversion.

The tool (and as a result the converter function, via the Options
struct) is configurable and provides the following flags:
- `--direct`: omit spec.claimRef and the equivalent labels.
- `--name`: specify an explicit XR name, skipping either the random
  suffix in the non-direct mode or the Claim name fallback in direct
  mode.
- `--kind`: override the derived "X<Kind>" default.
- `--gen-uid`: populate metadata.uid with a fresh random UUID

Signed-off-by: Theo Chatzimichos <tampakrap@gmail.com>
@tampakrap tampakrap requested review from a team and jcogilvie as code owners May 10, 2026 09:07
@tampakrap tampakrap removed the request for review from a team May 10, 2026 09:07
@tampakrap tampakrap requested review from adamwg and bobh66 May 10, 2026 09:07
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new resource convert claim-to-xr CLI command that transforms Crossplane Claim objects into Composite Resources (XRs). It includes core conversion logic supporting custom naming, kind derivation, optional claim linking, and UID generation, integrated into the CLI hierarchy and covered by extensive table-driven tests.

Changes

Claim-to-XR Conversion Feature

Layer / File(s) Summary
Conversion Options Contract
cmd/crossplane/resource/convert/claimtoxr/converter.go
Options struct defines XR naming (Name, Kind), linking behavior (Direct), and UID generation (GenerateUID). Error message constants for validation and linking are introduced.
Claim-to-XR Converter
cmd/crossplane/resource/convert/claimtoxr/converter.go
ConvertClaimToXR function validates input (kind, apiVersion, spec), creates a new XR, assigns apiVersion/kind from options, copies spec, generates or applies explicit XR names with suffixes, sets spec.claimRef for non-direct mode, and optionally generates a new UUID.
CLI Command Implementation
cmd/crossplane/resource/convert/claimtoxr/cmd.go
Cmd struct with arguments/flags for input file, output file, XR name, kind, direct mode, and UID generation. Run() reads Claim YAML, calls ConvertClaimToXR, marshals result to YAML with document header, and writes to stdout or file. AfterApply() initializes filesystem.
Convert Subcommand Wiring
cmd/crossplane/resource/convert/convert.go, cmd/crossplane/resource/resource.go
convert.Cmd wraps claimtoxr.Cmd as a claim-to-xr subcommand; wired into resource command group via import and struct field with kong tags.
Test Coverage
cmd/crossplane/resource/convert/claimtoxr/converter_test.go
Test helpers build Claims with custom metadata/spec. TestConvertClaimToXR table-driven test validates input errors, name generation, kind overrides, claim linking, label/annotation handling, spec field type preservation, and UID generation using custom cmp comparators for generated fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Important

Pre-merge checks failed

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

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Feature Gate Requirement ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new subcommand for converting Claims to XRs within the resource convert command hierarchy.
Description check ✅ Passed The description is directly related to the changeset, providing context about the Claim-to-XR converter, its usage, configuration options, and testing approach.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Breaking Changes ✅ Passed PR only adds new optional public types/functions to cmd/. No files removed, renamed, or modified. All new fields have zero-value defaults. No breaking changes detected.

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
cmd/crossplane/resource/convert/claimtoxr/cmd.go (1)

111-113: ⚡ Quick win

Consider adding more context to the conversion error.

While this error message is functional, it could be more helpful by including what kind of resource conversion was attempted. This helps users understand the context when troubleshooting.

💬 Suggested improvement
 	xr, err := ConvertClaimToXR(claim, Options{
 		Name:        c.Name,
 		Kind:        c.Kind,
 		Direct:      c.Direct,
 		GenerateUID: c.GenUID,
 	})
 	if err != nil {
-		return errors.Wrap(err, "failed to convert Claim to XR")
+		return errors.Wrapf(err, "failed to convert Claim %s/%s to Composite Resource", claim.GetNamespace(), claim.GetName())
 	}
🤖 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/resource/convert/claimtoxr/cmd.go` around lines 111 - 113,
Update the error wrapping around errors.Wrap(err, "failed to convert Claim to
XR") to include concrete resource context (e.g., the Claim's kind, name and
namespace or the target XR kind) so logs show what conversion failed; locate the
error call (errors.Wrap) in the Claim-to-XR conversion function and augment the
message to include identifiers (for example using claim.GetName(),
claim.GetNamespace(), claim.GetKind() or the target XR type variable) so the
returned wrapped error reads something like "failed to convert Claim
<kind>/<namespace>/<name> to XR <targetKind>".
cmd/crossplane/resource/convert/claimtoxr/converter.go (1)

136-156: 💤 Low value

Consider validating name length constraints.

Based on the external context, Crossplane has constraints on XR names (claim names cannot be longer than 57 characters to allow for the suffix). Should the conversion function validate this to provide a better error message, or is it acceptable to let Kubernetes validation catch this later?

This is more of a UX question - failing early with a clear message vs. letting it fail during apply. What's the team's preference?

🤖 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/resource/convert/claimtoxr/converter.go` around lines 136 -
156, Validate XR name length before generation and before accepting an explicit
name: when not opts.Direct (i.e., you will call
names.SimpleNameGenerator.GenerateName using claimName+"-") check that
len(claimName) <= 57 and return a clear error from the converter function if
it’s too long; also validate opts.Name when provided (the explicit override) to
ensure it meets Kubernetes resource name limits (e.g., <= 63 chars) and return a
descriptive error if it exceeds the limit. Update the logic around xrName,
opts.Direct, and opts.Name (and any callers that set
labels[labelClaimName]/labelClaimNamespace or call xrPaved.SetValue) to perform
these checks before generating or assigning xrName so the function fails early
with a helpful message.
🤖 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/resource/convert/claimtoxr/cmd.go`:
- Around line 100-102: The YAML unmarshal error uses a technical message
("Unmarshalling Error"); update the error returned when calling
yaml.Unmarshal(claimData, claim) to a user-facing message such as "failed to
parse Claim YAML: the input file does not contain valid YAML; please check the
file format and syntax" and wrap the original error for internal debugging
(e.g., annotate it) so callers get actionable guidance while preserving the
underlying error details from yaml.Unmarshal for logs/troubleshooting.

---

Nitpick comments:
In `@cmd/crossplane/resource/convert/claimtoxr/cmd.go`:
- Around line 111-113: Update the error wrapping around errors.Wrap(err, "failed
to convert Claim to XR") to include concrete resource context (e.g., the Claim's
kind, name and namespace or the target XR kind) so logs show what conversion
failed; locate the error call (errors.Wrap) in the Claim-to-XR conversion
function and augment the message to include identifiers (for example using
claim.GetName(), claim.GetNamespace(), claim.GetKind() or the target XR type
variable) so the returned wrapped error reads something like "failed to convert
Claim <kind>/<namespace>/<name> to XR <targetKind>".

In `@cmd/crossplane/resource/convert/claimtoxr/converter.go`:
- Around line 136-156: Validate XR name length before generation and before
accepting an explicit name: when not opts.Direct (i.e., you will call
names.SimpleNameGenerator.GenerateName using claimName+"-") check that
len(claimName) <= 57 and return a clear error from the converter function if
it’s too long; also validate opts.Name when provided (the explicit override) to
ensure it meets Kubernetes resource name limits (e.g., <= 63 chars) and return a
descriptive error if it exceeds the limit. Update the logic around xrName,
opts.Direct, and opts.Name (and any callers that set
labels[labelClaimName]/labelClaimNamespace or call xrPaved.SetValue) to perform
these checks before generating or assigning xrName so the function fails early
with a helpful message.
🪄 Autofix (Beta)

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

Run ID: 2363730b-0695-45f3-8c36-c05a53e9a7cd

📥 Commits

Reviewing files that changed from the base of the PR and between eb4d56a and 849cf1f.

📒 Files selected for processing (5)
  • cmd/crossplane/resource/convert/claimtoxr/cmd.go
  • cmd/crossplane/resource/convert/claimtoxr/converter.go
  • cmd/crossplane/resource/convert/claimtoxr/converter_test.go
  • cmd/crossplane/resource/convert/convert.go
  • cmd/crossplane/resource/resource.go

Comment on lines +100 to +102
if err := yaml.Unmarshal(claimData, claim); err != nil {
return errors.Wrap(err, "Unmarshalling Error")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Improve error message to be more user-friendly.

The error message "Unmarshalling Error" uses technical jargon that may not be meaningful to end users. CLI error messages should explain what went wrong from the user's perspective and ideally suggest next steps. As per coding guidelines, CLI error messages must avoid internal error details and provide actionable guidance.

Consider a message like:

"failed to parse Claim YAML: the input file does not contain valid YAML. Please check the file format and syntax"
💬 Suggested improvement
 	claim := &unstructured.Unstructured{}
 	if err := yaml.Unmarshal(claimData, claim); err != nil {
-		return errors.Wrap(err, "Unmarshalling Error")
+		return errors.Wrap(err, "failed to parse Claim YAML: the input file does not contain valid YAML")
 	}
🤖 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/resource/convert/claimtoxr/cmd.go` around lines 100 - 102, The
YAML unmarshal error uses a technical message ("Unmarshalling Error"); update
the error returned when calling yaml.Unmarshal(claimData, claim) to a
user-facing message such as "failed to parse Claim YAML: the input file does not
contain valid YAML; please check the file format and syntax" and wrap the
original error for internal debugging (e.g., annotate it) so callers get
actionable guidance while preserving the underlying error details from
yaml.Unmarshal for logs/troubleshooting.

@tampakrap
Copy link
Copy Markdown
Collaborator Author

I have created a branch for crossplane-diff in order to showcase how it would look like when using this tool https://github.com/crossplane-contrib/crossplane-diff/compare/main...tampakrap:crossplane-diff:theo/use_convertclaimtoxr_from_xprin?expand=1

(I am using xprin-helpers convert-claim-to-xr which is identical, since the crossplane/cli is not a dep of diff yet and it will require some work to do that)

@tampakrap
Copy link
Copy Markdown
Collaborator Author

tampakrap commented May 10, 2026

I chose crossplane resource convert claim-to-xr as I plan to introduce in a followup PR crossplane resource patch-xr. I am open to suggestions though.

@tampakrap tampakrap requested a review from haarchri May 11, 2026 11:08
// Flags.
OutputFile string `help:"The file to write the generated XR YAML to. If not specified, stdout will be used." placeholder:"PATH" predictor:"file" short:"o" type:"path"`
Name string `help:"The name to use for the XR. If empty, defaults to the Claim's name (direct mode) or the Claim's name with a random suffix (non-direct)." placeholder:"NAME" type:"string"`
Kind string `help:"The kind to use for the XR. If not specified, 'X' will be prepended to the Claim's kind (e.g. Infra -> XInfra)." placeholder:"KIND" type:"string"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

for me it feels safer to have the corresponding XRD as input - so i don't need to guess the Kind

output = f
}

outputW := bufio.NewWriter(output)
Copy link
Copy Markdown
Member

@haarchri haarchri May 11, 2026

Choose a reason for hiding this comment

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

compared to #7 we should use afero.WriteFile(...)

}

// ConvertClaimToXR converts a Crossplane Claim to a Composite Resource (XR).
func ConvertClaimToXR(claim *unstructured.Unstructured, opts Options) (*composite.Unstructured, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this to be exported ?, yes if we would switch it to internal folder - and thats what i would do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we want this exposed as an API so downstream folks (e.g. diff) can use it w/o having to invoke the CLI. (See the example)


// Cmd contains commands for working with Crossplane resources.
type Cmd struct {
Convert convert.Cmd `cmd:"" help:"Convert a Crossplane resource to a different kind." maturity:"alpha"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Convert convert.Cmd `cmd:"" help:"Convert a Crossplane resource to a different kind." maturity:"alpha"`
Convert convert.Cmd `cmd:"" help:"Convert a Crossplane resource to a different kind (e.g. Claim to XR)." maturity:"alpha"`

* Claim -> Composite Resource (XR)

Examples:
# Convert a Claim YAML file to an XR YAML file.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we add a real example for what this command is helpful?

  # Render a Composition using a converted Claim as the XR input.
  crossplane render <(crossplane resource convert claim-to-xr claim.yaml) composition.yaml functions.yaml

// Set XR kind - either from opts or by prepending X to Claim's kind
kind := opts.Kind
if kind == "" {
kind = "X" + claimKind
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lets check my comment above regarding XRD input

@haarchri
Copy link
Copy Markdown
Member

@tampakrap regarding your comment:

I chose crossplane resource convert claim-to-xr as I plan to introduce in a followup PR crossplane resource patch-xr. I am open to suggestions though.

i would lean toward grouping these under xr as the noun:

crossplane xr generate claim.yaml -o xr.yaml
crossplane xr patch xr.yaml ...

crossplane render xr.yaml composition.yaml function.yaml
crossplane render <(crossplane xr generate claim.yaml) composition.yaml functions.yaml

  • XR is the first-class object in v2, making it the noun matches the mental model
  • Mirrors the noun-grouped pattern we already use since changed
  • No hyphenated subcommands

Wdyt?

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.

4 participants