Skip to content

Commit 0e71d92

Browse files
author
Kolomiets
committed
Initial version of the library
1 parent b119e50 commit 0e71d92

15 files changed

+615
-1
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
lib

.github/workflows/build.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [12.x]
16+
17+
steps:
18+
- uses: actions/checkout@v2
19+
- name: Use Node.js ${{ matrix.node-version }}
20+
uses: actions/setup-node@v1
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
24+
- run: npm install
25+
- run: npm run build --if-present
26+
- run: npm test
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: jsii-pre-publish checks
2+
3+
on:
4+
push:
5+
branches:
6+
- "*"
7+
tags-ignore:
8+
- "v*"
9+
pull_request:
10+
branches: [ master ]
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
strategy:
18+
matrix:
19+
node-version: [12.x]
20+
21+
steps:
22+
- uses: actions/checkout@v2
23+
with:
24+
fetch-depth: 1
25+
26+
- name: Use Node.js ${{ matrix.node-version }}
27+
uses: actions/setup-node@v1
28+
with:
29+
node-version: ${{ matrix.node-version }}
30+
31+
- run: make

.github/workflows/jsii-publish.yml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: jsii-publish
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
build:
10+
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [12.x]
16+
17+
steps:
18+
- uses: actions/checkout@v2
19+
with:
20+
fetch-depth: 1
21+
22+
- name: Use Node.js ${{ matrix.node-version }}
23+
uses: actions/setup-node@v1
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
27+
- run: make
28+
29+
- run: make publish-npm
30+
env:
31+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
32+
33+
- run: make publish-pypi
34+
env:
35+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
36+
37+
- run: make publish-nuget
38+
env:
39+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
40+
41+
# - run: make publish-maven
42+
# env:
43+
# MAVEN_STAGING_PROFILE_ID: ${{ secrets.MAVEN_STAGING_PROFILE_ID }}
44+
# MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
45+
# MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
46+
# MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
47+
# MAVEN_GPG_PRIVATE_KEY_PASSPHRASE: ${{ secrets.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE }}

.gitignore

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# npm
2+
node_modules/
3+
.npm
4+
*.tgz
5+
6+
# code coverage
7+
coverage/
8+
9+
# jsii
10+
tsconfig.json
11+
.jsii
12+
dist/
13+
*.js
14+
*.d.ts
15+
.cdk.staging
16+
17+
lib/resources/

.npmignore

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
# Exclude typescript source and config
3+
*.ts
4+
tsconfig.json
5+
6+
# Include javascript files and typescript declarations
7+
!*.js
8+
!*.d.ts
9+
10+
# Exclude jsii outdir
11+
dist
12+
13+
# Exclude lambda dir
14+
lambda
15+
16+
# Include .jsii
17+
!.jsii

Makefile

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
SHELL := /bin/bash -o pipefail
2+
3+
DOCKER_IMAGE := jsii/superchain
4+
DOCKER_TAG := latest
5+
DOCKER_WORKDIR := /workdir
6+
7+
build:
8+
docker run \
9+
--workdir ${DOCKER_WORKDIR} \
10+
--volume ${PWD}:${DOCKER_WORKDIR} \
11+
${DOCKER_IMAGE}:${DOCKER_TAG} \
12+
/bin/bash -c "rm -rf dist && npm i && npm run package"
13+
14+
publish-npm:
15+
docker run \
16+
--workdir ${DOCKER_WORKDIR} \
17+
--volume ${PWD}:${DOCKER_WORKDIR} \
18+
--env NPM_TOKEN \
19+
${DOCKER_IMAGE}:${DOCKER_TAG} \
20+
/bin/bash -c "npx jsii-release-npm dist/js"
21+
22+
publish-nuget:
23+
docker run \
24+
--workdir ${DOCKER_WORKDIR} \
25+
--volume ${PWD}:${DOCKER_WORKDIR} \
26+
--env NUGET_API_KEY \
27+
${DOCKER_IMAGE}:${DOCKER_TAG} \
28+
/bin/bash -c "npx jsii-release-nuget dist/dotnet"
29+
30+
publish-pypi:
31+
docker run \
32+
--workdir ${DOCKER_WORKDIR} \
33+
--volume ${PWD}:${DOCKER_WORKDIR} \
34+
--env TWINE_USERNAME=__token__ \
35+
--env TWINE_PASSWORD=$(PYPI_TOKEN) \
36+
${DOCKER_IMAGE}:${DOCKER_TAG} \
37+
/bin/bash -c "npx jsii-release-pypi dist/python"
38+
39+
publish-maven:
40+
docker run \
41+
--workdir ${DOCKER_WORKDIR} \
42+
--volume ${PWD}:${DOCKER_WORKDIR} \
43+
--env MAVEN_STAGING_PROFILE_ID \
44+
--env MAVEN_USERNAME \
45+
--env MAVEN_PASSWORD \
46+
--env MAVEN_GPG_PRIVATE_KEY \
47+
--env MAVEN_GPG_PRIVATE_KEY_PASSPHRASE \
48+
--env MAVEN_DRYRUN \
49+
${DOCKER_IMAGE}:${DOCKER_TAG} \
50+
/bin/bash -c "npx jsii-release-maven dist/java"

