logoStacktape docs




Custom Resources

Overview and basic concepts

Custom resources enable you to create resources with custom provisioning logic. This enables users to specify execution logic for resource creation, update, and deletion. This enables 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

Common use cases for custom resources:

  1. 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.

  2. Performing provisioning steps not related to infrastructure - Deployment of infrastructure is only a part of having the application successfully running. You might need to seed the database or run migrations, both of which are a great fit for a custom resource. Another example might be running a smoke test from your custom resource to ensure that everything is running correctly after deployment.

  3. Any other use case - Since you are fully in control of the code and therefore of what the custom resource does, there is virtually no limit of how to use custom resources. Custom resources can be powerful, but we advise users to be reasonable with them.

Usage

In Stacktape, custom resources are made of two parts:

  1. 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.

  2. 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.

resources:
myMongoCluster:
type: mongo-db-atlas-cluster
properties:
clusterTier: M10
mongoSeeder:
type: custom-resource-definition
properties:
packageConfig:
filePath: seed-the-mongo-cluster.ts
accessControl:
allowAccessTo:
- myMongoCluster
seedMongoCluster:
type: custom-resource-instance
properties:
definitionName: mongoSeeder
resourceProperties:
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.

CustomResourceDefinition  API reference
type
Required

No description

Type: string "custom-resource-definition"

properties.environment

Environment variables injected to the batch job's environment

Type: Array of EnvironmentVar

  • Environment variables are often used to inject information about other parts of the infrastrucutre (such as database URLs, secrets, etc.).

properties.runtime

Runtime used to execute the custom resource lambda function

Type: string ENUM

Possible values: dotnetcore2.1go1.xjava11java8nodejs12.xnodejs14.xpython2.7python3.6python3.7python3.8ruby2.5

  • Stacktape automatically detects the function's language uses the latest runtime version associated with that language
  • Example: uses nodejs14.x for all files ending with .js and .ts
  • You might want to use an older version if some of your dependencies are not not compatible with the default runtime version

properties.timeout
Default: 10

Maximum amount of time (in seconds) the custom resource lambda function is allowed to run

Type: number

Maximum allowed time is 900 seconds.

properties.memory

Amount of memory (in MB) available to the function during execution

Type: number

  • Must be between 128 MB and 10,240 MB in 1-MB increments.
  • Amount of CPU power available to the function is also set using memory property - it's proportionate to the amount of available memory. Function with 1797MB has a CPU power equal to 1 virtual CPU. Lambda function can have a maximum of 6 vCPUs (at 10,240 MB of RAM).

properties.accessControl

Configures access to other resources of your stack (such as relational-databases, buckets, event-buses, etc.).

Type: AccessControl

overrides

Overrides one or more properties of the specified child resource.

Type: Object

  • Child resouces are specified using their descriptive name (e.g. DbInstance or Events.0.HttpApiRoute).
  • To see all configurable child resources for given Stacktape resource, use stacktape stack-info --detailed command.
  • To see the list of properties that can be overriden, refer to AWS Cloudformation docs.

resources:
myMongoCluster:
type: mongo-db-atlas-cluster
properties:
clusterTier: M10
mongoSeeder:
type: custom-resource-definition
properties:
packageConfig:
filePath: seed-the-mongo-cluster.ts
accessControl:
allowAccessTo:
- myMongoCluster
seedMongoCluster:
type: custom-resource-instance
properties:
definitionName: mongoSeeder
resourceProperties:
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.

LambdaPackageConfig  API reference
Parent API reference: CustomResourceDefinition
filePath
Required

Path to the entry point of your workload (relative to the stacktape config file)

Type: string

  • Stacktape tries to bundle all your source code with its dependencies into a single file.
  • If a certain dependency doesn't support static bundling (because it has binary, uses dynamic require() calls, etc.), Stacktape will install it copy it to the bundle

languageSpecificConfig

Configuration of packaging properties specific to given language

Type: LambdaLanguageSpecificConfig

handler

Exported function to use as the handler for you lambda function

Type: string

includeFiles

Files that should be explicitly included in the deployment package (glob pattern)

