Custom Resources
Overview
Custom resources enable you to create resources with custom provisioning logic. This enables users to specify execution logic for resource creation, update, and deletion therefore allowing users to execute any kind of logic during stack deploy/delete. Moreover, it gives users the ability to provision any type of resource that is not included among Stacktape or Cloudformation resources. That way, users can manage all related resources in a single stack.
When to use
Provisioning non-AWS resources - While AWS offers huge amount of services and resources, in some cases, AWS might not offer a solution(resource) for your use-case, and often time a third-party solution better fits your needs. For example: using Auth0 over Cognito or using Algolia over AWS Elastisearch. These non-AWS resources can be provisioned using custom resources.
If you are not provisioning custom resource but are looking for a way to execute custom logic during deployment process take a look at deployment scripts
Usage
In Stacktape, custom resources are made of two parts:
custom-resource-definition - definition is special type of lambda function. It contains the custom logic - the code, which gets executed each time custom-resource-instance is created, updated, or deleted. To put into perspective of standard programming languages: custom-resource-definition is like a class and custom-resource-instance is instance of this class.
custom-resource-instance - when you specify instance, you must specify custom-resource-definition defintionName (which you want to instantiate) and resourceProperties(which can be thought of as arguments), which will be passed to the executing code.
All parts of a custom resource are shown in the following snippet. Individual parts are explained in the subsequent sections.
The following example shows a custom resource that is used to seed a mongo cluster.
Copy
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsaccessControl:allowAccessTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Custom resource definition
Custom resource definition is similar to function definition. This is because on the background custom-resource-definition leverages lambda functions.
Copy
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsaccessControl:allowAccessTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Code of custom resource
Package configuration allows you to specify a path to your code and other package properties. The code contains a logic of your custom resource.
Code is during deployment packaged and executed as lambda functions. Refer to lambda functions packaging docs.
Copy
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsaccessControl:allowAccessTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Code example
The following example shows the code of our seeding custom resource in file seed-the-mongo-cluster.ts
See AWS documentation to learn more about:
- the type of requests (events) custom resource receives during stack operations
- response objects which you should return during respective operations
Copy
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// }// }
Access control
Access control is configured same as access control of functions or other workloads.
In this example, we are granting access to the mongo cluster. This will inject credentials for access which are used in code.
Copy
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsaccessControl:allowAccessTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')
Environment
Use environment variables for injecting information that resource needs during code execution.
Custom resource instance
Custom-resource-instance, creates instance of specified custom-resource-definition. Depending on the action (create/update/delete) stack is undergoing, the instance receives event (see code example) and executes the code.
Copy
resources:myMongoCluster:type: mongo-db-atlas-clusterproperties:clusterTier: M10mongoSeeder:type: custom-resource-definitionproperties:packaging:type: stacktape-lambda-buildpackproperties:entryfilePath: seed-the-mongo-cluster.tsaccessControl:allowAccessTo:- myMongoClusterseedMongoCluster:type: custom-resource-instanceproperties:definitionName: mongoSeederresourceProperties:mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')