Project: Crossplane (Orchestration & Management / Scheduling & Orchestration)
File: one-pager-composition-function-build-tooling.md

Owner: Nic Cope (@negz) Reviewers: Jared Watts (@jbw976) Status: Draft A Composition Function extends Crossplane to support a new way of configuring how to reconcile a composite resource (XR). Each Function is a gRPC server. Crossplane sends state to a Function via a `RunFunctionRequest` RPC. The Function is intended to return updated state via a `RunFunctionResponse` RPC. Part of the goal of Composition Functions is to allow Crossplane users to build Functions using their general purpose programming language (GPL) of choice. I think there will be broadly two types of Function: Generic, reusable Functions. These Functions support a common use case. They're intended to work with any kind of XR, and often expose a domain specific language (DSL) that their users must use to express Composition logic. and are examples of generic Functions. Purpose-build Functions. These Functions are strongly coupled to a the schema of a specific kind of XR. Rather that exposing a configuration DSL, the Function's GPL code is the Composition logic. Take a look at the for more context on Functions. Quoting from that document: I believe it's critical that Composition Functions are easy to develop - much easier than developing a Crossplane Provider. Another way to think about this is that the developer experience must scale. Some Functions will be software engineering projects. They'll be maintained by a team of contributors, have unit and end-to-end tests, release branches, continuous integration (CI), etc. At the other, simpler, end of the scale many Functions will be more like configuration that happens to be expressed using a GPL. For someone writing a Function like this, needing to learn and use a new set of build and CI tools is a potentially huge barrier to entry. The emergent process for developing a Composition Function is: Scaffold a new Function from a template using `crossplane beta xpkg init`. Add your logic to the scaffolded project (e.g. edit `fn.go`). Optionally, add and run unit tests for your Function logic. Optionally, test your Function end-to-end using `crossplane beta render` Once you're satisfied that your Function works end-to-end it's time to make it available to install and use in your Crossplane control plane. To do this you must: Build your Function's "runtime" - the OCI image used to deploy it. Build a package from the OCI image runtime using `crossplane xpkg build`. Push your package to an OCI registry using `crossplane xpkg push` This document proposes a set of guiding principles for the Composition Function developer experience, with a particular focus on build and CI. Ultimately Functions are open-ended enough that a developer could choose whatever path best suits them, so consider this a proposed "golden

This golden path will inform what choices we make in Function template repositories like , and thus establish patterns for the broader Function ecosystem. I propose that we strive to keep the set of tools and technologies a Crossplane user must learn to write a Function as small at possible. Learning Crossplane alone is hard enough - we don't want users to also need to learn a new build tool if we can avoid it. I propose that the minimum set of tools required to build a Function be: Your programming language of choice - e.g. Go, Python. Docker. The `crossplane` CLI. When it comes to templates used to scaffold a new Function I think we'll want to offer a little more than the minimum required set of tools. For example I believe linting, testing, and CI should be part of the golden path we establish. Most language runtimes aren't opinionated about these things so we'll need to make some tooling choices that will affect Function developers. Where we must include a tool in a Function template, I propose we bias for tools that: Where possible, are the de-facto standard for their language or ecosystem. Are idiomatic and widely adopted within their language ecosystem. Have great documentation and educational resources available. Put otherwise, we should bias for tools that a developer likely already knows and uses. For example if you're a Python developer there's a good chance you're familiar with the `pylint` linter. For CI, I propose we stick with GitHub Actions and avoid 'intermediary' scripting or automation layers such as `make`. For example, a Function runtime should build in CI using the . Today only one Function template exists - for the Go programming language. Go is an interesting example for two reasons: The language is more opinionated than most when it comes to tooling. For example it includes tools like `go test` and `go generate`. The Crossplane is written in Go, so we have established patterns and practices, like heavy use of Makefiles and the [build submodule]. In `function-template-go`, I propose that Function developers: Use native tools like `go run`, `go generate`, and `go test` to develop and (unit) test their Function. Use the language's defacto standard linter, `golangci-lint`, to lint their Function. Use a GitHub Actions workflow consisting only of steps that either invoke widely used and documented actions (e.g. `docker/build-push-action`), run a `go` command, or run a `crossplane` command. For example, to run unit tests in CI: ```yaml jobs: unit-test: runs-on: ubuntu-22.04 steps: name: Checkout uses: actions/checkout@v4 name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} name: Run Unit Tests run: go test -v -cover ./... ```