I've been thinking about the following two things:
- What is most valuable and unique in the Upbound DevEx? I think it's the concept of a Project as an on-disk development artifact encompassing both configuration (XRDs, compositions, operations) and code (functions, tests). Separating the development artifact (project) from the artifact that gets installed into a cluster (xpkg) is powerful.
- It sounds like a lot of folks have built their own test frameworks with overlapping but distinct feature sets. I don't think our (Upbound) implementation of tests is clearly better or worse than the others: the differences are partly a matter of taste and partly the result of different requirements.
We could standardize the concept of a Project in Crossplane, and implement the basic tooling for building a Project into a set of XPKGs into the core of the CLI. This would mean standardizing:
- A metadata file describing a project (like the current
upbound.yaml). - A basic directory layout for projects.
- How a project gets built into a set of XPKGs (dependency structure etc.).
The core of the CLI would have a set of commands for working with projects, like:
crossplane project initcrossplane project buildcrossplane project pushcrossplane project testcrossplane project run
The build, test, and run commands would call out to plugins to build
functions, run tests, and spin up clusters rather than having these features
built into the CLI core. This makes the CLI extensible to suit different
opinions and requirements while leveraging a common core to avoid duplicated
work and help unify the ecosystem.
The core would not include:
- Code for building functions.
- A test definition or code for running tests.
- Code for spinning up test clusters.
These concerns, where there could be many different implementations, would be left to plugins. The core could still include libraries to help with plugin development (e.g., utilities for building compliant xpkgs, a library-only version of render), but actual implementation would be left to plugins.
A couple of examples of how this might work:
crossplane project buildwould call out to a plugin (probably a separate executable) to build each function. The contract between the core CLI and the plugin might be that the plugin writes a function runtime image to an OCI layout in a particular location, which the core CLI would then consume to build into an xpkg.crossplane project testwould call out to plugins to spin up a cluster (for e2e tests) and to execute tests; the plugin would be provided with the test configuration file (all or part of which could be specific to the plugin), and would write results to stdout in a standardized format for the CLI to consume and present to the user.crossplane project runwould call out to a plugin to spin up a cluster. The plugin would return a kubeconfig on stdout, which the core CLI would then use to install the packages.
I mostly like this idea, although I think we should be sure to provide a default plugin set for testing and running if no override is provided.
As an ecosystem, I think it's okay to have an opinion about tests. And I think the proliferation of different implementations mostly speaks to the lack of a cohesive approach upstream when we were all implementing our own test stacks. If there were one golden thing, we'd probably all have gone with that and as a community extended it until it met our requirements.
Speaking for myself, I went with the helm -> helm postRender -> crossplane render pipeline because i was familiar with
helm-unittestand it was the path of least resistance. But if a sensible upstream implementation had been the path of least resistance, I'd have used it instead.