This is a guest post from Grant Gumina, Principal Product Manager at Upbound who recently built provider-pizza, a Crossplane provider for the Domino’s API. In this post he shares what he learned about providers, and some common mistakes beginners might fall into when writing their first provider.
In my day job, I work at Upbound helping customers deploy and scale Crossplane into production. Our product, Upbound Cloud is a managed service of Crossplane which lets infrastructure operators define and deploy custom cloud APIs and consoles for their teams.
Being “the product guy”, I haven’t written production code in years. However after seeing customers use Crossplane to orchestrate everything from AWS environments to GitHub ticketing systems, I wanted to use my technical background to test the limits of Crossplane's extensibility story. So I spent a weekend figuring out how to order pizza through Crossplane by building a Crossplane provider for the Domino's Pizza API.
If you're not familiar with the project, Crossplane is a cloud-native control plane which allows users to address and abstract infrastructure resources running on-premises or in the cloud(s) of your choice. It does this by installing into a Kubernetes cluster and extending the cluster's API through Providers which install into it.
Each Provider installed into the cluster running Crossplane adds cluster-scoped CRDs for various "managed resources". As a user, you can use
kubectl to interact with these resources. For example, Crossplane lets you
kubectl apply -f db.yaml to provision a database.
Provider-pizza is my attempt at learning more about the inner workings of Crossplane, and seeing how far I could extend the metaphor of a "universal cloud API", but it's not the focus of this post. Since Crossplane is still fairly young, there's not a ton of resources geared towards novices. I'm hoping this post can help guide aspiring provider builders in the right direction, and help you avoid some of the mistakes I made when writing my first provider.
View the project on GitHub to learn more about how it works, see how to run it yourself, and order a delicious pizza. Keep reading if you're interested in learning more about Crossplane providers.
The Anatomy of a Provider
Crossplane has an incredible extensibility story, thanks to it's provider model. Providers are basically Kubernetes-style controllers which connect anything with an API to the Kubernetes cluster where Crossplane is running, giving you a CRD representing each resource.
To build my first provider, I cloned the Crossplane team's provider-template repository on GitHub, and got to work.
Inside of a Provider, you can have many different managed resources, each having it's own
provider-pizza, these Managed Resources are defined in the
/apis directory. You can see there's a Managed Resource of type
Just like a Kubernetes controller, providers run on their own reconciliation loops. The loop has several methods:
Setup - Setup is called as soon as the provider starts up. It registers the controller which triggers these methods to run when events occur.
Connect - Generates a Crossplane
ExternalClient which is used to connect to the managed resource. The connect method typically uses the values supplied by the user and defined in
ProviderConfig to authenticate with the external service (typically a web API such as the Domino's pizza API).
ExternalClient objects will interact with the external resource (in this case the Domino's API) and store connection details as a secret.
Create - I used this method to create the order object based out of the user input and
ProviderConfig details. I also set a property on the Order object so I could tell whether or not an order had been requested later on in the
Observe method. This let me return
ExternalObservation with the appropriate information about whether or not the resource was up to date or created yet.
Observe - This is the mitochondria of the controller. The Observe method gets called every few seconds and updates the object's status. This is where I made calls to Domino's tracker API to get the latest information about my order. Returning an
ExternalObservation object with
ResourceExists set to false calls the
Create method. This is the default behavior of
I didn't initially realize this, and the poor workers at the Wallingford Dominos fulfilled half a dozen or so orders before I caught the mistake.
Update - This method gets called when the
ResourceUpToDate property on the
ExternalObservation object is set to false. I didn't use this method for anything, but if the Domnio's API supported order modification, I suspect this is where I would handle that.
Delete - This method gets called when the
ResourceExists property on the
ExternalObservation object is set to false.
Crossplane Providers can be configured with secrets for authentication or other user defined values by applying a
ProviderConfig, a CRD type which gets installed by the user.
ProviderConfigs have a standard format Crossplane expects to read when configuring a Provider. They can reference Kubernetes secrets, so users can store credentials needed to connect to the web services.
In provider-pizza, payment information is stored as a secret and referenced by the ProviderConfig:
apiVersion: v1 kind: Secret metadata: name: payment-secret namespace: crossplane-system type: Opaque data: credentials.json: ewogIGNyZWRlbnRpYWxzOiBCQVNFNjRFTkNPREVEX1BST1ZJREVSX0NSRURTLAogIFR5cGU6ICJDcmVkaXRDYXJkIiwKICBDYXJkVHlwZTogIlZpc2EiLAogIE51bWJlcjogIjExMTEyMjIyMzMzMzQ0NDQiLAogIEV4cGlyYXRpb246ICIwMTIzIiwKICBTZWN1cml0eUNvZGU6ICIxMjMiLAp9 --- apiVersion: provider-pizza.crossplane.io/v1alpha1 kind: ProviderConfig metadata: name: example spec: credentials: source: Secret secretRef: namespace: crossplane-system name: payment-secret key: payment-secret-key
Crossplane is designed with extensibility in mind. Typically users orchestrate cloud and on-premises infrastructure with the project, but as you can see, any service with an API can be used as well. Once installed, providers give Crossplane users a uniform interface and API to orchestrate and manipulate the managed resources they represent.
We saw how you can
kubectl -f apply order.yaml, but you can just as easily
kubectl -f apply database.yaml using another provider like provider-aws.
If you’re interested in learning more about how you can use Crossplane to address and abstract your own infrastructure, even if it’s not pizza related, we’d love to talk with you. Join the community Slack and follow us on Twitter.
Looking to deploy Crossplane into production, or already doing so? We’d love to give you a demo of Upbound Cloud and see how it might help with your use case. Visit us at upbound.io or email firstname.lastname@example.org to learn more.
We're excited to see the continual growth of the Crossplane community and would love for you to get involved. Whether you are a developer, user, or just interested in what we're up to, feel free to join us via one of the following methods: