Skip to content

Add a getting started guide for control plane projects#1110

Open
adamwg wants to merge 7 commits into
crossplane:masterfrom
adamwg:awg/devex-guide
Open

Add a getting started guide for control plane projects#1110
adamwg wants to merge 7 commits into
crossplane:masterfrom
adamwg:awg/devex-guide

Conversation

@adamwg

@adamwg adamwg commented Jun 15, 2026

Copy link
Copy Markdown
Member

Walk users through building their first control plane project using a simple WebApp example. This guide includes two "choose your own adventure" sections:

  • Generating an XRD from either an example XR or SimpleSchema.
  • Writing a function in any of our supported languages: Go Templating, Python, Go, or KCL.

@netlify

netlify Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy Preview for crossplane ready!

Name Link
🔨 Latest commit b402b1a
🔍 Latest deploy log https://app.netlify.com/projects/crossplane/deploys/6a3d846425dca1000871e93f
😎 Deploy Preview https://deploy-preview-1110--crossplane.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 90 (🔴 down 3 from production)
Accessibility: 90 (🔴 down 2 from production)
Best Practices: 92 (no change from production)
SEO: 100 (no change from production)
PWA: 70 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@adamwg adamwg force-pushed the awg/devex-guide branch from 248432f to 912f357 Compare June 18, 2026 21:38

@jbw976 jbw976 left a comment

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.

nice guide! the new devex tooling really is powerful and smooth 💪

I found a couple issues while walking through it using go, let me know if you have any questions about the feedback! 🙇‍♂️

@@ -0,0 +1,7 @@
---

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.

would it make sense to link this new guide somehow from the core crossplane docs getting started section for more visibility? i.e. maybe a placeholder page so it shows up in that nav pane, but that page then just has a short intro and link to this content?

that may make things confusing, so feel free to disregard, just thinking of more ways to make this new guide discoverable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good idea. I thought about adding a note in the header of the existing "get started with compositions" page, but there's already a lot of frontmatter there so it kinda gets lost. I've taken your suggestion and added a stub page in that section that links to the CLI docs. Open to suggestions on where it sits in the nav and its content.

@@ -0,0 +1,7 @@
---

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.

will you add this to v2.3 also so it shows up in latest?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, I'll copy it to v2.3 and the new v2.4. Figured it would be easier to review and update in master first :-).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

├── operations
└── tests

6 directories, 1 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.

mine says 5 dirs and 1 file, but i don't appear to be missing anything 🔍

maybe just a mac vs linux difference?

❯ tree example-project-webapp

example-project-webapp
├── apis
├── crossplane-project.yaml
├── examples
├── functions
├── operations
└── tests

5 directories, 1 file

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Huh, weird. Must be a Linux vs. BSD tree difference. I guess Linux counts the top-level directory?


The command writes the generated XRD to `apis/webapps/definition.yaml`:

```yaml

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.

we can consider using {copy-lines="none"} for all of these blocks that are just showing what the tooling was expected to output. we aren't expecting folks to copy/paste this anywhere right? using copy none may reduce any potential confusion.

This comment applies to a few different blocks in this doc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good idea, done.

can render and run the project against a realistic input. If you generated your
XRD from an example XR, you already have the example and can skip this step.

Create `examples/webapp/podinfo.yaml`:

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.

the directory name under apis/ was webapps, but the directory here under examples/ is singular webapp. is that intentional?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In the up tooling we had up example generate, which used the singular kind under examples/, so I had just followed that convention. But we don't have such a command in the upstream tooling (yet, at least), and I think the consistency of apis/webapps and examples/webapps is nice, so updated to use the plural.

functions automatically, so you only pass the example XR and the composition:

```shell
crossplane composition render examples/webapp/podinfo.yaml apis/webapps/composition.yaml

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.

when i run this command for the go based function/composition, i get this error:

crossplane: error: cannot build embedded functions: failed to build function "compose-webapp": failed to build runtime images: failed to build function: build: go build: exit status 1: # example.com/my-org/example-project-webapp/compose-webapp
                   ./fn.go:124:29: undefined: corev1.ServiceAPIVersionV1 (but have ServiceApiVersionV1)

Indeed, the same error is shown for that line in my IDE by the language server, and it looks correct that ServiceApiVersionV1 is the correct casing in schemas/go/io/k8s/core/v1/core.go.

Making that change seems to fix the error and the render is able to run.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is due to crossplane/cli#113, which is fixed now so uppercase API should always be generated. (I actually noticed the issue while testing this guide as well!)

deployment.apps/podinfo 3/3 3 3 45s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/podinfo ClusterIP 10.96.142.110 <none> 9898/TCP 45s

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.

the service is not shown in my output, but it does seem to exist. Looks like it's missing the label, I think the go code is not applying that label to the Service object.

❯ kubectl get deployment,service -l app.kubernetes.io/name=podinfo

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/podinfo   3/3     3            3           20s

❯ k get service
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    106s
podinfo      ClusterIP   10.96.109.136   <none>        9898/TCP   28s

❯ k get service podinfo -o json | jq '.metadata.labels'
{
  "crossplane.io/composite": "podinfo"
}

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.

i think this issue affects templated yaml (function-go-templating) also, but python and KCL do correctly apply the label to the service.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch - added the labels to the Go and go-templating examples so the command works consistently now.

adamwg added 6 commits June 25, 2026 13:28
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Walk users through building their first control plane project using a simple
WebApp example.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
This makes the composed resources consistent with the Python and KCL
implementations.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
The `up` tooling created examples in a directory named after the singular kind,
even though compositions and XRDs go in a directory named after the plural. The
upstream tooling doesn't generate examples (yet), so there's no reason to stick
with this convention. Pluralize the example directory to make it consistent.

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@adamwg adamwg force-pushed the awg/devex-guide branch from 912f357 to 617ad0d Compare June 25, 2026 19:36
Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@adamwg adamwg force-pushed the awg/devex-guide branch from 617ad0d to b402b1a Compare June 25, 2026 19:41
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.

2 participants