-
Notifications
You must be signed in to change notification settings - Fork 31
feat: add Rust support to Crossplane projects #374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jonasz-lasut
wants to merge
5
commits into
crossplane:main
Choose a base branch
from
jonasz-lasut:rust-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
8a6a64f
feat(schemas): generate Rust models for project schemas
jonasz-lasut 25d17c0
feat(function): scaffold Rust functions
jonasz-lasut 0852128
feat(project): build Rust functions
jonasz-lasut a44b48f
docs(rust): add testing guide for Rust support
jonasz-lasut ec20721
fix(rust): address review comments
jonasz-lasut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| target/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| [package] | ||
| name = "{{ .Name }}" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
| license = "Apache-2.0" | ||
| publish = false | ||
| description = "A Crossplane composition function." | ||
|
|
||
| # The function image runs this binary, whatever the package is called. | ||
| [[bin]] | ||
| name = "function" | ||
| path = "src/main.rs" | ||
|
|
||
| [dependencies] | ||
| function-sdk-rust = "0.3" | ||
| tonic = "0.14" | ||
| tokio = { version = "1", features = ["rt-multi-thread", "macros"] } | ||
| clap = { version = "4", features = ["derive", "env"] } | ||
| serde = { version = "1", features = ["derive"] } | ||
| serde_json = "1" | ||
| tracing = "0.1" | ||
| {{- if .HasSchemas }} | ||
| # Every generated model. To compile only the API groups this function imports, | ||
| # add default-features = false and list their features, which | ||
| # {{ .SchemasPath }}/Cargo.toml names. | ||
| crossplane-models = { path = "{{ .SchemasPath }}" } | ||
| {{- end }} | ||
|
|
||
| [lints.rust] | ||
| unsafe_code = "forbid" | ||
|
|
||
| [lints.clippy] | ||
| all = { level = "warn", priority = -1 } | ||
|
|
||
| # Function images should be small. | ||
| [profile.release] | ||
| strip = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # {{ .Name }} | ||
|
|
||
| A Crossplane composition function written in Rust with | ||
| [function-sdk-rust](https://github.com/crossplane/function-sdk-rust). | ||
|
|
||
| - Build: `cargo build` | ||
| - Test: `cargo test` | ||
| - Lint: `cargo clippy --all-targets -- -D warnings` | ||
| - Format: `cargo fmt` | ||
| - Run locally without mTLS: `cargo run -- --insecure` | ||
| - Package: `crossplane project build` from the project root | ||
|
|
||
| Cargo writes `Cargo.lock` on the first build. Commit it to have | ||
| `crossplane project build` resolve the same dependency versions every time: the | ||
| build uses the lock file when there is one. | ||
| {{- if .HasSchemas }} | ||
|
|
||
| Typed models for this project's XRDs and its dependencies are generated into | ||
| `{{ .SchemasPath }}` as the `crossplane-models` crate, which this function | ||
| depends on by path. Each API group and version is a module named after the | ||
| reversed group, and exports every type of that version. The kinds of | ||
| `platform.example.org/v1alpha1` are imported like this: | ||
|
|
||
| ```rust | ||
| use crossplane_models::org::example::platform::v1alpha1::{XBucket, XBucketSpec}; | ||
| ``` | ||
|
|
||
| The models are regenerated by `crossplane project build` and | ||
| `crossplane dependency add`. | ||
| {{- end }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # The toolchain for working on this function, with the components that format | ||
| # and lint it. crossplane project build compiles with the toolchain of its build | ||
| # image instead. | ||
| [toolchain] | ||
| channel = "stable" | ||
| components = ["clippy", "rustfmt"] |
84 changes: 84 additions & 0 deletions
84
cmd/crossplane/function/templates/rust/src/function.rs.tmpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| //! A Crossplane composition function. | ||
|
|
||
| use function_sdk_rust::proto::v1::function_runner_service_server::FunctionRunnerService; | ||
| use function_sdk_rust::proto::v1::{RunFunctionRequest, RunFunctionResponse}; | ||
| use function_sdk_rust::response; | ||
| use tonic::{Request, Response, Status}; | ||
|
|
||
| /// The composition function. | ||
| #[derive(Debug, Default)] | ||
| pub struct Function; | ||
|
|
||
| #[tonic::async_trait] | ||
| impl FunctionRunnerService for Function { | ||
| async fn run_function( | ||
| &self, | ||
| request: Request<RunFunctionRequest>, | ||
| ) -> Result<Response<RunFunctionResponse>, Status> { | ||
| let req = request.into_inner(); | ||
| let tag = req.meta.as_ref().map(|m| m.tag.clone()).unwrap_or_default(); | ||
| tracing::info!(tag, "running function"); | ||
|
|
||
| let mut rsp = response::to(&req, response::DEFAULT_TTL); | ||
|
|
||
| // Add your composition logic here. For example, read the observed | ||
| // composite resource with function_sdk_rust::resource::get, and compose | ||
| // desired resources by updating rsp.desired.resources with | ||
| // function_sdk_rust::resource::update. | ||
| {{- if .HasSchemas }} | ||
| // | ||
| // Both take any serde type, including the models generated for this | ||
| // project in the crossplane-models crate. A kind of | ||
| // platform.example.org/v1alpha1 is read like this: | ||
| // | ||
| // use crossplane_models::org::example::platform::v1alpha1::XBucket; | ||
| // | ||
| // let observed = req.observed.as_ref().and_then(|s| s.composite.as_ref()); | ||
| // let xr: XBucket = function_sdk_rust::resource::get(observed) | ||
| // .map_err(|e| Status::invalid_argument(e.to_string()))?; | ||
| {{- end }} | ||
|
|
||
| response::normal(&mut rsp, "Function completed successfully"); | ||
|
|
||
| Ok(Response::new(rsp)) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use function_sdk_rust::proto::v1::{Resource, State}; | ||
| use function_sdk_rust::resource; | ||
|
|
||
| #[tokio::test] | ||
| async fn responds_to_an_observed_composite_resource() { | ||
| let mut composite = Resource::default(); | ||
| resource::update( | ||
| &mut composite, | ||
| &serde_json::json!({ | ||
| "apiVersion": "example.crossplane.io/v1alpha1", | ||
| "kind": "Example", | ||
| "metadata": {"name": "example"}, | ||
| "spec": {}, | ||
| }), | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| let req = RunFunctionRequest { | ||
| observed: Some(State { | ||
| composite: Some(composite), | ||
| ..Default::default() | ||
| }), | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let rsp = Function | ||
| .run_function(Request::new(req)) | ||
| .await | ||
| .unwrap() | ||
| .into_inner(); | ||
|
|
||
| let messages: Vec<_> = rsp.results.iter().map(|r| r.message.as_str()).collect(); | ||
| assert_eq!(messages, ["Function completed successfully"]); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| //! The composition function's CLI entrypoint. | ||
|
|
||
| use clap::Parser; | ||
| use function_sdk_rust::{Args, logging, serve}; | ||
|
|
||
| mod function; | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> Result<(), function_sdk_rust::Error> { | ||
| let args = Args::parse(); | ||
| logging::configure(args.debug); | ||
| serve(function::Function, &args).await | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: crossplane/cli
Length of output: 6912
🏁 Script executed:
Repository: crossplane/cli
Length of output: 10841
🤖 get_repo_knowledge executed:
get_repo_knowledge crossplane/cli /tmp/coderabbit-repo-knowledge/crossplane-cli-9b32699c/architecture /tmp/coderabbit-repo-knowledge/crossplane-cli-9b32699c/conventions /tmp/coderabbit-repo-knowledge/crossplane-cli-9b32699c/learningsLength of output: 41149
Gate Rust schema generation for existing projects.
When
spec.schemas.languagesis omitted,GetLanguages()returnsnil, andgenerator.Filtertreats an empty list as all generators. SinceAllLanguages()now includes Rust, existing projects can generate Rust schemas on their next build or schema-generation run after upgrade. This changes generated artifacts and build behavior.Could you add a feature gate that defaults off, or require an explicit
rustentry until Rust generation is stable? Document the upgrade path for existing projects.🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to get a maintainer opinion on this, Rust models are generated natively, which makes the process quick and does not introduce any external dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonasz-lasut, thank you for the context. Native generation is a strong reason to avoid a feature gate for performance or dependency reasons.The concern is compatibility, not generation cost. Existing projects with omitted
spec.schemas.languagescurrently select all supported languages. Adding Rust changes their generated output after a CLI upgrade, including a newschemas/rustcrate.A maintainer decision is needed on the default-language contract:
languagesintentionally means “all languages available in this CLI version”, this change is valid. The documentation should state that upgrades can add generated language directories.languagesis expected to preserve existing generated artifacts, Rust should require an explicit"rust"entry until a future compatibility boundary.Could a maintainer confirm which contract Crossplane wants for
spec.schemas.languages? I will keep this thread open pending that decision.You are interacting with an AI system.