5 min read | by Jordi Prats
With Crossplane we define Composite resources as the combination of other resources. Let's take a look on how to do this we are going to take some terraform code, tranform it into Crossplane objects and the create a Composition based on them
We are going to use the following terraform code that creates a SecurityGroup on AWS with an outgoing rule (SecurityGroupRule):
resource "aws_security_group" "pod_sg" { name = "xplane-xplanesg" description = "xplane SG test" vpc_id = "vpc-1234abcd" } resource "aws_security_group_rule" "egress_any_sg_pod" { description = "allow all outbound traffic" security_group_id = aws_security_group.pod_sg.id type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
The process to translate this terraform code to a Crossplane object is specially easy if we are using the AWS jet provider: We just need to convert the resources's and it's properties names to camelcase. For example, from_port would be forPort. For the resource's names we will have to also exclude the aws_ bit: aws_security_group would be SecurityGroup.
So, the resulting Crossplane objects would be:
apiVersion: ec2.aws.jet.crossplane.io/v1alpha2 kind: SecurityGroup metadata: name: 'xplanesg' labels: sgname: 'xplanesg' spec: forProvider: name: 'xplane-xplanesg' region: 'eu-west-1' description: 'xplane SG test' vpcId: 'vpc-1234abcd' providerConfigRef: name: 'aws-jetprovider-config' --- apiVersion: ec2.aws.jet.crossplane.io/v1alpha2 kind: SecurityGroupRule metadata: name: 'xplanesg-egress' spec: forProvider: securityGroupIdSelector: matchLabels: sgname: 'xplanesg' fromPort: 0 toPort: 0 protocol: "-1" type: 'egress' region: 'eu-west-1' cidrBlocks: - '0.0.0.0/0' providerConfigRef: name: 'aws-jetprovider-config'
Please notice how it's not completely boilerplate-free:
By applying these objects we will be able to see how they get created on AWS:
$ kubectl get securitygroup NAME READY SYNCED EXTERNAL-NAME AGE xplanesg True False sg-11c0052b1ad079839 1m30s $ kubectl get securitygrouprule NAME READY SYNCED EXTERNAL-NAME AGE xplanesg-egress True True sgrule-3584302207 1m30s
To create a Composition with these two resources we will need to create two objects:
To create the CompositeResourceDefinition we need to, basically, define the OpenAPI structural schema. In order to simplify it's definition we are going to use just one variable: It's region. The actual definition would look like follows:
apiVersion: apiextensions.crossplane.io/v1 kind: CompositeResourceDefinition metadata: name: sgsallowoutgoing.pet2cattle.com spec: group: pet2cattle.com names: kind: SGAllowOutgoing plural: sgsallowoutgoing versions: - name: v1alpha1 served: true referenceable: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: parameters: type: object properties: region: type: string required: - region required: - parameters
Once we have the CompositeResourceDefinition in place we can now create a Composition for it. To do so we can take the objects we want it to create and us the patch definitions in order to create them with the proper values:
apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: name: sgsallowoutgoing labels: crossplane.io/xrd: sgsallowoutgoing.pet2cattle.com spec: writeConnectionSecretsToNamespace: crossplane-system compositeTypeRef: apiVersion: pet2cattle.com/v1alpha1 kind: SGAllowOutgoing resources: - name: sg base: apiVersion: ec2.aws.jet.crossplane.io/v1alpha2 kind: SecurityGroup spec: forProvider: description: 'xplane SG test' vpcId: 'vpc-1234abcd' providerConfigRef: name: 'aws-jetprovider-config' patches: - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: spec.forProvider.name - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: metadata.annotations.crossplane.io/external-name - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: metadata.labels.sgname - type: FromCompositeFieldPath fromFieldPath: spec.parameters.region toFieldPath: spec.forProvider.region - name: sg-egress base: apiVersion: ec2.aws.jet.crossplane.io/v1alpha2 kind: SecurityGroupRule spec: forProvider: fromPort: 0 toPort: 0 protocol: "-1" type: 'egress' cidrBlocks: - '0.0.0.0/0' providerConfigRef: name: 'aws-jetprovider-config' patches: - type: FromCompositeFieldPath fromFieldPath: spec.parameters.region toFieldPath: spec.forProvider.region - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: spec.forProvider.securityGroupIdSelector.matchLabels.sgname
This is an over simplification to make it easier to understand with plenty of hardcoded values so you can make sense out of it.
Once both resources are available we can now proceed creating an instance of this new resource:
apiVersion: pet2cattle.com/v1alpha1 kind: SGAllowOutgoing metadata: name: sgone spec: parameters: region: eu-west-1
If we apply this object it will create, in turn, create the objects we have defined:
$ kubectl get sgallowoutgoing NAME READY COMPOSITION AGE sgone True sgsallowoutgoing 4m13s $ kubectl get securitygroup NAME READY SYNCED EXTERNAL-NAME AGE xplanesg True False sg-11c0052b1ad079839 5h33m sgone-jr6lv True False sg-ad6a7ad890ca61632 4m25s $ kubectl get securitygrouprule NAME READY SYNCED EXTERNAL-NAME AGE xplanesg-egress True True sgrule-3584302207 5h26m sgone-mdvmc True True sgrule-3652368947 4m30s
Using kubectl describe on the composition instance we'll be able to see the objects it's using under the hood:
$ kubectl describe sgallowoutgoing.pet2cattle.com/sgone Name: sgone Namespace: Labels: crossplane.io/composite=sgone Annotations: <none> API Version: pet2cattle.com/v1alpha1 Kind: SGAllowOutgoing Metadata: (...) Spec: Composition Ref: Name: sgsallowoutgoing Composition Update Policy: Automatic Parameters: Region: eu-west-1 Resource Refs: API Version: ec2.aws.jet.crossplane.io/v1alpha2 Kind: SecurityGroup Name: sgone-jr6lv API Version: ec2.aws.jet.crossplane.io/v1alpha2 Kind: SecurityGroupRule Name: sgone-mdvmc Write Connection Secret To Ref: Name: e5ca5def-ab90-4fe9-a2e1-fd588105586a Namespace: crossplane-system Status: Conditions: Last Transition Time: 2022-03-14T22:42:21Z Reason: Available Status: True Type: Ready Connection Details: Last Published Time: 2022-03-14T22:41:51Z Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal PublishConnectionSecret 3m15s defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details Normal SelectComposition 44s (x9 over 3m16s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition Normal ComposeResources 43s (x9 over 3m15s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
For your convenience, I have also uploaded all these objects to the GitHub's repo: pet2cattle/crossplane-composition-example
Posted on 16/03/2022