README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
# cdk-stepfunctions-patterns
2-
A set of Step Functions high-level patterns.
2+
![build](https://github.com/kolomied/cdk-stepfunctions-patterns/workflows/build/badge.svg)
3+
![jsii-publish](https://github.com/kolomied/cdk-stepfunctions-patterns/workflows/jsii-publish/badge.svg)
4+
![downloads](https://img.shields.io/npm/dt/cdk-stepfunctions-patterns)
5+
6+
[![npm version](https://badge.fury.io/js/cdk-stepfunctions-patterns.svg)](https://badge.fury.io/js/cdk-stepfunctions-patterns)
7+
[![PyPI version](https://badge.fury.io/py/cdk-stepfunctions-patterns.svg)](https://badge.fury.io/py/cdk-stepfunctions-patterns)
8+
[![NuGet version](https://badge.fury.io/nu/Talnakh.StepFunctions.Patterns.svg)](https://badge.fury.io/nu/Talnakh.StepFunctions.Patterns)
9+
10+
A simple CDK seeder for SQL Server RDS databases.
11+
12+
*cdk-stepfunctions-patterns* library is a set of [AWS CDK](https://aws.amazon.com/cdk/) constructs that provide
13+
resiliency patterns implementation for AWS Step Functions.
14+

lambda/jitter/main.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import random
2+
3+
class Backoff:
4+
def __init__(self, base, backoff, cap):
5+
self.base = base
6+
self.backoff = backoff
7+
self.cap = cap
8+
9+
def expo(self, n):
10+
return min(self.cap, pow(self.backoff, n) * self.base)
11+
12+
def backoff(self, n):
13+
v = self.expo(n)
14+
return random.uniform(0, v)
15+
16+
def decor(self, n):
17+
sleep = self.backoff(n)
18+
return min(cap, random.uniform(self.base, sleep * 3))
19+
20+
def lambda_handler(event, context):
21+
n = event.get("RetryCount", 0)
22+
base = event.get("Interval", 1)
23+
backoff = event.get("Backoff", 2)
24+
25+
fullBackoff = Backoff(base, backoff, 200)
26+
return round(fullBackoff.backoff(n))

lib/aspect/ResilienceLambdaChecker.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';
3+
import { RetryProps } from '@aws-cdk/aws-stepfunctions'
4+
import { ResilientLambdaTask } from '../construct/ResilientLambdaTask'
5+
6+
export interface ResilienceLambdaCheckerProps {
7+
readonly fail?: boolean;
8+
}
9+
10+
export class ResilienceLambdaChecker implements cdk.IAspect {
11+
12+
private readonly _fail?: boolean;
13+
14+
constructor(props?: ResilienceLambdaCheckerProps) {
15+
this._fail = props?.fail
16+
}
17+
18+
public visit(construct: cdk.IConstruct): void {
19+
20+
if (construct instanceof tasks.LambdaInvoke) {
21+
const reporter = this._fail ? construct.node.addError : construct.node.addWarning;
22+
23+
const retries = this.getRetryConfiguration(construct);
24+
if (retries.length > 0) {
25+
const unhandledErrors = this.getUnhandledTransientErrors(retries);
26+
27+
if (unhandledErrors.length > 0) {
28+
reporter.apply(construct.node, [`Missing retry for transient errors: ${unhandledErrors}.`]);
29+
}
30+
} else {
31+
reporter.apply(construct.node, ['No retry for AWS Lambda transient errors defined - consider using ResilientLambdaTask construct.']);
32+
//ResilientLambdaTask.addDefaultRetry(construct);
33+
}
34+
}
35+
}
36+
37+
private getUnhandledTransientErrors(retries: Array<RetryProps>): Array<string> {
38+
return ResilientLambdaTask.TransientErrors.filter(transientError =>
39+
retries.every(config => !config.errors?.includes(transientError)));
40+
}
41+
42+
private getRetryConfiguration(construct: cdk.IConstruct): Array<RetryProps> {
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44+
return (construct as any).retries as Array<RetryProps> || []
45+
}
46+
}

lib/construct/ResilientLambdaTask.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';
3+
4+
export class ResilientLambdaTask extends tasks.LambdaInvoke {
5+
6+
public static readonly TransientErrors: string[] = [
7+
"Lambda.ServiceException",
8+
"Lambda.AWSLambdaException",
9+
"Lambda.SdkClientException",
10+
"Lambda.TooManyRequestsException"
11+
]
12+
13+
constructor(scope: cdk.Construct, id: string, props: tasks.LambdaInvokeProps) {
14+
super(scope, id, props)
15+
ResilientLambdaTask.addDefaultRetry(this);
16+
}
17+
18+
public static addDefaultRetry(task: tasks.LambdaInvoke): void {
19+
// https://docs.aws.amazon.com/step-functions/latest/dg/bp-lambda-serviceexception.html
20+
task.addRetry({
21+
errors: ResilientLambdaTask.TransientErrors,
22+
backoffRate: 2,
23+
maxAttempts: 6,
24+
interval: cdk.Duration.seconds(2)
25+
});
26+
}
27+
}

0 commit comments

Comments
 (0)