Creating a Composition
A CompositeResourceDefinition (XRD) defines the type and schema of your Composite Resource (XR). It informs Crossplane about the desired XR and its fields. An XRD is similar to a CustomResourceDefinition (CRD) but with a more opinionated structure. Creating an XRD primarily involves specifying an OpenAPI "structural schema".
Let's start by providing a definition that allows application team members to create a DynamoDB table in their respective namespaces. In this example, users only need to specify the name, key attributes, and index name fields.
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xdynamodbtables.awsblueprints.io
spec:
  group: awsblueprints.io
  names:
    kind: XDynamoDBTable
    plural: xdynamodbtables
  claimNames:
    kind: DynamoDBTable
    plural: dynamodbtables
  connectionSecretKeys:
    - tableName
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          description: Table is the Schema for the tables API
          properties:
            spec:
              type: object
              properties:
                resourceConfig:
                  properties:
                    deletionPolicy:
                      description: Defaults to Delete
                      enum:
                        - Delete
                        - Orphan
                      type: string
                    name:
                      type: string
                    providerConfigName:
                      type: string
                      default: aws-provider-config
                    region:
                      type: string
                      default: ""
                    tags:
                      additionalProperties:
                        type: string
                      description: Key-value map of resource tags.
                      type: object
                  required:
                    - region
                  type: object
                dynamoConfig:
                  properties:
                    attribute: #required for hashKey and/or rangeKey
                      items:
                        properties:
                          name: #name of the hashKey and/or rangeKey
                            type: string
                          type:
                            enum:
                              - B #binary
                              - N #number
                              - S #string
                            type: string
                        required:
                          - name
                          - type
                        type: object
                      type: array
                    hashKey:
                      type: string
                    rangeKey:
                      type: string
                    billingMode:
                      type: string
                      default: PAY_PER_REQUEST
                    readCapacity:
                      type: number
                    writeCapacity:
                      type: number
                    globalSecondaryIndex:
                      items:
                        properties:
                          hashKey:
                            type: string
                          name:
                            type: string
                          rangeKey:
                            type: string
                          readCapacity:
                            type: number
                          writeCapacity:
                            type: number
                          projectionType:
                            type: string
                            default: ALL
                          nonKeyAttributes: #required for gsi
                            items:
                              type: string
                            type: array
                        type: object
                        required:
                          - name
                      type: array
                    localSecondaryIndex:
                      items:
                        properties:
                          name:
                            type: string
                          rangeKey:
                            type: string
                          projectionType:
                            type: string
                          nonKeyAttributes: #required for lsi
                            items:
                              type: string
                            type: array
                        type: object
                        required:
                          - name
                          - rangeKey
                          - projectionType
                          - nonKeyAttributes
                      type: array
                  required:
                    - attribute
                  type: object
              required:
                - dynamoConfig
            status:
              type: object
              description: TableStatus defines the observed state of Table
              properties:
                tableArn:
                  description: Indicates this table's ARN
                  type: string
                tableName:
                  description: Indicates this table's Name
                  type: string
          required:
            - spec
A Composition informs Crossplane about the actions to take when a Composite Resource is created. Each Composition establishes a link between an XR and a set of one or more Managed Resources. When the XR is created, updated, or deleted, the associated Managed Resources are correspondingly created, updated, or deleted.
The following Composition provisions the managed resource Table:
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: table.dynamodb.awsblueprints.io
  labels:
    awsblueprints.io/provider: aws
    awsblueprints.io/environment: dev
spec:
  writeConnectionSecretsToNamespace: crossplane-system
  compositeTypeRef:
    apiVersion: awsblueprints.io/v1alpha1
    kind: XDynamoDBTable
  patchSets:
    - name: common-fields
      patches:
        - type: FromCompositeFieldPath
          fromFieldPath: spec.resourceConfig.providerConfigName
          toFieldPath: spec.providerConfigRef.name
        - type: FromCompositeFieldPath
          fromFieldPath: spec.name
          toFieldPath: metadata.annotations[crossplane.io/external-name]
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.name
          toFieldPath: metadata.annotations[crossplane.io/external-name]
          transforms:
            - type: string
              string:
                type: Regexp
                regexp:
                  match: ^(.*?)-crossplane
  resources:
    - name: table
      connectionDetails:
        - type: FromFieldPath
          name: tableName
          fromFieldPath: status.atProvider.id
      base:
        apiVersion: dynamodb.aws.upbound.io/v1beta1
        kind: Table
        spec:
          forProvider:
            writeConnectionSecretToRef:
              name: cartsdynamo
              namespace: crossplane-system
            region: ""
          providerConfigRef:
            name: aws-provider-config
      patches:
        - type: PatchSet
          patchSetName: common-fields
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.attribute
          toFieldPath: spec.forProvider.attribute
          policy:
            mergeOptions:
              appendSlice: true
              keepMapValues: true
        - type: FromCompositeFieldPath
          fromFieldPath: spec.resourceConfig.tags
          toFieldPath: spec.forProvider.tags
          policy:
            mergeOptions:
              keepMapValues: true
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.attribute[0].name
          toFieldPath: spec.forProvider.hashKey
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.billingMode
          toFieldPath: spec.forProvider.billingMode
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.rangeKey
          toFieldPath: spec.forProvider.rangeKey
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.readCapacity
          toFieldPath: spec.forProvider.readCapacity
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.writeCapacity
          toFieldPath: spec.forProvider.writeCapacity
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.globalSecondaryIndex[0].name
          toFieldPath: spec.forProvider.globalSecondaryIndex[0].name
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.attribute[1].name
          toFieldPath: spec.forProvider.globalSecondaryIndex[0].hashKey
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.globalSecondaryIndex[0].projectionType
          toFieldPath: spec.forProvider.globalSecondaryIndex[0].projectionType
          policy:
            mergeOptions:
              keepMapValues: true
        - type: FromCompositeFieldPath
          fromFieldPath: spec.dynamoConfig.localSecondaryIndex
          toFieldPath: spec.forProvider.localSecondaryIndex
          policy:
            mergeOptions:
              keepMapValues: true
        - type: ToCompositeFieldPath
          fromFieldPath: status.atProvider.id
          toFieldPath: status.tableName
        - type: ToCompositeFieldPath
          fromFieldPath: status.atProvider.arn
          toFieldPath: status.tableArn
Let's apply this configuration to our EKS cluster:
compositeresourcedefinition.apiextensions.crossplane.io/xdynamodbtables.awsblueprints.io created
composition.apiextensions.crossplane.io/table.dynamodb.awsblueprints.io created
With these resources in place, we've successfully set up a Crossplane Composition for creating DynamoDB tables. This abstraction allows application developers to provision standardized DynamoDB tables without needing to understand the underlying AWS-specific details.