design/one-pager-composition-function-build-tooling.md
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:
function-patch-and-transform and
function-auto-ready are examples of generic
Functions.Take a look at the Composition Functions design document 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:
crossplane beta xpkg init.fn.go).crossplane beta renderOnce 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:
crossplane xpkg build.crossplane xpkg pushThis 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 path". This golden path will
inform what choices we make in Function template repositories like
function-template-go, 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:
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:
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 docker/build-push-action.
Today only one Function template exists -
function-template-go for the Go programming language.
Go is an interesting example for two reasons:
go test and go generate.In function-template-go, I propose that Function developers:
go run, go generate, and go test to develop and
(unit) test their Function.golangci-lint, to lint their
Function.docker/build-push-action), run a
go command, or run a crossplane command.For example, to run unit tests in CI:
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 ./...