Custom Resources
Custom resources allow you to create resources with custom provisioning logic. This enables you to define the logic for creating, updating, and deleting resources, giving you the ability to provision any type of resource that is not natively supported by Stacktape or Cloudformation. This way, you can manage all your related resources in a single stack.
A common use case for custom resources is provisioning non-AWS resources. While AWS offers a vast array of services, a third-party solution may better fit your needs (e.g., using Auth0 instead of Cognito, or Algolia instead of OpenSearch).
If you are not provisioning a custom resource but are looking for a way to execute custom logic during the deployment process, see the deployment scripts documentation.
How to use custom resources
In Stacktape, a custom resource consists of two parts:
- Definition: A special type of Lambda function that contains the custom logic for creating, updating, and deleting the resource.
- Instance: An instantiation of a custom resource definition.
The following example shows a custom resource that seeds a MongoDB cluster.
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsconnectTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Custom resource definition
A custom resource definition is similar to a function definition, as it uses a Lambda function to execute the custom logic.
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsconnectTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Code of custom resource
The packaging property allows you to specify the path to your code and other packaging-related properties. The code is packaged and executed as a Lambda function. For more information, see the packaging documentation.
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsconnectTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Code example
The following example shows the code for the MongoDB seeding custom resource. For more information on the types of requests (events) a custom resource receives and the response objects you should return, see the AWS documentation.
import mongoose from 'mongoose';import { SUCCESS, FAILED, send } from 'cfn-response-promise';// incoming event is in following form// {// "RequestType" : "Create" || "Update" || "Delete",// "RequestId" : "9db53695-b0a0-47d6-908a-ea2d8a3ab5d7",// "ResponseURL" : "https://...",// "ResourceType" : "AWS::Cloudformation::CustomResource",// "LogicalResourceId" : "...",// "StackId" : "arn:aws:cloudformation:...",// "ResourceProperties" : {// ... properties of custom-resource-instance// }// }export default async (event, context) => {// custom resource definition codelet success = true;let dataToReturn = {};try {// we are only seeding database if the operation is Createif (event.RequestType === 'Create') {// we are using the "mongoConnectionString" property passed by custom-resource-instance to create connectionconst connection = await mongoose.connect(event.ResourceProperties.mongoConnectionString, {authMechanism: 'MONGODB-AWS',authSource: '$external',useNewUrlParser: true,useUnifiedTopology: true,dbName: 'my-test-database'});// code with seeding the database ...// ...}} catch (err) {success = false;}await send(event, context, success ? SUCCESS : FAILED, dataToReturn, 'customresourceid');};// function must respond to "ResponseURL" with response in following form// we are using "cfn-response-promise" library which formats response for us// {// "Status" : "SUCCESS" || "FAILED",// "RequestId" : "9db53695-b0a0-47d6-908a-ea2d8a3ab5d7",// "LogicalResourceId" : "...",// "StackId" : "arn:aws:cloudformation:...",// "PhysicalResourceId" : "...",// "Data" : {// ... attributes which can be queried in template using $Param// }// }
Accessing other resources
By default, resource-to-resource communication is not allowed in AWS. Access must be explicitly granted using IAM permissions. Stacktape automatically handles IAM permissions for the underlying AWS services it creates.
If your custom resource needs to communicate with other infrastructure components, you must grant the necessary permissions manually. You can do this in two ways:
connectTo
The connectTo property is a list of resource names or AWS services that the custom resource will be able to access. Stacktape will automatically grant the basic IAM permissions required for communication. This is useful if you do not want to manage IAM permissions yourself.
When you use the connectTo property, Stacktape also automatically injects information about the connected resource as environment variables into your custom resource's runtime.
Configures access to other resources in your stack and AWS services. By specifying resources here, Stacktape automatically:
- Configures IAM role permissions.
- Sets up security group rules to allow network traffic.
- Injects environment variables with connection details into the compute resource.
Environment variables are named STP_[RESOURCE_NAME]_[VARIABLE_NAME] (e.g., STP_MY_DATABASE_CONNECTION_STRING).
In this example, we grant access to a MongoDB cluster. This will inject the necessary credentials into the custom resource's runtime, which are then used in the code example.
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsconnectTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Environment variables
You can use environment variables to inject information that the resource needs during execution.
Custom resource instance
A custom resource instance creates an instance of a specified custom resource definition. Depending on the action being performed on the stack (create, update, or delete), the instance receives an event and executes the corresponding logic in the custom resource definition.
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsconnectTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')