Composite resource definitions (XRDs) define the type and schema of your composite resource (XR). Think of the XRD as the object that defines the shape of your API. The fields you choose to define in the spec
of your XRD are highly dependent on your use case. Below are best practices you can apply to help you approach how to define an XRD.
Purpose
If you haven’t read and gone through the framework’s self-evaluation exercise, it’s recommended you do that now. It’s difficult to build a custom API without knowing who you are building it for and why you’re building it. First, figure this out. The next questions you need to figure out are:
- What managed resources do you want to compose together as part of this API?
- For each managed resource you want to compose, what are the required fields?
Which managed resources to compose
Most Crossplane providers have lots of different managed resources that map to appropriate APIs to manage a particular set of infrastructure or resources.
Generic | Crossplane provider-aws | AWS infrastructure |
---|---|---|
resource-1 | Instance | An EC2 Instance |
resource-2 | SecurityGroup | A Security Group |
When you create infrastructure in a cloud service, sometimes that infrastructure maps to a single API (such as an S3 bucket). Other times it actually maps to multiple APIs. For example, when you create an EKS cluster in AWS, these objects get created:
- an AWS cluster object
- some IAM rules
- a NodeGroup
- network settings
- and more
Multiple APIs get called and multiple objects get created.
Use the Upbound Marketplace’s API documentation feature. In the Marketplace, search for a resource you want to create. Within that resource’s API documentation, under the spec.forProvider
struct, look for properties that have a naming pattern like <resource>Ref
or <resource>IdSelector
. Those properties may communicate that, in order for your desired resource to create, you may need to provide references to other resources. This tip is a heuristic, not a firm rule.
As an example, consider the provider-aws Instance
resource for an EC2 Instance. You can see below there is a reference to a networkInterface
. This points to another resource you may choose to create in Crossplane and then reference in your Instance resource.
Required fields for composed resources
Two reasons it’s important to know the required fields in the managed resources you want to compose are:
- There’s a high likelihood you want to either expose or let your consumers influence indirectly these fields in your API
- Composite Resources result in instantiated managed resources. You need to create valid managed resources in order for your composite resource to create.
Use the Upbound Marketplace’s API documentation feature to see the required fields under the spec.forProvider
. For example, notice in the AWS EKS Cluster resource API documentation how region
has a “required” label whereas version
isn’t.
Sometimes a managed resource has fields that need to have a value but it’s not explicitly marked as required
. For example, consider the AWS Lambda Function resource API documentation. It stipulates that filename, image_uri, or s3_bucket must be specified
, yet none of those fields have a “required” label. It’s helpful to look at a resource’s “Examples” tab in the Upbound Marketplace. Examples show verified configurations for managed resources.
Authoring an XRD
Scaffolding an XRD
XRDs follow the OpenAPI “structural schema.” Below is boilerplate .YAML that you can use to scaffold the start of an XRD.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: <plural-name>.<group>
spec:
group: <group.example.com>
names:
kind: X-<kind-name>
plural: X-<kind-name-plural>
claimNames:
kind: <kind-name>
plural: <kind-name-plural>
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
<to-do>
required:
- parameters
status:
type: object
properties:
<todo>
In your XRD, you should give your Composite Resource a name and update the API group. The preceding scaffold also assumes you want this Composite Resource to be claimable. If you don’t want to allow users to create claims against this Composite Resource, omit the claimNames
field.
Authoring best practices
The following are some other best practices to abide by as you author new XRDs:
Inputs belong in spec.properties.parameters
All the inputs you want to provide as part of your API belong in your XRD’s spec.properties.parameters
stanza, as seen in the example below.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
spec:
...
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB:
type: integer
required:
- storageGB
required:
- parameters
Rely on the OpenAPI v3 data models
The OpenAPI v3 data models define the types and values available for you to use as part of defining your XRD.
Use OpenAPI v3 data types
The OpenAPI v3 data types define the value types available for you to use as part of your schema.
Use enums for static options
It’s recommended you use Enums for static options for field value. For example, if you have field called environment
and there are a defined list of options for your users to choose from:
environment:
type: string
enum: [Dev, DR, UAT, Pre-prod, Prod]
String validation
It’s recommended you use pattern
for complex string validations. For example:
teamEmailAddress:
type: string
pattern: "^[a-zA-Z0-9@\\.]*$"
XRD status
The spec.status
field of your XRD is a useful way to exchange data between various Crossplane resources. To patch a value from one Crossplane resource to another, you can publish and consume values via an XRD’s status. To do so, your XRD must first define the status field. Patching is a concept relevant to compositions; it’s discussed in detail later for how to do this.
Versioning
Over the lifetime of your custom API, there is a chance the shape of the API could change and you could introduce breaking changes. Crossplane has a built-in capability to help with this. The preceding scaffolding recommends a boilerplate version named v1alpha1
. Notice the versions
field is an array, so you can declare multiple versions of your API definition in the XRD. Crossplane doesn’t allow serving multiple versions. Meaning, once v1alpha2 goes live, all the composites are force-migrated to that version:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
spec:
...
versions:
- name: v1alpha1
served: false
referenceable: true
schema:
...
- name: v1alpha2
served: true
referenceable: true
schema:
...
Note the preceding example is just the definition part of your new API version. You also need to create new compositions that serve the implementation the new version. Read Authoring Compositions to learn how to do that.
Converting resources between XRD versions
Beginning in Crossplane v1.12.0 and later, Crossplane supports using webhooks to convert resources between defined versions of an XRD. To do this, you declare a conversion
strategy in the spec
of the XRD.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
spec:
conversion:
strategy: Webhook
webhook:
...
Next steps
Defining an XRD is half the equation for building a custom API. The other important steps is to define a composition which implements the definition contained in your XRD. Read Authoring Compositions to learn how to do that.