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