Type: Array of string

  • Example glob pattern: images/*.jpg

excludeFiles

Files that should be explicitly excluded from deployment package (glob pattern)

Type: Array of string

Example glob pattern: images/*.jpg

dependenciesToIgnore

Dependencies to ignore.

Type: Array of string

  • These dependencies won't be a part of your deployment package.

resources:
myMongoCluster:
type: mongo-db-atlas-cluster
properties:
clusterTier: M10
mongoSeeder:
type: custom-resource-definition
properties:
packageConfig:
filePath: seed-the-mongo-cluster.ts
accessControl:
allowAccessTo:
- myMongoCluster
seedMongoCluster:
type: custom-resource-instance
properties:
definitionName: mongoSeeder
resourceProperties:
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

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 code
let success = true;
let dataToReturn = {};
try {
// we are only seeding database if the operation is Create
if (event.RequestType === 'Create') {
// we are using the "mongoConnectionString" property passed by custom-resource-instance to create connection
const 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.

AccessControl  API reference
Parent API reference: CustomResourceDefinition
iamRoleStatements

Raw AWS IAM role statements appended to your resources's role.

Type: Array of StpIamRoleStatement

allowAccessTo

Names of the resources that will recieve basic permissions.

Type: Array of string

Granted permissions:

Bucket

  • list objects in a bucket
  • create / get / delete / tag object in a bucket

DynamoDb Table

  • get / put / update / delete item in a table
  • scan / query a table
  • describe table stream

MongoDb Atlas Cluster

  • Allows connection to a cluster with accessibilityMode set to scoping-workloads-in-vpc. To learn more about MongoDb Atlas clusters accessibility modes, refer to MongoDB Atlas cluster docs.

Relational database

  • Allows connection to a relational database with accessibilityMode set to scoping-workloads-in-vpc. To learn more about relational database accessibility modes, refer to Relational databases docs.

Redis cluster

  • Allows connection to a redis cluster with accessibilityMode set to scoping-workloads-in-vpc. To learn more about redis cluster accessibility modes, refer to Redis clusters docs.

Event bus

  • publish events to the specified Event bus

Function

  • invoke the specified function

Batch job

  • submit batch-job instance into batch-job queue
  • list submitted job instances in a batch-job queue
  • describe / terminate a batch-job instance
  • list executions of state machine which executes the batch-job according to its strategy
  • start / terminate execution of a state machine which executes the batch-job according to its strategy

In this example, we are granting access to the mongo cluster. This will inject credentials for access which are used in code.

resources:
myMongoCluster:
type: mongo-db-atlas-cluster
properties:
clusterTier: M10
mongoSeeder:
type: custom-resource-definition
properties:
packageConfig:
filePath: seed-the-mongo-cluster.ts
accessControl:
allowAccessTo:
- myMongoCluster
seedMongoCluster:
type: custom-resource-instance
properties:
definitionName: mongoSeeder
resourceProperties:
mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')

StpIamRoleStatement  API reference
Parent API reference: AccessControl
Resource
Required

List of resources we want to access

Type: Array of string

  • See AWS reference here.

Sid

Statement identifier.

Type: string

  • See AWS reference here.

Effect

Effect of the statement

Type: string

  • See AWS reference here.

Action

List of actions allowed/denied by the statement

Type: Array of string

see AWS reference here.

Condition

No description

Type: UNSPECIFIED

Environment

Use environment variables for injecting information that resource needs during code execution.

EnvironmentVar  API reference
Parent API reference: CustomResourceDefinition
name
Required

Name of the environment variable

Type: string

value
Required

Value of the environment variable

Type: (string or number or boolean)

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.

resources:
myMongoCluster:
type: mongo-db-atlas-cluster
properties:
clusterTier: M10
mongoSeeder:
type: custom-resource-definition
properties:
packageConfig:
filePath: seed-the-mongo-cluster.ts
accessControl:
allowAccessTo:
- myMongoCluster
seedMongoCluster:
type: custom-resource-instance
properties:
definitionName: mongoSeeder
resourceProperties:
mongoConnectionString: $Param('myMongoCluster', 'AtlasMongoCluster::SrvConnectionString')

CustomResourceInstance  API reference
type
Required

No description

Type: string "custom-resource-instance"

properties.definitionName
Required

Name of the custom-resource-definition to use

Type: string

properties.resourceProperties
Required

Properties passed to the custom resource instance

Type: Object

  • These properties will be accessible to the custom resource lambda function during execution

overrides

Overrides one or more properties of the specified child resource.

Type: Object

  • Child resouces are specified using their descriptive name (e.g. DbInstance or Events.0.HttpApiRoute).
  • To see all configurable child resources for given Stacktape resource, use stacktape stack-info --detailed command.
  • To see the list of properties that can be overriden, refer to AWS Cloudformation docs.

API reference

LambdaLanguageSpecificConfig  API reference
Parent API reference: LambdaPackageConfig
tsConfigPath

Path to tsconfig.json file to use.

Type: string

This is used mostly to resolve path aliases.

emitTsDecoratorMetadata

Emits decorator metadata to the final bundle.

Type: boolean

  • This is used by frameworks like NestJS or ORMs like TypeORM.
  • This is not turned on by default, because it can slow down the build process.

dependenciesToExcludeFromBundle

Dependencies to exclude from main bundle.

Type: Array of string

  • These dependencies will be treated as external and won't be statically built into the main bundle
  • Instead, they will be installed and copied to the deployment package.
  • Using * means all of the workload's dependencies will be treated as external