├── .eslintrc.json ├── .gitattributes ├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── pull-request-lint.yml │ └── release.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── state-machine.iml └── vcs.xml ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── API.md ├── LICENSE ├── README.md ├── package.json ├── src ├── BuildStateType.ts ├── StateMachine.ts ├── StepFunctionsAutoDiscover.ts └── index.ts ├── test ├── StateMachine.test.ts ├── StateMachineAutoDiscover.test.ts ├── __snapshots__ │ ├── StateMachine.test.ts.snap │ └── StateMachineAutoDiscover.test.ts.snap ├── sample.json └── step-functions │ ├── test.json.asl │ ├── test.workflow.json │ ├── test.yaml.asl │ └── test2.workflow.json ├── tsconfig.dev.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module", 16 | "project": "./tsconfig.dev.json" 17 | }, 18 | "extends": [ 19 | "plugin:import/typescript" 20 | ], 21 | "settings": { 22 | "import/parsers": { 23 | "@typescript-eslint/parser": [ 24 | ".ts", 25 | ".tsx" 26 | ] 27 | }, 28 | "import/resolver": { 29 | "node": {}, 30 | "typescript": { 31 | "project": "./tsconfig.dev.json", 32 | "alwaysTryTypes": true 33 | } 34 | } 35 | }, 36 | "ignorePatterns": [ 37 | "*.js", 38 | "*.d.ts", 39 | "node_modules/", 40 | "*.generated.ts", 41 | "coverage", 42 | "!.projenrc.ts", 43 | "!projenrc/**/*.ts" 44 | ], 45 | "rules": { 46 | "indent": [ 47 | "off" 48 | ], 49 | "@typescript-eslint/indent": [ 50 | "error", 51 | 2 52 | ], 53 | "quotes": [ 54 | "error", 55 | "single", 56 | { 57 | "avoidEscape": true 58 | } 59 | ], 60 | "comma-dangle": [ 61 | "error", 62 | "always-multiline" 63 | ], 64 | "comma-spacing": [ 65 | "error", 66 | { 67 | "before": false, 68 | "after": true 69 | } 70 | ], 71 | "no-multi-spaces": [ 72 | "error", 73 | { 74 | "ignoreEOLComments": false 75 | } 76 | ], 77 | "array-bracket-spacing": [ 78 | "error", 79 | "never" 80 | ], 81 | "array-bracket-newline": [ 82 | "error", 83 | "consistent" 84 | ], 85 | "object-curly-spacing": [ 86 | "error", 87 | "always" 88 | ], 89 | "object-curly-newline": [ 90 | "error", 91 | { 92 | "multiline": true, 93 | "consistent": true 94 | } 95 | ], 96 | "object-property-newline": [ 97 | "error", 98 | { 99 | "allowAllPropertiesOnSameLine": true 100 | } 101 | ], 102 | "keyword-spacing": [ 103 | "error" 104 | ], 105 | "brace-style": [ 106 | "error", 107 | "1tbs", 108 | { 109 | "allowSingleLine": true 110 | } 111 | ], 112 | "space-before-blocks": [ 113 | "error" 114 | ], 115 | "curly": [ 116 | "error", 117 | "multi-line", 118 | "consistent" 119 | ], 120 | "@typescript-eslint/member-delimiter-style": [ 121 | "error" 122 | ], 123 | "semi": [ 124 | "error", 125 | "always" 126 | ], 127 | "max-len": [ 128 | "error", 129 | { 130 | "code": 150, 131 | "ignoreUrls": true, 132 | "ignoreStrings": true, 133 | "ignoreTemplateLiterals": true, 134 | "ignoreComments": true, 135 | "ignoreRegExpLiterals": true 136 | } 137 | ], 138 | "quote-props": [ 139 | "error", 140 | "consistent-as-needed" 141 | ], 142 | "@typescript-eslint/no-require-imports": [ 143 | "error" 144 | ], 145 | "import/no-extraneous-dependencies": [ 146 | "error", 147 | { 148 | "devDependencies": [ 149 | "**/test/**", 150 | "**/build-tools/**", 151 | ".projenrc.ts", 152 | "projenrc/**/*.ts" 153 | ], 154 | "optionalDependencies": false, 155 | "peerDependencies": true 156 | } 157 | ], 158 | "import/no-unresolved": [ 159 | "error" 160 | ], 161 | "import/order": [ 162 | "warn", 163 | { 164 | "groups": [ 165 | "builtin", 166 | "external" 167 | ], 168 | "alphabetize": { 169 | "order": "asc", 170 | "caseInsensitive": true 171 | } 172 | } 173 | ], 174 | "import/no-duplicates": [ 175 | "error" 176 | ], 177 | "no-shadow": [ 178 | "off" 179 | ], 180 | "@typescript-eslint/no-shadow": [ 181 | "error" 182 | ], 183 | "key-spacing": [ 184 | "error" 185 | ], 186 | "no-multiple-empty-lines": [ 187 | "error" 188 | ], 189 | "@typescript-eslint/no-floating-promises": [ 190 | "error" 191 | ], 192 | "no-return-await": [ 193 | "off" 194 | ], 195 | "@typescript-eslint/return-await": [ 196 | "error" 197 | ], 198 | "no-trailing-spaces": [ 199 | "error" 200 | ], 201 | "dot-notation": [ 202 | "error" 203 | ], 204 | "no-bitwise": [ 205 | "error" 206 | ], 207 | "@typescript-eslint/member-ordering": [ 208 | "error", 209 | { 210 | "default": [ 211 | "public-static-field", 212 | "public-static-method", 213 | "protected-static-field", 214 | "protected-static-method", 215 | "private-static-field", 216 | "private-static-method", 217 | "field", 218 | "constructor", 219 | "method" 220 | ] 221 | } 222 | ] 223 | }, 224 | "overrides": [ 225 | { 226 | "files": [ 227 | ".projenrc.ts" 228 | ], 229 | "rules": { 230 | "@typescript-eslint/no-require-imports": "off", 231 | "import/no-extraneous-dependencies": "off" 232 | } 233 | } 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/build.yml linguist-generated 9 | /.github/workflows/pull-request-lint.yml linguist-generated 10 | /.github/workflows/release.yml linguist-generated 11 | /.gitignore linguist-generated 12 | /.mergify.yml linguist-generated 13 | /.npmignore linguist-generated 14 | /.projen/** linguist-generated 15 | /.projen/deps.json linguist-generated 16 | /.projen/files.json linguist-generated 17 | /.projen/tasks.json linguist-generated 18 | /API.md linguist-generated 19 | /LICENSE linguist-generated 20 | /package.json linguist-generated 21 | /tsconfig.dev.json linguist-generated 22 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mbonig] 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: lts/* 26 | - name: Install dependencies 27 | run: yarn install --check-files 28 | - name: build 29 | run: npx projen build 30 | - name: Find mutations 31 | id: self_mutation 32 | run: |- 33 | git add . 34 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.self_mutation.outputs.self_mutation_happened 38 | uses: actions/upload-artifact@v4.3.6 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | - name: Fail build on mutation 44 | if: steps.self_mutation.outputs.self_mutation_happened 45 | run: |- 46 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 47 | cat repo.patch 48 | exit 1 49 | - name: Backup artifact permissions 50 | run: cd dist && getfacl -R . > permissions-backup.acl 51 | continue-on-error: true 52 | - name: Upload artifact 53 | uses: actions/upload-artifact@v4.3.6 54 | with: 55 | name: build-artifact 56 | path: dist 57 | overwrite: true 58 | self-mutation: 59 | needs: build 60 | runs-on: ubuntu-latest 61 | permissions: 62 | contents: write 63 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v4 67 | with: 68 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 69 | ref: ${{ github.event.pull_request.head.ref }} 70 | repository: ${{ github.event.pull_request.head.repo.full_name }} 71 | - name: Download patch 72 | uses: actions/download-artifact@v4 73 | with: 74 | name: repo.patch 75 | path: ${{ runner.temp }} 76 | - name: Apply patch 77 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 78 | - name: Set git identity 79 | run: |- 80 | git config user.name "github-actions" 81 | git config user.email "github-actions@github.com" 82 | - name: Push changes 83 | env: 84 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 85 | run: |- 86 | git add . 87 | git commit -s -m "chore: self mutation" 88 | git push origin HEAD:$PULL_REQUEST_REF 89 | package-js: 90 | needs: build 91 | runs-on: ubuntu-latest 92 | permissions: 93 | contents: read 94 | if: ${{ !needs.build.outputs.self_mutation_happened }} 95 | steps: 96 | - uses: actions/setup-node@v4 97 | with: 98 | node-version: lts/* 99 | - name: Download build artifacts 100 | uses: actions/download-artifact@v4 101 | with: 102 | name: build-artifact 103 | path: dist 104 | - name: Restore build artifact permissions 105 | run: cd dist && setfacl --restore=permissions-backup.acl 106 | continue-on-error: true 107 | - name: Checkout 108 | uses: actions/checkout@v4 109 | with: 110 | ref: ${{ github.event.pull_request.head.ref }} 111 | repository: ${{ github.event.pull_request.head.repo.full_name }} 112 | path: .repo 113 | - name: Install Dependencies 114 | run: cd .repo && yarn install --check-files --frozen-lockfile 115 | - name: Extract build artifact 116 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 117 | - name: Move build artifact out of the way 118 | run: mv dist dist.old 119 | - name: Create js artifact 120 | run: cd .repo && npx projen package:js 121 | - name: Collect js artifact 122 | run: mv .repo/dist dist 123 | package-python: 124 | needs: build 125 | runs-on: ubuntu-latest 126 | permissions: 127 | contents: read 128 | if: ${{ !needs.build.outputs.self_mutation_happened }} 129 | steps: 130 | - uses: actions/setup-node@v4 131 | with: 132 | node-version: lts/* 133 | - uses: actions/setup-python@v5 134 | with: 135 | python-version: 3.x 136 | - name: Download build artifacts 137 | uses: actions/download-artifact@v4 138 | with: 139 | name: build-artifact 140 | path: dist 141 | - name: Restore build artifact permissions 142 | run: cd dist && setfacl --restore=permissions-backup.acl 143 | continue-on-error: true 144 | - name: Checkout 145 | uses: actions/checkout@v4 146 | with: 147 | ref: ${{ github.event.pull_request.head.ref }} 148 | repository: ${{ github.event.pull_request.head.repo.full_name }} 149 | path: .repo 150 | - name: Install Dependencies 151 | run: cd .repo && yarn install --check-files --frozen-lockfile 152 | - name: Extract build artifact 153 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 154 | - name: Move build artifact out of the way 155 | run: mv dist dist.old 156 | - name: Create python artifact 157 | run: cd .repo && npx projen package:python 158 | - name: Collect python artifact 159 | run: mv .repo/dist dist 160 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | jobs: 14 | validate: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: write 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v5.4.0 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | types: |- 25 | feat 26 | fix 27 | chore 28 | requireScope: false 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions" 30 | git config user.email "github-actions@github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: lts/* 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release 38 | run: npx projen release 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | - name: Backup artifact permissions 51 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 52 | run: cd dist && getfacl -R . > permissions-backup.acl 53 | continue-on-error: true 54 | - name: Upload artifact 55 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 56 | uses: actions/upload-artifact@v4.3.6 57 | with: 58 | name: build-artifact 59 | path: dist 60 | overwrite: true 61 | release_github: 62 | name: Publish to GitHub Releases 63 | needs: 64 | - release 65 | - release_npm 66 | - release_pypi 67 | runs-on: ubuntu-latest 68 | permissions: 69 | contents: write 70 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 71 | steps: 72 | - uses: actions/setup-node@v4 73 | with: 74 | node-version: lts/* 75 | - name: Download build artifacts 76 | uses: actions/download-artifact@v4 77 | with: 78 | name: build-artifact 79 | path: dist 80 | - name: Restore build artifact permissions 81 | run: cd dist && setfacl --restore=permissions-backup.acl 82 | continue-on-error: true 83 | - name: Release 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | GITHUB_REPOSITORY: ${{ github.repository }} 87 | GITHUB_REF: ${{ github.sha }} 88 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_REF 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 89 | release_npm: 90 | name: Publish to npm 91 | needs: release 92 | runs-on: ubuntu-latest 93 | permissions: 94 | id-token: write 95 | contents: read 96 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 97 | steps: 98 | - uses: actions/setup-node@v4 99 | with: 100 | node-version: lts/* 101 | - name: Download build artifacts 102 | uses: actions/download-artifact@v4 103 | with: 104 | name: build-artifact 105 | path: dist 106 | - name: Restore build artifact permissions 107 | run: cd dist && setfacl --restore=permissions-backup.acl 108 | continue-on-error: true 109 | - name: Checkout 110 | uses: actions/checkout@v4 111 | with: 112 | path: .repo 113 | - name: Install Dependencies 114 | run: cd .repo && yarn install --check-files --frozen-lockfile 115 | - name: Extract build artifact 116 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 117 | - name: Move build artifact out of the way 118 | run: mv dist dist.old 119 | - name: Create js artifact 120 | run: cd .repo && npx projen package:js 121 | - name: Collect js artifact 122 | run: mv .repo/dist dist 123 | - name: Release 124 | env: 125 | NPM_DIST_TAG: latest 126 | NPM_REGISTRY: registry.npmjs.org 127 | NPM_CONFIG_PROVENANCE: "true" 128 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 129 | run: npx -p publib@latest publib-npm 130 | release_pypi: 131 | name: Publish to PyPI 132 | needs: release 133 | runs-on: ubuntu-latest 134 | permissions: 135 | contents: read 136 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 137 | steps: 138 | - uses: actions/setup-node@v4 139 | with: 140 | node-version: lts/* 141 | - uses: actions/setup-python@v5 142 | with: 143 | python-version: 3.x 144 | - name: Download build artifacts 145 | uses: actions/download-artifact@v4 146 | with: 147 | name: build-artifact 148 | path: dist 149 | - name: Restore build artifact permissions 150 | run: cd dist && setfacl --restore=permissions-backup.acl 151 | continue-on-error: true 152 | - name: Checkout 153 | uses: actions/checkout@v4 154 | with: 155 | path: .repo 156 | - name: Install Dependencies 157 | run: cd .repo && yarn install --check-files --frozen-lockfile 158 | - name: Extract build artifact 159 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 160 | - name: Move build artifact out of the way 161 | run: mv dist dist.old 162 | - name: Create python artifact 163 | run: cd .repo && npx projen package:python 164 | - name: Collect python artifact 165 | run: mv .repo/dist dist 166 | - name: Release 167 | env: 168 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 169 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 170 | run: npx -p publib@latest publib-pypi 171 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/package.json 8 | !/LICENSE 9 | !/.npmignore 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | lib-cov 22 | coverage 23 | *.lcov 24 | .nyc_output 25 | build/Release 26 | node_modules/ 27 | jspm_packages/ 28 | *.tsbuildinfo 29 | .eslintcache 30 | *.tgz 31 | .yarn-integrity 32 | .cache 33 | .idea/ 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.mergify.yml 42 | !/.github/pull_request_template.md 43 | !/test/ 44 | !/tsconfig.dev.json 45 | !/src/ 46 | /lib 47 | /dist/ 48 | !/.eslintrc.json 49 | .jsii 50 | tsconfig.json 51 | !/API.md 52 | !/.projenrc.ts 53 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 21 | 22 | 32 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/state-machine.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-python 12 | pull_request_rules: 13 | - name: Automatic merge on approval and successful build 14 | actions: 15 | delete_head_branch: {} 16 | queue: 17 | method: squash 18 | name: default 19 | commit_message_template: |- 20 | {{ title }} (#{{ number }}) 21 | 22 | {{ body }} 23 | conditions: 24 | - "#approved-reviews-by>=1" 25 | - -label~=(do-not-merge) 26 | - status-success=build 27 | - status-success=package-js 28 | - status-success=package-python 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /test/ 11 | /tsconfig.dev.json 12 | /src/ 13 | !/lib/ 14 | !/lib/**/*.js 15 | !/lib/**/*.d.ts 16 | dist 17 | /tsconfig.json 18 | /.github/ 19 | /.vscode/ 20 | /.idea/ 21 | /.projenrc.js 22 | tsconfig.tsbuildinfo 23 | /.eslintrc.json 24 | !.jsii 25 | /.gitattributes 26 | /.projenrc.ts 27 | /projenrc 28 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@matthewbonig/cdk-construct-library", 5 | "version": "0.0.14", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/jest", 10 | "version": "28", 11 | "type": "build" 12 | }, 13 | { 14 | "name": "@types/js-yaml", 15 | "type": "build" 16 | }, 17 | { 18 | "name": "@types/node", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/eslint-plugin", 23 | "version": "^7", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "@typescript-eslint/parser", 28 | "version": "^7", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "commit-and-tag-version", 33 | "version": "^12", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "eslint-import-resolver-typescript", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "eslint-plugin-import", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "eslint", 46 | "version": "^8", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "jest-junit", 51 | "version": "^15", 52 | "type": "build" 53 | }, 54 | { 55 | "name": "jest", 56 | "version": "28", 57 | "type": "build" 58 | }, 59 | { 60 | "name": "jsii-diff", 61 | "type": "build" 62 | }, 63 | { 64 | "name": "jsii-docgen", 65 | "version": "^10.5.0", 66 | "type": "build" 67 | }, 68 | { 69 | "name": "jsii-pacmak", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii-rosetta", 74 | "version": "~5.5.0", 75 | "type": "build" 76 | }, 77 | { 78 | "name": "jsii", 79 | "version": "~5.5.0", 80 | "type": "build" 81 | }, 82 | { 83 | "name": "projen", 84 | "version": "~0.88.2", 85 | "type": "build" 86 | }, 87 | { 88 | "name": "ts-jest", 89 | "version": "28", 90 | "type": "build" 91 | }, 92 | { 93 | "name": "ts-node", 94 | "type": "build" 95 | }, 96 | { 97 | "name": "typescript", 98 | "type": "build" 99 | }, 100 | { 101 | "name": "case", 102 | "type": "bundled" 103 | }, 104 | { 105 | "name": "js-yaml", 106 | "type": "bundled" 107 | }, 108 | { 109 | "name": "lodash.merge", 110 | "type": "bundled" 111 | }, 112 | { 113 | "name": "aws-cdk-lib", 114 | "version": "^2.85.0", 115 | "type": "peer" 116 | }, 117 | { 118 | "name": "constructs", 119 | "version": "^10.3.0", 120 | "type": "peer" 121 | }, 122 | { 123 | "name": "projen", 124 | "version": "^0.88.2", 125 | "type": "peer" 126 | }, 127 | { 128 | "name": "case", 129 | "type": "runtime" 130 | }, 131 | { 132 | "name": "js-yaml", 133 | "type": "runtime" 134 | }, 135 | { 136 | "name": "lodash.merge", 137 | "type": "runtime" 138 | }, 139 | { 140 | "name": "projen", 141 | "version": "^0.88.2", 142 | "type": "runtime" 143 | } 144 | ], 145 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 146 | } 147 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/build.yml", 7 | ".github/workflows/pull-request-lint.yml", 8 | ".github/workflows/release.yml", 9 | ".gitignore", 10 | ".mergify.yml", 11 | ".projen/deps.json", 12 | ".projen/files.json", 13 | ".projen/tasks.json", 14 | "LICENSE", 15 | "tsconfig.dev.json" 16 | ], 17 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 18 | } 19 | -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "", 36 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 37 | }, 38 | "steps": [ 39 | { 40 | "builtin": "release/bump-version" 41 | } 42 | ], 43 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 44 | }, 45 | "clobber": { 46 | "name": "clobber", 47 | "description": "hard resets to HEAD of origin and cleans the local repo", 48 | "env": { 49 | "BRANCH": "$(git branch --show-current)" 50 | }, 51 | "steps": [ 52 | { 53 | "exec": "git checkout -b scratch", 54 | "name": "save current HEAD in \"scratch\" branch" 55 | }, 56 | { 57 | "exec": "git checkout $BRANCH" 58 | }, 59 | { 60 | "exec": "git fetch origin", 61 | "name": "fetch latest changes from origin" 62 | }, 63 | { 64 | "exec": "git reset --hard origin/$BRANCH", 65 | "name": "hard reset to origin commit" 66 | }, 67 | { 68 | "exec": "git clean -fdx", 69 | "name": "clean all untracked files" 70 | }, 71 | { 72 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 73 | } 74 | ], 75 | "condition": "git diff --exit-code > /dev/null" 76 | }, 77 | "compat": { 78 | "name": "compat", 79 | "description": "Perform API compatibility check against latest version", 80 | "steps": [ 81 | { 82 | "exec": "jsii-diff npm:$(node -p \"require('./package.json').name\") -k --ignore-file .compatignore || (echo \"\nUNEXPECTED BREAKING CHANGES: add keys such as 'removed:constructs.Node.of' to .compatignore to skip.\n\" && exit 1)" 83 | } 84 | ] 85 | }, 86 | "compile": { 87 | "name": "compile", 88 | "description": "Only compile", 89 | "steps": [ 90 | { 91 | "exec": "jsii --silence-warnings=reserved-word" 92 | } 93 | ] 94 | }, 95 | "default": { 96 | "name": "default", 97 | "description": "Synthesize project files", 98 | "steps": [ 99 | { 100 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 101 | } 102 | ] 103 | }, 104 | "docgen": { 105 | "name": "docgen", 106 | "description": "Generate API.md from .jsii manifest", 107 | "steps": [ 108 | { 109 | "exec": "jsii-docgen -o API.md" 110 | } 111 | ] 112 | }, 113 | "eject": { 114 | "name": "eject", 115 | "description": "Remove projen from the project", 116 | "env": { 117 | "PROJEN_EJECTING": "true" 118 | }, 119 | "steps": [ 120 | { 121 | "spawn": "default" 122 | } 123 | ] 124 | }, 125 | "eslint": { 126 | "name": "eslint", 127 | "description": "Runs eslint against the codebase", 128 | "steps": [ 129 | { 130 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 131 | "receiveArgs": true 132 | } 133 | ] 134 | }, 135 | "install": { 136 | "name": "install", 137 | "description": "Install project dependencies and update lockfile (non-frozen)", 138 | "steps": [ 139 | { 140 | "exec": "yarn install --check-files" 141 | } 142 | ] 143 | }, 144 | "install:ci": { 145 | "name": "install:ci", 146 | "description": "Install project dependencies using frozen lockfile", 147 | "steps": [ 148 | { 149 | "exec": "yarn install --check-files --frozen-lockfile" 150 | } 151 | ] 152 | }, 153 | "package": { 154 | "name": "package", 155 | "description": "Creates the distribution package", 156 | "steps": [ 157 | { 158 | "spawn": "package:js", 159 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 160 | }, 161 | { 162 | "spawn": "package-all", 163 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 164 | } 165 | ] 166 | }, 167 | "package-all": { 168 | "name": "package-all", 169 | "description": "Packages artifacts for all target languages", 170 | "steps": [ 171 | { 172 | "spawn": "package:js" 173 | }, 174 | { 175 | "spawn": "package:python" 176 | } 177 | ] 178 | }, 179 | "package:js": { 180 | "name": "package:js", 181 | "description": "Create js language bindings", 182 | "steps": [ 183 | { 184 | "exec": "jsii-pacmak -v --target js" 185 | } 186 | ] 187 | }, 188 | "package:python": { 189 | "name": "package:python", 190 | "description": "Create python language bindings", 191 | "steps": [ 192 | { 193 | "exec": "jsii-pacmak -v --target python" 194 | } 195 | ] 196 | }, 197 | "post-compile": { 198 | "name": "post-compile", 199 | "description": "Runs after successful compilation", 200 | "steps": [ 201 | { 202 | "spawn": "docgen" 203 | } 204 | ] 205 | }, 206 | "pre-compile": { 207 | "name": "pre-compile", 208 | "description": "Prepare the project for compilation" 209 | }, 210 | "release": { 211 | "name": "release", 212 | "description": "Prepare a release from \"main\" branch", 213 | "env": { 214 | "RELEASE": "true" 215 | }, 216 | "steps": [ 217 | { 218 | "exec": "rm -fr dist" 219 | }, 220 | { 221 | "spawn": "bump" 222 | }, 223 | { 224 | "spawn": "build" 225 | }, 226 | { 227 | "spawn": "unbump" 228 | }, 229 | { 230 | "exec": "git diff --ignore-space-at-eol --exit-code" 231 | } 232 | ] 233 | }, 234 | "test": { 235 | "name": "test", 236 | "description": "Run tests", 237 | "steps": [ 238 | { 239 | "exec": "jest --passWithNoTests --updateSnapshot", 240 | "receiveArgs": true 241 | }, 242 | { 243 | "spawn": "eslint" 244 | } 245 | ] 246 | }, 247 | "test:watch": { 248 | "name": "test:watch", 249 | "description": "Run jest in watch mode", 250 | "steps": [ 251 | { 252 | "exec": "jest --watch" 253 | } 254 | ] 255 | }, 256 | "unbump": { 257 | "name": "unbump", 258 | "description": "Restores version to 0.0.0", 259 | "env": { 260 | "OUTFILE": "package.json", 261 | "CHANGELOG": "dist/changelog.md", 262 | "BUMPFILE": "dist/version.txt", 263 | "RELEASETAG": "dist/releasetag.txt", 264 | "RELEASE_TAG_PREFIX": "", 265 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 266 | }, 267 | "steps": [ 268 | { 269 | "builtin": "release/reset-version" 270 | } 271 | ] 272 | }, 273 | "watch": { 274 | "name": "watch", 275 | "description": "Watch & compile in the background", 276 | "steps": [ 277 | { 278 | "exec": "jsii -w --silence-warnings=reserved-word" 279 | } 280 | ] 281 | } 282 | }, 283 | "env": { 284 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 285 | }, 286 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 287 | } 288 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { CdkConstruct } from '@matthewbonig/cdk-construct-library'; 2 | 3 | const lodash = 'lodash.merge'; 4 | const projenDep = 'projen@^0.88.2'; 5 | const project = new CdkConstruct({ 6 | description: 'A Step Function state machine construct focused on working well with the Workflow Studio', 7 | cdkVersion: '2.85.0', 8 | name: 'state-machine', 9 | constructsVersion: '10.3.0', 10 | deps: [ 11 | projenDep, 12 | lodash, 13 | 'case', 14 | 'js-yaml', 15 | ], 16 | peerDeps: [ 17 | projenDep, 18 | 'constructs@10.3.0', 19 | ], 20 | devDeps: [ 21 | projenDep, 22 | '@types/js-yaml', 23 | '@matthewbonig/cdk-construct-library@0.0.14', 24 | ], 25 | bundledDeps: [lodash, 'case', 'js-yaml'], 26 | keywords: ['awscdk', 'cdk', 'AWS Step Functions'], 27 | disablePublishToGo: true, 28 | disablePublishToMaven: true, 29 | disablePublishToNuGet: true, 30 | 31 | }); 32 | project.github!.actions.set('actions/upload-artifact', 'actions/upload-artifact@v4.3.6'); 33 | 34 | project.synth(); 35 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### StateMachine 6 | 7 | #### Initializers 8 | 9 | ```typescript 10 | import { StateMachine } from '@matthewbonig/state-machine' 11 | 12 | new StateMachine(scope: Construct, id: string, props: StateMachineProps) 13 | ``` 14 | 15 | | **Name** | **Type** | **Description** | 16 | | --- | --- | --- | 17 | | scope | constructs.Construct | *No description.* | 18 | | id | string | *No description.* | 19 | | props | StateMachineProps | *No description.* | 20 | 21 | --- 22 | 23 | ##### `scope`Required 24 | 25 | - *Type:* constructs.Construct 26 | 27 | --- 28 | 29 | ##### `id`Required 30 | 31 | - *Type:* string 32 | 33 | --- 34 | 35 | ##### `props`Required 36 | 37 | - *Type:* StateMachineProps 38 | 39 | --- 40 | 41 | #### Methods 42 | 43 | | **Name** | **Description** | 44 | | --- | --- | 45 | | toString | Returns a string representation of this construct. | 46 | | applyRemovalPolicy | Apply the given removal policy to this resource. | 47 | | addToRolePolicy | Add the given statement to the role's policy. | 48 | | grant | Grant the given identity custom permissions. | 49 | | grantExecution | Grant the given identity permissions on all executions of the state machine. | 50 | | grantRead | Grant the given identity permissions to read results from state machine. | 51 | | grantStartExecution | Grant the given identity permissions to start an execution of this state machine. | 52 | | grantStartSyncExecution | Grant the given identity permissions to start a synchronous execution of this state machine. | 53 | | grantTaskResponse | Grant the given identity task response permissions on a state machine. | 54 | | metric | Return the given named metric for this State Machine's executions. | 55 | | metricAborted | Metric for the number of executions that were aborted. | 56 | | metricFailed | Metric for the number of executions that failed. | 57 | | metricStarted | Metric for the number of executions that were started. | 58 | | metricSucceeded | Metric for the number of executions that succeeded. | 59 | | metricThrottled | Metric for the number of executions that were throttled. | 60 | | metricTime | Metric for the interval, in milliseconds, between the time the execution starts and the time it closes. | 61 | | metricTimedOut | Metric for the number of executions that timed out. | 62 | 63 | --- 64 | 65 | ##### `toString` 66 | 67 | ```typescript 68 | public toString(): string 69 | ``` 70 | 71 | Returns a string representation of this construct. 72 | 73 | ##### `applyRemovalPolicy` 74 | 75 | ```typescript 76 | public applyRemovalPolicy(policy: RemovalPolicy): void 77 | ``` 78 | 79 | Apply the given removal policy to this resource. 80 | 81 | The Removal Policy controls what happens to this resource when it stops 82 | being managed by CloudFormation, either because you've removed it from the 83 | CDK application or because you've made a change that requires the resource 84 | to be replaced. 85 | 86 | The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS 87 | account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). 88 | 89 | ###### `policy`Required 90 | 91 | - *Type:* aws-cdk-lib.RemovalPolicy 92 | 93 | --- 94 | 95 | ##### `addToRolePolicy` 96 | 97 | ```typescript 98 | public addToRolePolicy(statement: PolicyStatement): void 99 | ``` 100 | 101 | Add the given statement to the role's policy. 102 | 103 | ###### `statement`Required 104 | 105 | - *Type:* aws-cdk-lib.aws_iam.PolicyStatement 106 | 107 | --- 108 | 109 | ##### `grant` 110 | 111 | ```typescript 112 | public grant(identity: IGrantable, actions: ...string[]): Grant 113 | ``` 114 | 115 | Grant the given identity custom permissions. 116 | 117 | ###### `identity`Required 118 | 119 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 120 | 121 | --- 122 | 123 | ###### `actions`Required 124 | 125 | - *Type:* ...string[] 126 | 127 | --- 128 | 129 | ##### `grantExecution` 130 | 131 | ```typescript 132 | public grantExecution(identity: IGrantable, actions: ...string[]): Grant 133 | ``` 134 | 135 | Grant the given identity permissions on all executions of the state machine. 136 | 137 | ###### `identity`Required 138 | 139 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 140 | 141 | --- 142 | 143 | ###### `actions`Required 144 | 145 | - *Type:* ...string[] 146 | 147 | --- 148 | 149 | ##### `grantRead` 150 | 151 | ```typescript 152 | public grantRead(identity: IGrantable): Grant 153 | ``` 154 | 155 | Grant the given identity permissions to read results from state machine. 156 | 157 | ###### `identity`Required 158 | 159 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 160 | 161 | --- 162 | 163 | ##### `grantStartExecution` 164 | 165 | ```typescript 166 | public grantStartExecution(identity: IGrantable): Grant 167 | ``` 168 | 169 | Grant the given identity permissions to start an execution of this state machine. 170 | 171 | ###### `identity`Required 172 | 173 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 174 | 175 | --- 176 | 177 | ##### `grantStartSyncExecution` 178 | 179 | ```typescript 180 | public grantStartSyncExecution(identity: IGrantable): Grant 181 | ``` 182 | 183 | Grant the given identity permissions to start a synchronous execution of this state machine. 184 | 185 | ###### `identity`Required 186 | 187 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 188 | 189 | --- 190 | 191 | ##### `grantTaskResponse` 192 | 193 | ```typescript 194 | public grantTaskResponse(identity: IGrantable): Grant 195 | ``` 196 | 197 | Grant the given identity task response permissions on a state machine. 198 | 199 | ###### `identity`Required 200 | 201 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 202 | 203 | --- 204 | 205 | ##### `metric` 206 | 207 | ```typescript 208 | public metric(metricName: string, props?: MetricOptions): Metric 209 | ``` 210 | 211 | Return the given named metric for this State Machine's executions. 212 | 213 | ###### `metricName`Required 214 | 215 | - *Type:* string 216 | 217 | --- 218 | 219 | ###### `props`Optional 220 | 221 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 222 | 223 | --- 224 | 225 | ##### `metricAborted` 226 | 227 | ```typescript 228 | public metricAborted(props?: MetricOptions): Metric 229 | ``` 230 | 231 | Metric for the number of executions that were aborted. 232 | 233 | ###### `props`Optional 234 | 235 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 236 | 237 | --- 238 | 239 | ##### `metricFailed` 240 | 241 | ```typescript 242 | public metricFailed(props?: MetricOptions): Metric 243 | ``` 244 | 245 | Metric for the number of executions that failed. 246 | 247 | ###### `props`Optional 248 | 249 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 250 | 251 | --- 252 | 253 | ##### `metricStarted` 254 | 255 | ```typescript 256 | public metricStarted(props?: MetricOptions): Metric 257 | ``` 258 | 259 | Metric for the number of executions that were started. 260 | 261 | ###### `props`Optional 262 | 263 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 264 | 265 | --- 266 | 267 | ##### `metricSucceeded` 268 | 269 | ```typescript 270 | public metricSucceeded(props?: MetricOptions): Metric 271 | ``` 272 | 273 | Metric for the number of executions that succeeded. 274 | 275 | ###### `props`Optional 276 | 277 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 278 | 279 | --- 280 | 281 | ##### `metricThrottled` 282 | 283 | ```typescript 284 | public metricThrottled(props?: MetricOptions): Metric 285 | ``` 286 | 287 | Metric for the number of executions that were throttled. 288 | 289 | ###### `props`Optional 290 | 291 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 292 | 293 | --- 294 | 295 | ##### `metricTime` 296 | 297 | ```typescript 298 | public metricTime(props?: MetricOptions): Metric 299 | ``` 300 | 301 | Metric for the interval, in milliseconds, between the time the execution starts and the time it closes. 302 | 303 | ###### `props`Optional 304 | 305 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 306 | 307 | --- 308 | 309 | ##### `metricTimedOut` 310 | 311 | ```typescript 312 | public metricTimedOut(props?: MetricOptions): Metric 313 | ``` 314 | 315 | Metric for the number of executions that timed out. 316 | 317 | ###### `props`Optional 318 | 319 | - *Type:* aws-cdk-lib.aws_cloudwatch.MetricOptions 320 | 321 | --- 322 | 323 | #### Static Functions 324 | 325 | | **Name** | **Description** | 326 | | --- | --- | 327 | | isConstruct | Checks if `x` is a construct. | 328 | | isOwnedResource | Returns true if the construct was created by CDK, and false otherwise. | 329 | | isResource | Check whether the given construct is a Resource. | 330 | | fromStateMachineArn | Import a state machine. | 331 | | fromStateMachineName | Import a state machine via resource name. | 332 | 333 | --- 334 | 335 | ##### `isConstruct` 336 | 337 | ```typescript 338 | import { StateMachine } from '@matthewbonig/state-machine' 339 | 340 | StateMachine.isConstruct(x: any) 341 | ``` 342 | 343 | Checks if `x` is a construct. 344 | 345 | Use this method instead of `instanceof` to properly detect `Construct` 346 | instances, even when the construct library is symlinked. 347 | 348 | Explanation: in JavaScript, multiple copies of the `constructs` library on 349 | disk are seen as independent, completely different libraries. As a 350 | consequence, the class `Construct` in each copy of the `constructs` library 351 | is seen as a different class, and an instance of one class will not test as 352 | `instanceof` the other class. `npm install` will not create installations 353 | like this, but users may manually symlink construct libraries together or 354 | use a monorepo tool: in those cases, multiple copies of the `constructs` 355 | library can be accidentally installed, and `instanceof` will behave 356 | unpredictably. It is safest to avoid using `instanceof`, and using 357 | this type-testing method instead. 358 | 359 | ###### `x`Required 360 | 361 | - *Type:* any 362 | 363 | Any object. 364 | 365 | --- 366 | 367 | ##### `isOwnedResource` 368 | 369 | ```typescript 370 | import { StateMachine } from '@matthewbonig/state-machine' 371 | 372 | StateMachine.isOwnedResource(construct: IConstruct) 373 | ``` 374 | 375 | Returns true if the construct was created by CDK, and false otherwise. 376 | 377 | ###### `construct`Required 378 | 379 | - *Type:* constructs.IConstruct 380 | 381 | --- 382 | 383 | ##### `isResource` 384 | 385 | ```typescript 386 | import { StateMachine } from '@matthewbonig/state-machine' 387 | 388 | StateMachine.isResource(construct: IConstruct) 389 | ``` 390 | 391 | Check whether the given construct is a Resource. 392 | 393 | ###### `construct`Required 394 | 395 | - *Type:* constructs.IConstruct 396 | 397 | --- 398 | 399 | ##### `fromStateMachineArn` 400 | 401 | ```typescript 402 | import { StateMachine } from '@matthewbonig/state-machine' 403 | 404 | StateMachine.fromStateMachineArn(scope: Construct, id: string, stateMachineArn: string) 405 | ``` 406 | 407 | Import a state machine. 408 | 409 | ###### `scope`Required 410 | 411 | - *Type:* constructs.Construct 412 | 413 | --- 414 | 415 | ###### `id`Required 416 | 417 | - *Type:* string 418 | 419 | --- 420 | 421 | ###### `stateMachineArn`Required 422 | 423 | - *Type:* string 424 | 425 | --- 426 | 427 | ##### `fromStateMachineName` 428 | 429 | ```typescript 430 | import { StateMachine } from '@matthewbonig/state-machine' 431 | 432 | StateMachine.fromStateMachineName(scope: Construct, id: string, stateMachineName: string) 433 | ``` 434 | 435 | Import a state machine via resource name. 436 | 437 | ###### `scope`Required 438 | 439 | - *Type:* constructs.Construct 440 | 441 | --- 442 | 443 | ###### `id`Required 444 | 445 | - *Type:* string 446 | 447 | --- 448 | 449 | ###### `stateMachineName`Required 450 | 451 | - *Type:* string 452 | 453 | --- 454 | 455 | #### Properties 456 | 457 | | **Name** | **Type** | **Description** | 458 | | --- | --- | --- | 459 | | node | constructs.Node | The tree node. | 460 | | env | aws-cdk-lib.ResourceEnvironment | The environment this resource belongs to. | 461 | | stack | aws-cdk-lib.Stack | The stack in which this resource is defined. | 462 | | grantPrincipal | aws-cdk-lib.aws_iam.IPrincipal | The principal this state machine is running as. | 463 | | role | aws-cdk-lib.aws_iam.IRole | Execution role of this state machine. | 464 | | stateMachineArn | string | The ARN of the state machine. | 465 | | stateMachineName | string | The name of the state machine. | 466 | | stateMachineType | aws-cdk-lib.aws_stepfunctions.StateMachineType | Type of the state machine. | 467 | 468 | --- 469 | 470 | ##### `node`Required 471 | 472 | ```typescript 473 | public readonly node: Node; 474 | ``` 475 | 476 | - *Type:* constructs.Node 477 | 478 | The tree node. 479 | 480 | --- 481 | 482 | ##### `env`Required 483 | 484 | ```typescript 485 | public readonly env: ResourceEnvironment; 486 | ``` 487 | 488 | - *Type:* aws-cdk-lib.ResourceEnvironment 489 | 490 | The environment this resource belongs to. 491 | 492 | For resources that are created and managed by the CDK 493 | (generally, those created by creating new class instances like Role, Bucket, etc.), 494 | this is always the same as the environment of the stack they belong to; 495 | however, for imported resources 496 | (those obtained from static methods like fromRoleArn, fromBucketName, etc.), 497 | that might be different than the stack they were imported into. 498 | 499 | --- 500 | 501 | ##### `stack`Required 502 | 503 | ```typescript 504 | public readonly stack: Stack; 505 | ``` 506 | 507 | - *Type:* aws-cdk-lib.Stack 508 | 509 | The stack in which this resource is defined. 510 | 511 | --- 512 | 513 | ##### `grantPrincipal`Required 514 | 515 | ```typescript 516 | public readonly grantPrincipal: IPrincipal; 517 | ``` 518 | 519 | - *Type:* aws-cdk-lib.aws_iam.IPrincipal 520 | 521 | The principal this state machine is running as. 522 | 523 | --- 524 | 525 | ##### `role`Required 526 | 527 | ```typescript 528 | public readonly role: IRole; 529 | ``` 530 | 531 | - *Type:* aws-cdk-lib.aws_iam.IRole 532 | 533 | Execution role of this state machine. 534 | 535 | --- 536 | 537 | ##### `stateMachineArn`Required 538 | 539 | ```typescript 540 | public readonly stateMachineArn: string; 541 | ``` 542 | 543 | - *Type:* string 544 | 545 | The ARN of the state machine. 546 | 547 | --- 548 | 549 | ##### `stateMachineName`Required 550 | 551 | ```typescript 552 | public readonly stateMachineName: string; 553 | ``` 554 | 555 | - *Type:* string 556 | 557 | The name of the state machine. 558 | 559 | --- 560 | 561 | ##### `stateMachineType`Required 562 | 563 | ```typescript 564 | public readonly stateMachineType: StateMachineType; 565 | ``` 566 | 567 | - *Type:* aws-cdk-lib.aws_stepfunctions.StateMachineType 568 | 569 | Type of the state machine. 570 | 571 | --- 572 | 573 | 574 | ### StepFunctionsAutoDiscover 575 | 576 | A projen component for discovering AWS Step Function state machine workflow ASL files and generating a strongly typed interface and construct to use it. 577 | 578 | Simply add a new instance and hand it your AwsCdkTypeScriptApp projen class: 579 | ``` 580 | const project = new AwsCdkTypeScriptApp({ ... }); 581 | new StepFunctionsAutoDiscover(project); 582 | ``` 583 | 584 | And any *.workflow.json file will cause the generation of a new strongly-typed StateMachine-derived class you can use. 585 | Note that these constructs are NOT jsii-compatible. If you need that, 586 | please open an [issue](https://github.com/mbonig/state-machine/issues/new) 587 | 588 | #### Initializers 589 | 590 | ```typescript 591 | import { StepFunctionsAutoDiscover } from '@matthewbonig/state-machine' 592 | 593 | new StepFunctionsAutoDiscover(project: AwsCdkTypeScriptApp, _options?: StepFunctionsAutoDiscoverOptions) 594 | ``` 595 | 596 | | **Name** | **Type** | **Description** | 597 | | --- | --- | --- | 598 | | project | projen.awscdk.AwsCdkTypeScriptApp | *No description.* | 599 | | _options | StepFunctionsAutoDiscoverOptions | *No description.* | 600 | 601 | --- 602 | 603 | ##### `project`Required 604 | 605 | - *Type:* projen.awscdk.AwsCdkTypeScriptApp 606 | 607 | --- 608 | 609 | ##### `_options`Optional 610 | 611 | - *Type:* StepFunctionsAutoDiscoverOptions 612 | 613 | --- 614 | 615 | #### Methods 616 | 617 | | **Name** | **Description** | 618 | | --- | --- | 619 | | toString | Returns a string representation of this construct. | 620 | | postSynthesize | Called after synthesis. | 621 | | preSynthesize | Called before synthesis. | 622 | | synthesize | Synthesizes files to the project output directory. | 623 | 624 | --- 625 | 626 | ##### `toString` 627 | 628 | ```typescript 629 | public toString(): string 630 | ``` 631 | 632 | Returns a string representation of this construct. 633 | 634 | ##### `postSynthesize` 635 | 636 | ```typescript 637 | public postSynthesize(): void 638 | ``` 639 | 640 | Called after synthesis. 641 | 642 | Order is *not* guaranteed. 643 | 644 | ##### `preSynthesize` 645 | 646 | ```typescript 647 | public preSynthesize(): void 648 | ``` 649 | 650 | Called before synthesis. 651 | 652 | ##### `synthesize` 653 | 654 | ```typescript 655 | public synthesize(): void 656 | ``` 657 | 658 | Synthesizes files to the project output directory. 659 | 660 | #### Static Functions 661 | 662 | | **Name** | **Description** | 663 | | --- | --- | 664 | | isConstruct | Checks if `x` is a construct. | 665 | | isComponent | Test whether the given construct is a component. | 666 | 667 | --- 668 | 669 | ##### `isConstruct` 670 | 671 | ```typescript 672 | import { StepFunctionsAutoDiscover } from '@matthewbonig/state-machine' 673 | 674 | StepFunctionsAutoDiscover.isConstruct(x: any) 675 | ``` 676 | 677 | Checks if `x` is a construct. 678 | 679 | Use this method instead of `instanceof` to properly detect `Construct` 680 | instances, even when the construct library is symlinked. 681 | 682 | Explanation: in JavaScript, multiple copies of the `constructs` library on 683 | disk are seen as independent, completely different libraries. As a 684 | consequence, the class `Construct` in each copy of the `constructs` library 685 | is seen as a different class, and an instance of one class will not test as 686 | `instanceof` the other class. `npm install` will not create installations 687 | like this, but users may manually symlink construct libraries together or 688 | use a monorepo tool: in those cases, multiple copies of the `constructs` 689 | library can be accidentally installed, and `instanceof` will behave 690 | unpredictably. It is safest to avoid using `instanceof`, and using 691 | this type-testing method instead. 692 | 693 | ###### `x`Required 694 | 695 | - *Type:* any 696 | 697 | Any object. 698 | 699 | --- 700 | 701 | ##### `isComponent` 702 | 703 | ```typescript 704 | import { StepFunctionsAutoDiscover } from '@matthewbonig/state-machine' 705 | 706 | StepFunctionsAutoDiscover.isComponent(x: any) 707 | ``` 708 | 709 | Test whether the given construct is a component. 710 | 711 | ###### `x`Required 712 | 713 | - *Type:* any 714 | 715 | --- 716 | 717 | #### Properties 718 | 719 | | **Name** | **Type** | **Description** | 720 | | --- | --- | --- | 721 | | node | constructs.Node | The tree node. | 722 | | project | projen.Project | *No description.* | 723 | | entrypoints | string[] | Auto-discovered entry points with paths relative to the project directory. | 724 | 725 | --- 726 | 727 | ##### `node`Required 728 | 729 | ```typescript 730 | public readonly node: Node; 731 | ``` 732 | 733 | - *Type:* constructs.Node 734 | 735 | The tree node. 736 | 737 | --- 738 | 739 | ##### `project`Required 740 | 741 | ```typescript 742 | public readonly project: Project; 743 | ``` 744 | 745 | - *Type:* projen.Project 746 | 747 | --- 748 | 749 | ##### `entrypoints`Required 750 | 751 | ```typescript 752 | public readonly entrypoints: string[]; 753 | ``` 754 | 755 | - *Type:* string[] 756 | 757 | Auto-discovered entry points with paths relative to the project directory. 758 | 759 | --- 760 | 761 | 762 | ## Structs 763 | 764 | ### StateMachineProps 765 | 766 | #### Initializer 767 | 768 | ```typescript 769 | import { StateMachineProps } from '@matthewbonig/state-machine' 770 | 771 | const stateMachineProps: StateMachineProps = { ... } 772 | ``` 773 | 774 | #### Properties 775 | 776 | | **Name** | **Type** | **Description** | 777 | | --- | --- | --- | 778 | | definition | any | An object that can be serialized into an ASL. | 779 | | aslYaml | boolean | Should the ASL definition be written as YAML. | 780 | | logs | aws-cdk-lib.aws_stepfunctions.LogOptions | Defines what execution history events are logged and where they are logged. | 781 | | overrides | any | An object that matches the schema/shape of the ASL .States map with overridden values. | 782 | | role | aws-cdk-lib.aws_iam.IRole | The execution role for the state machine service. | 783 | | stateMachineName | string | A name for the state machine. | 784 | | stateMachineType | aws-cdk-lib.aws_stepfunctions.StateMachineType | Type of the state machine. | 785 | | timeout | aws-cdk-lib.Duration | Maximum run time for this state machine. | 786 | | tracingEnabled | boolean | Specifies whether Amazon X-Ray tracing is enabled for this state machine. | 787 | 788 | --- 789 | 790 | ##### `definition`Required 791 | 792 | ```typescript 793 | public readonly definition: any; 794 | ``` 795 | 796 | - *Type:* any 797 | 798 | An object that can be serialized into an ASL. 799 | 800 | --- 801 | 802 | ##### `aslYaml`Optional 803 | 804 | ```typescript 805 | public readonly aslYaml: boolean; 806 | ``` 807 | 808 | - *Type:* boolean 809 | - *Default:* false 810 | 811 | Should the ASL definition be written as YAML. 812 | 813 | --- 814 | 815 | ##### `logs`Optional 816 | 817 | ```typescript 818 | public readonly logs: LogOptions; 819 | ``` 820 | 821 | - *Type:* aws-cdk-lib.aws_stepfunctions.LogOptions 822 | - *Default:* No logging 823 | 824 | Defines what execution history events are logged and where they are logged. 825 | 826 | --- 827 | 828 | ##### `overrides`Optional 829 | 830 | ```typescript 831 | public readonly overrides: any; 832 | ``` 833 | 834 | - *Type:* any 835 | 836 | An object that matches the schema/shape of the ASL .States map with overridden values. 837 | 838 | --- 839 | 840 | *Example* 841 | 842 | ```typescript 843 | {'My First State': { Parameters: { FunctionName: 'aLambdaFunctionArn' } } } 844 | ``` 845 | 846 | 847 | ##### `role`Optional 848 | 849 | ```typescript 850 | public readonly role: IRole; 851 | ``` 852 | 853 | - *Type:* aws-cdk-lib.aws_iam.IRole 854 | - *Default:* A role is automatically created 855 | 856 | The execution role for the state machine service. 857 | 858 | --- 859 | 860 | ##### `stateMachineName`Optional 861 | 862 | ```typescript 863 | public readonly stateMachineName: string; 864 | ``` 865 | 866 | - *Type:* string 867 | - *Default:* A name is automatically generated 868 | 869 | A name for the state machine. 870 | 871 | --- 872 | 873 | ##### `stateMachineType`Optional 874 | 875 | ```typescript 876 | public readonly stateMachineType: StateMachineType; 877 | ``` 878 | 879 | - *Type:* aws-cdk-lib.aws_stepfunctions.StateMachineType 880 | - *Default:* StateMachineType.STANDARD 881 | 882 | Type of the state machine. 883 | 884 | --- 885 | 886 | ##### `timeout`Optional 887 | 888 | ```typescript 889 | public readonly timeout: Duration; 890 | ``` 891 | 892 | - *Type:* aws-cdk-lib.Duration 893 | - *Default:* No timeout 894 | 895 | Maximum run time for this state machine. 896 | 897 | --- 898 | 899 | ##### `tracingEnabled`Optional 900 | 901 | ```typescript 902 | public readonly tracingEnabled: boolean; 903 | ``` 904 | 905 | - *Type:* boolean 906 | - *Default:* false 907 | 908 | Specifies whether Amazon X-Ray tracing is enabled for this state machine. 909 | 910 | --- 911 | 912 | ### StepFunctionsAutoDiscoverOptions 913 | 914 | #### Initializer 915 | 916 | ```typescript 917 | import { StepFunctionsAutoDiscoverOptions } from '@matthewbonig/state-machine' 918 | 919 | const stepFunctionsAutoDiscoverOptions: StepFunctionsAutoDiscoverOptions = { ... } 920 | ``` 921 | 922 | #### Properties 923 | 924 | | **Name** | **Type** | **Description** | 925 | | --- | --- | --- | 926 | | extension | string | An optional extension to use for discovering state machine files. | 927 | 928 | --- 929 | 930 | ##### `extension`Optional 931 | 932 | ```typescript 933 | public readonly extension: string; 934 | ``` 935 | 936 | - *Type:* string 937 | - *Default:* '.workflow.json' (JSON_STEPFUNCTION_EXT) 938 | 939 | An optional extension to use for discovering state machine files. 940 | 941 | --- 942 | 943 | 944 | 945 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Matthew Bonig 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workflow Studio compatible State Machine 2 | 3 | [![View on Construct Hub](https://constructs.dev/badge?package=%40matthewbonig%2Fstate-machine)](https://constructs.dev/packages/@matthewbonig/state-machine) 4 | 5 | This is a Workflow Studio compatible AWS Step Function state machine construct. 6 | 7 | The goal of this construct is to make it easy to build and maintain your state machines using the Workflow Studio but still 8 | leverage the AWS CDK as the source of truth for the state machine. 9 | 10 | Read more about it [here](https://matthewbonig.com/2022/02/19/step-functions-and-the-cdk/). 11 | 12 | ## How to Use This Construct 13 | 14 | Start by designing your initial state machine using the Workflow Studio. 15 | When done with your first draft, copy and paste the ASL definition to a local file. 16 | 17 | Create a new instance of this construct, handing it a fully parsed version of the ASL. 18 | Then add overridden values. 19 | The fields in the `overrides` field should match the `States` field of the ASL. 20 | 21 | ## Version Usage 22 | 23 | The AWS CDK `StateMachine` construct introduced a change in version [**2.85.0**](https://github.com/aws/aws-cdk/pull/25932) that deprecated an earlier usage of 'definition' 24 | by this construct. This construct has been updated to use the new 'definitionBody' field. 25 | 26 | If you are using a version of the CDK before version **2.85.0**, you should use version **0.0.28** of this construct. 27 | 28 | If you are using a version fo the CDK great or equal to **2.85.0**, you should use version **0.0.29+** of this construct. 29 | 30 | ### Projen component 31 | 32 | There is a projen component included in this library which will help you in using the construct. It works similar 33 | to the [auto-discovery feature](https://projen.io/docs/integrations/aws/#aws-lambda-functions). To use it, first add the component 34 | to your projen project: 35 | 36 | ```js 37 | // ... 38 | const { StepFunctionsAutoDiscover } = require('@matthewbonig/state-machine'); 39 | 40 | const project = new awscdk.AwsCdkTypeScriptApp({ 41 | // ..., 42 | deps: [ 43 | // ..., 44 | '@matthewbonig/state-machine', 45 | ] 46 | }); 47 | 48 | new StepFunctionsAutoDiscover(project); 49 | ``` 50 | 51 | Now projen will look for any files with a suffix `.workflow.json` and generate new files beside the .json: 52 | * A typed `overrides` interface which is based on your workflow. 53 | * A construct derived from `StateMachine` that uses this override. 54 | 55 | Instead of using the `StateMachine` construct directly you can now use the generated one: 56 | 57 | ```text 58 | . 59 | ├── MyFancyThing.workflow.json 60 | └── MyFancyThing-statemachine.ts 61 | ``` 62 | 63 | ```ts 64 | export class SomeStack extends Stack { 65 | constructor(scope: Construct, id: string, props: StackProps) { 66 | super(scope, id, props); 67 | const handler = new NodejsFunction(this, 'MyHandler'); 68 | new SomeFancyThingStateMachine(this, 'MyFancyWorkflow', { 69 | overrides: { 70 | 'My First State': { 71 | Parameters: { 72 | FunctionName: handler.functionName 73 | } 74 | } 75 | } 76 | }) 77 | } 78 | } 79 | ``` 80 | 81 | > :warning: **The interfaces and constructs generated here are NOT jsii compliant (they use Partials and Omits) and cannot be 82 | compiled by jsii into other languages. If you plan to distribute any libraries you cannot use this.** 83 | 84 | ### Alternative Extensions 85 | 86 | There is an optional parameter, `extension` that you can pass to have it search for alternative extensions. 87 | AWS recommends that ASL definition files have a `.asl.json` extension, which will be picked up by some IDE 88 | tools. This extension was recommended after initial development of this component. Therefore, the default is 89 | to use the original extension. But, you can override this by passing a different extension to the 90 | AutoDiscover's constructor options. There are two constants defined, `JSON_STEPFUNCTION_EXT` and `AWS_RECOMMENDED_JSON_EXT` that you can use. 91 | 92 | ```js 93 | // ... 94 | const { StepFunctionsAutoDiscover, AWS_RECOMMENDED_JSON_EXT } = require('@matthewbonig/state-machine'); 95 | 96 | const project = new awscdk.AwsCdkTypeScriptApp({ 97 | // ..., 98 | deps: [ 99 | // ..., 100 | '@matthewbonig/state-machine', 101 | ] 102 | }); 103 | 104 | new StepFunctionsAutoDiscover(project, { extension: AWS_RECOMMENDED_JSON_EXT }); 105 | ``` 106 | 107 | ### Yaml files 108 | 109 | Yaml files are supported as well. You can provide an extension to the AutoDiscover component to have it search for yaml files. If the file has 'yaml' or 'yml' anywhere in the name it will be parsed as yaml. If not, it will be parsed as json. 110 | 111 | ```js 112 | // ... 113 | const { StepFunctionsAutoDiscover } = require('@matthewbonig/state-machine'); 114 | 115 | const project = new awscdk.AwsCdkTypeScriptApp({ 116 | // ..., 117 | deps: [ 118 | // ..., 119 | '@matthewbonig/state-machine', 120 | ] 121 | }); 122 | 123 | new StepFunctionsAutoDiscover(project, { extension: '.yaml.asl' }); 124 | ``` 125 | 126 | ### Examples 127 | 128 | ```ts 129 | const secret = new Secret(stack, 'Secret', {}); 130 | new StateMachine(stack, 'Test', { 131 | stateMachineName: 'A nice state machine', 132 | definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'sample.json'), 'utf8').toString()), 133 | overrides: { 134 | 'Read database credentials secret': { 135 | Parameters: { 136 | SecretId: secret.secretArn, 137 | }, 138 | }, 139 | }, 140 | }); 141 | ``` 142 | 143 | You can also override nested states in arrays, for example: 144 | 145 | ```ts 146 | new StateMachine(stack, 'Test', { 147 | stateMachineName: 'A-nice-state-machine', 148 | overrides: { 149 | Branches: [{ 150 | // pass an empty object too offset overrides 151 | }, { 152 | StartAt: 'StartInstances', 153 | States: { 154 | StartInstances: { 155 | Parameters: { 156 | InstanceIds: ['INSTANCE_ID'], 157 | }, 158 | }, 159 | }, 160 | }], 161 | }, 162 | stateMachineType: StateMachineType.STANDARD, 163 | definition: { 164 | States: { 165 | Branches: [ 166 | { 167 | StartAt: 'ResumeCluster', 168 | States: { 169 | 'Redshift Pass': { 170 | Type: 'Pass', 171 | End: true, 172 | }, 173 | }, 174 | }, 175 | { 176 | StartAt: 'StartInstances', 177 | States: { 178 | 'StartInstances': { 179 | Type: 'Task', 180 | Parameters: { 181 | InstanceIds: [ 182 | 'MyData', 183 | ], 184 | }, 185 | Resource: 'arn:aws:states:::aws-sdk:ec2:startInstances', 186 | Next: 'DescribeInstanceStatus', 187 | }, 188 | 'DescribeInstanceStatus': { 189 | Type: 'Task', 190 | Next: 'EC2 Pass', 191 | Parameters: { 192 | InstanceIds: [ 193 | 'MyData', 194 | ], 195 | }, 196 | Resource: 'arn:aws:states:::aws-sdk:ec2:describeInstanceStatus', 197 | }, 198 | 'EC2 Pass': { 199 | Type: 'Pass', 200 | End: true, 201 | }, 202 | }, 203 | }, 204 | ], 205 | }, 206 | }, 207 | }); 208 | ``` 209 | 210 | For Python, be sure to use a context manager when opening your JSON file. 211 | - You do not need to `str()` the dictionary object you supply as your `definition` prop. 212 | - Elements of your override path **do** need to be strings. 213 | 214 | ```python 215 | secret = Secret(stack, 'Secret') 216 | 217 | with open('sample.json', 'r+', encoding='utf-8') as sample: 218 | sample_dict = json.load(sample) 219 | 220 | state_machine = StateMachine( 221 | self, 222 | 'Test', 223 | definition = sample_dict, 224 | overrides = { 225 | "Read database credentials secret": { 226 | "Parameters": { 227 | "SecretId": secret.secret_arn, 228 | }, 229 | }, 230 | }) 231 | ``` 232 | 233 | In this example, the ASL has a state called 'Read database credentials secret' and the SecretId parameter is overridden with a 234 | CDK generated value. 235 | Future changes can be done by editing, debugging, and testing the state machine directly in the Workflow Studio. 236 | Once everything is working properly, copy and paste the ASL back to your local file. 237 | 238 | ## Issues 239 | 240 | Please open any issues you have on [Github](https://github.com/mbonig/state-machine/issues). 241 | 242 | ## Contributing 243 | 244 | Please submit PRs from forked repositories if you'd like to contribute. 245 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@matthewbonig/state-machine", 3 | "description": "A Step Function state machine construct focused on working well with the Workflow Studio", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/mbonig/state-machine.git" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compat": "npx projen compat", 13 | "compile": "npx projen compile", 14 | "default": "npx projen default", 15 | "docgen": "npx projen docgen", 16 | "eject": "npx projen eject", 17 | "eslint": "npx projen eslint", 18 | "package": "npx projen package", 19 | "package-all": "npx projen package-all", 20 | "package:js": "npx projen package:js", 21 | "package:python": "npx projen package:python", 22 | "post-compile": "npx projen post-compile", 23 | "pre-compile": "npx projen pre-compile", 24 | "release": "npx projen release", 25 | "test": "npx projen test", 26 | "test:watch": "npx projen test:watch", 27 | "unbump": "npx projen unbump", 28 | "watch": "npx projen watch", 29 | "projen": "npx projen" 30 | }, 31 | "author": { 32 | "name": "Matthew Bonig", 33 | "email": "matthew.bonig@gmail.com", 34 | "organization": false 35 | }, 36 | "devDependencies": { 37 | "@matthewbonig/cdk-construct-library": "0.0.14", 38 | "@types/jest": "28", 39 | "@types/js-yaml": "^4.0.5", 40 | "@types/node": "^18", 41 | "@typescript-eslint/eslint-plugin": "^7", 42 | "@typescript-eslint/parser": "^7", 43 | "aws-cdk-lib": "2.85.0", 44 | "commit-and-tag-version": "^12", 45 | "constructs": "10.3.0", 46 | "eslint": "^8", 47 | "eslint-import-resolver-typescript": "^3.6.3", 48 | "eslint-plugin-import": "^2.31.0", 49 | "jest": "28", 50 | "jest-junit": "^15", 51 | "jsii": "~5.5.0", 52 | "jsii-diff": "^1.55.1", 53 | "jsii-docgen": "^10.5.0", 54 | "jsii-pacmak": "^1.101.0", 55 | "jsii-rosetta": "~5.5.0", 56 | "projen": "0.88.2", 57 | "ts-jest": "28", 58 | "ts-node": "^10.9.1", 59 | "typescript": "^4.9.4" 60 | }, 61 | "peerDependencies": { 62 | "aws-cdk-lib": "^2.85.0", 63 | "constructs": "^10.3.0", 64 | "projen": "^0.88.2" 65 | }, 66 | "dependencies": { 67 | "case": "^1.6.3", 68 | "js-yaml": "^4.1.0", 69 | "lodash.merge": "^4.6.2", 70 | "projen": "^0.88.2" 71 | }, 72 | "bundledDependencies": [ 73 | "case", 74 | "js-yaml", 75 | "lodash.merge" 76 | ], 77 | "keywords": [ 78 | "AWS Step Functions", 79 | "awscdk", 80 | "cdk" 81 | ], 82 | "main": "lib/index.js", 83 | "license": "MIT", 84 | "publishConfig": { 85 | "access": "public" 86 | }, 87 | "version": "0.0.0", 88 | "jest": { 89 | "coverageProvider": "v8", 90 | "testMatch": [ 91 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 92 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 93 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 94 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 95 | ], 96 | "clearMocks": true, 97 | "collectCoverage": true, 98 | "coverageReporters": [ 99 | "json", 100 | "lcov", 101 | "clover", 102 | "cobertura", 103 | "text" 104 | ], 105 | "coverageDirectory": "coverage", 106 | "coveragePathIgnorePatterns": [ 107 | "/node_modules/" 108 | ], 109 | "testPathIgnorePatterns": [ 110 | "/node_modules/" 111 | ], 112 | "watchPathIgnorePatterns": [ 113 | "/node_modules/" 114 | ], 115 | "reporters": [ 116 | "default", 117 | [ 118 | "jest-junit", 119 | { 120 | "outputDirectory": "test-reports" 121 | } 122 | ] 123 | ], 124 | "preset": "ts-jest", 125 | "globals": { 126 | "ts-jest": { 127 | "tsconfig": "tsconfig.dev.json" 128 | } 129 | } 130 | }, 131 | "types": "lib/index.d.ts", 132 | "stability": "stable", 133 | "jsii": { 134 | "outdir": "dist", 135 | "targets": { 136 | "python": { 137 | "distName": "mbonig.state-machine", 138 | "module": "mbonig_state_machine" 139 | } 140 | }, 141 | "tsc": { 142 | "outDir": "lib", 143 | "rootDir": "src" 144 | } 145 | }, 146 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 147 | } 148 | -------------------------------------------------------------------------------- /src/BuildStateType.ts: -------------------------------------------------------------------------------- 1 | export function buildStateType(stateDef: any, indent = 2) { 2 | let stateType = 'Partial<{\n'; 3 | const indentTabs = ' '.repeat(indent); 4 | const closeIndents = ' '.repeat(indent - 1); 5 | if (stateDef === null) { 6 | // we're going to return an any here because while it's null now, maybe it'll be an any later. 7 | return 'any'; 8 | } 9 | if (typeof stateDef === 'string') { 10 | return 'string'; 11 | } 12 | if (Number.isInteger(stateDef)) { 13 | return 'number'; 14 | } 15 | for (const prop of Object.keys(stateDef)) { 16 | const propValue = stateDef[prop]; 17 | const safeProp = prop.replace(/'/g, '\\\''); 18 | if (typeof propValue === 'string') { 19 | stateType += `${indentTabs}'${safeProp}': string;\n`; 20 | } else if (Array.isArray(propValue)) { 21 | const arrayPropTypes = propValue.map(pv => buildStateType(pv, indent + 1)); 22 | stateType += `${indentTabs}'${safeProp}': Partial<[${arrayPropTypes.join(', ')}]>;\n`; 23 | } else { 24 | stateType += `${indentTabs}'${safeProp}': ${buildStateType(propValue, indent + 1)};\n`; 25 | } 26 | } 27 | stateType += `${closeIndents}}>`; 28 | return stateType; 29 | } 30 | -------------------------------------------------------------------------------- /src/StateMachine.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from 'aws-cdk-lib'; 2 | import * as iam from 'aws-cdk-lib/aws-iam'; 3 | import * as aws_stepfunctions from 'aws-cdk-lib/aws-stepfunctions'; 4 | import { DefinitionBody, LogOptions, StateMachineType } from 'aws-cdk-lib/aws-stepfunctions'; 5 | import { Construct } from 'constructs'; 6 | import * as yaml from 'js-yaml'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-require-imports 9 | const merge = require('lodash.merge'); 10 | 11 | export interface StateMachineProps { 12 | 13 | /** 14 | * An object that matches the schema/shape of the ASL .States map with overridden values. 15 | * 16 | * @example {'My First State': { Parameters: { FunctionName: 'aLambdaFunctionArn' } } } 17 | */ 18 | readonly overrides?: any; 19 | 20 | /** 21 | * An object that can be serialized into an ASL. 22 | */ 23 | readonly definition: any; 24 | 25 | /** 26 | * A name for the state machine 27 | * 28 | * @default A name is automatically generated 29 | */ 30 | readonly stateMachineName?: string; 31 | 32 | /** 33 | * The execution role for the state machine service 34 | * 35 | * @default A role is automatically created 36 | */ 37 | readonly role?: iam.IRole; 38 | 39 | /** 40 | * Maximum run time for this state machine 41 | * 42 | * @default No timeout 43 | */ 44 | readonly timeout?: Duration; 45 | 46 | /** 47 | * Type of the state machine 48 | * 49 | * @default StateMachineType.STANDARD 50 | */ 51 | readonly stateMachineType?: StateMachineType; 52 | 53 | /** 54 | * Defines what execution history events are logged and where they are logged. 55 | * 56 | * @default No logging 57 | */ 58 | readonly logs?: LogOptions; 59 | 60 | /** 61 | * Specifies whether Amazon X-Ray tracing is enabled for this state machine. 62 | * 63 | * @default false 64 | */ 65 | readonly tracingEnabled?: boolean; 66 | 67 | /** 68 | * Should the ASL definition be written as YAML 69 | * 70 | * @default false 71 | */ 72 | readonly aslYaml?: boolean; 73 | 74 | } 75 | 76 | export class StateMachine extends aws_stepfunctions.StateMachine { 77 | 78 | constructor(scope: Construct, id: string, props: StateMachineProps) { 79 | const mergedDefinition = merge(props.definition, { States: props.overrides }); 80 | let definitionString: string; 81 | if (props.aslYaml) { 82 | definitionString = yaml.dump(mergedDefinition); 83 | } else { 84 | definitionString = JSON.stringify(mergedDefinition); 85 | } 86 | const propsMinusDefinition = { ...props, definition: undefined }; 87 | delete propsMinusDefinition.definition; 88 | super(scope, id, { 89 | ...propsMinusDefinition, 90 | definitionBody: DefinitionBody.fromString(definitionString), 91 | }); 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/StepFunctionsAutoDiscover.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { basename, dirname, extname, join } from 'path'; 4 | import { pascal } from 'case'; 5 | import * as yaml from 'js-yaml'; 6 | import { Component, Project, SourceCode } from 'projen'; 7 | import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; 8 | import { AwsCdkDeps } from 'projen/lib/awscdk/awscdk-deps'; 9 | import { AutoDiscoverBase } from 'projen/lib/cdk'; 10 | import { buildStateType } from './BuildStateType'; 11 | 12 | /** 13 | * The original extension used to search for ASL files. 14 | */ 15 | export const JSON_STEPFUNCTION_EXT = '.workflow.json'; 16 | 17 | /** 18 | * The AWS-recommended extension for ASL files. 19 | */ 20 | export const AWS_RECOMMENDED_JSON_EXT = '.json.asl'; 21 | 22 | /** 23 | * The AWS-recommended extension for YAML ASL files. 24 | */ 25 | export const AWS_RECOMMENDED_YAML_EXT = '.yaml.asl'; 26 | 27 | export interface StepFunctionsAutoDiscoverOptions { 28 | /** 29 | * An optional extension to use for discovering state machine files. 30 | * 31 | * @default '.workflow.json' (JSON_STEPFUNCTION_EXT) 32 | */ 33 | readonly extension?: string; 34 | } 35 | 36 | /** 37 | * A projen component for discovering AWS Step Function state machine workflow ASL files 38 | * and generating a strongly typed interface and construct to use it. 39 | * 40 | * Simply add a new instance and hand it your AwsCdkTypeScriptApp projen class: 41 | * ``` 42 | * const project = new AwsCdkTypeScriptApp({ ... }); 43 | * new StepFunctionsAutoDiscover(project); 44 | * ``` 45 | * 46 | * And any *.workflow.json file will cause the generation of a new strongly-typed StateMachine-derived class you can use. 47 | * Note that these constructs are NOT jsii-compatible. If you need that, 48 | * please open an [issue](https://github.com/mbonig/state-machine/issues/new) 49 | */ 50 | export class StepFunctionsAutoDiscover extends AutoDiscoverBase { 51 | constructor(project: AwsCdkTypeScriptApp, _options?: StepFunctionsAutoDiscoverOptions) { 52 | if (_options?.extension) { 53 | if (!_options.extension.startsWith('.')) { 54 | throw new Error('extension must start with a .'); 55 | } 56 | } 57 | let extension = _options?.extension || JSON_STEPFUNCTION_EXT; 58 | super(project, { 59 | extension: extension, 60 | projectdir: project.srcdir, 61 | }); 62 | for (const entrypoint of this.entrypoints) { 63 | new StepFunctionsStateMachine(this.project, { 64 | workflowAsl: entrypoint, 65 | cdkDeps: project.cdkDeps, 66 | extension, 67 | }); 68 | } 69 | } 70 | } 71 | 72 | export interface StepFunctionsStateMachineOptions { 73 | readonly extension: string; 74 | readonly constructFile?: string; 75 | readonly workflowAsl: string; 76 | readonly constructName?: string; 77 | readonly cdkDeps: AwsCdkDeps; 78 | } 79 | 80 | /** 81 | * Don't use this class directly. 82 | */ 83 | export class StepFunctionsStateMachine extends Component { 84 | constructor(project: Project, options: StepFunctionsStateMachineOptions) { 85 | super(project); 86 | 87 | const extension = options.extension ?? JSON_STEPFUNCTION_EXT; 88 | const workflowAsl = options.workflowAsl; 89 | 90 | if ( 91 | !workflowAsl.endsWith(extension) 92 | ) { 93 | throw new Error( 94 | `${workflowAsl} must have a ${extension} extension`, 95 | ); 96 | } 97 | const basePath = join( 98 | dirname(workflowAsl), 99 | basename( 100 | workflowAsl, 101 | extension, 102 | ), 103 | ); 104 | const constructFile = options.constructFile ?? `${basePath}-statemachine.ts`; 105 | 106 | if (extname(constructFile) !== '.ts') { 107 | throw new Error( 108 | `Construct file name "${constructFile}" must have a .ts extension`, 109 | ); 110 | } 111 | 112 | // type names 113 | const constructName = 114 | options.constructName ?? pascal(basename(basePath)) + 'StateMachine'; 115 | 116 | const src = new SourceCode(project, constructFile); 117 | if (src.marker) { 118 | src.line(`// ${src.marker}`); 119 | } 120 | src.line('import fs from \'fs\';'); 121 | src.line('import path from \'path\';'); 122 | src.line('import { StateMachine, StateMachineProps } from \'@matthewbonig/state-machine\';'); 123 | src.line('import { Construct } from \'constructs\';'); 124 | 125 | let isYaml = /(yaml|yml)/.test(extension); 126 | if (isYaml) { 127 | src.line('import * as yaml from \'js-yaml\';'); 128 | } 129 | 130 | src.open(`export interface ${constructName}Overrides {`); 131 | 132 | let workflowDefinition: any; 133 | if (isYaml) { 134 | workflowDefinition = yaml.load(fs.readFileSync(join(project.outdir, workflowAsl)).toString()); 135 | } else { 136 | workflowDefinition = JSON.parse(fs.readFileSync(join(project.outdir, workflowAsl)).toString()); 137 | 138 | } 139 | if (!workflowDefinition.States) { 140 | throw new Error(`The workflow file ${workflowAsl} doesn't appear to be a valid ASL file, it doesn't contain a 'States' field`); 141 | } 142 | for (const [stateName, stateDef] of Object.entries(workflowDefinition.States)) { 143 | const stateType = buildStateType(stateDef); 144 | src.line(`readonly '${stateName}': ${stateType};`); 145 | } 146 | src.close('}'); 147 | 148 | src.open(`export interface ${constructName}Props extends Omit {`); 149 | src.line(`readonly overrides: Partial<${constructName}Overrides>;`); 150 | src.close('}'); 151 | 152 | 153 | src.open(`export class ${constructName} extends StateMachine {`); 154 | src.open(`constructor(scope: Construct, id: string, props: ${constructName}Props) {`); 155 | src.open('super(scope, id, {'); 156 | src.line('...props,'); 157 | const relativeFile = path.basename(workflowAsl); 158 | if (isYaml) { 159 | src.line(`definition: yaml.load(fs.readFileSync(path.join(__dirname, '${relativeFile}')).toString()),`); 160 | src.line('aslYaml: true,'); 161 | } else { 162 | src.line(`definition: JSON.parse(fs.readFileSync(path.join(__dirname, '${relativeFile}')).toString()),`); 163 | 164 | } 165 | src.close('});'); 166 | src.close('}'); 167 | src.close('}'); 168 | 169 | this.project.logger.verbose( 170 | `${basePath}: construct "${constructName}" generated under "${constructFile}"`, 171 | ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './StateMachine'; 2 | export { StepFunctionsAutoDiscover, StepFunctionsAutoDiscoverOptions } from './StepFunctionsAutoDiscover'; 3 | -------------------------------------------------------------------------------- /test/StateMachine.test.ts: -------------------------------------------------------------------------------- 1 | import { App, Stack } from 'aws-cdk-lib'; 2 | import { Template } from 'aws-cdk-lib/assertions'; 3 | import { StateMachineType } from 'aws-cdk-lib/aws-stepfunctions'; 4 | import { StateMachine } from '../src'; 5 | 6 | test('simple snapshot', () => { 7 | const stack = new Stack(new App(), 'TestingStack', {}); 8 | new StateMachine(stack, 'Test', { 9 | stateMachineName: 'A-nice-state-machine', 10 | overrides: { one: { Parameters: { thingOne: 'test passed' } } }, 11 | stateMachineType: StateMachineType.STANDARD, 12 | definition: { States: { one: { Parameters: { thingOne: 'test failed' } } } }, 13 | }); 14 | const assert = Template.fromStack(stack); 15 | expect(assert.toJSON()).toMatchSnapshot(); 16 | }); 17 | 18 | test('handles arrays', () => { 19 | const stack = new Stack(new App(), 'TestingStack', {}); 20 | let firstBranch = { 21 | StartAt: 'ResumeCluster', 22 | States: { 23 | 'ResumeCluster': { 24 | Type: 'Task', 25 | Parameters: { 26 | ClusterIdentifier: 'MyData', 27 | }, 28 | Resource: 'arn:aws:states:::aws-sdk:redshift:resumeCluster', 29 | Next: 'DescribeClusters', 30 | }, 31 | 'DescribeClusters': { 32 | Type: 'Task', 33 | Parameters: { 34 | ClusterIdentifier: '', 35 | }, 36 | Resource: 'arn:aws:states:::aws-sdk:redshift:describeClusters', 37 | Next: 'Evaluate Cluster Status', 38 | }, 39 | 'Evaluate Cluster Status': { 40 | Type: 'Choice', 41 | Choices: [ 42 | { 43 | Variable: '$.Clusters[0].ClusterStatus', 44 | StringEquals: 'available', 45 | Next: 'Redshift Pass', 46 | }, 47 | ], 48 | Default: 'Redshift Wait', 49 | }, 50 | 'Redshift Pass': { 51 | Type: 'Pass', 52 | End: true, 53 | }, 54 | 'Redshift Wait': { 55 | Type: 'Wait', 56 | Seconds: 5, 57 | Next: 'DescribeClusters', 58 | }, 59 | }, 60 | }; 61 | new StateMachine(stack, 'Test', { 62 | stateMachineName: 'A-nice-state-machine', 63 | overrides: { 64 | Branches: [{}, { 65 | StartAt: 'StartInstances', 66 | States: { 67 | StartInstances: { 68 | Parameters: { 69 | InstanceIds: ['INSTANCE_ID'], 70 | }, 71 | }, 72 | DescribeInstanceStatus: { 73 | Parameters: { 74 | InstanceIds: ['INSTANCE_ID'], 75 | }, 76 | }, 77 | }, 78 | }], 79 | }, 80 | stateMachineType: StateMachineType.STANDARD, 81 | definition: { 82 | States: { 83 | Branches: [ 84 | firstBranch, 85 | { 86 | StartAt: 'StartInstances', 87 | States: { 88 | 'StartInstances': { 89 | Type: 'Task', 90 | Parameters: { 91 | InstanceIds: [ 92 | 'MyData', 93 | ], 94 | }, 95 | Resource: 'arn:aws:states:::aws-sdk:ec2:startInstances', 96 | Next: 'DescribeInstanceStatus', 97 | }, 98 | 'DescribeInstanceStatus': { 99 | Type: 'Task', 100 | Next: 'Evaluate Instance Status', 101 | Parameters: { 102 | InstanceIds: [ 103 | 'MyData', 104 | ], 105 | }, 106 | Resource: 'arn:aws:states:::aws-sdk:ec2:describeInstanceStatus', 107 | }, 108 | 'Evaluate Instance Status': { 109 | Type: 'Choice', 110 | Choices: [ 111 | { 112 | And: [ 113 | { 114 | Variable: '$.InstanceStatuses[0].InstanceState.Name', 115 | StringEquals: 'running', 116 | }, 117 | { 118 | Variable: '$.InstanceStatuses[0].SystemStatus.Details[0].Status', 119 | StringEquals: 'passed', 120 | }, 121 | { 122 | Variable: '$.InstanceStatuses[0].InstanceStatus.Details[0].Status', 123 | StringEquals: 'passed', 124 | }, 125 | ], 126 | Next: 'EC2 Pass', 127 | }, 128 | ], 129 | Default: 'EC2 Wait', 130 | }, 131 | 'EC2 Pass': { 132 | Type: 'Pass', 133 | End: true, 134 | }, 135 | 'EC2 Wait': { 136 | Type: 'Wait', 137 | Seconds: 5, 138 | Next: 'DescribeInstanceStatus', 139 | }, 140 | }, 141 | }, 142 | ], 143 | }, 144 | }, 145 | }); 146 | const assert = Template.fromStack(stack); 147 | assert.hasResourceProperties('AWS::StepFunctions::StateMachine', { 148 | DefinitionString: JSON.stringify({ 149 | States: { 150 | Branches: [ 151 | firstBranch, 152 | { 153 | StartAt: 'StartInstances', 154 | States: { 155 | 'StartInstances': { 156 | Type: 'Task', 157 | Parameters: { 158 | InstanceIds: ['INSTANCE_ID'], 159 | }, 160 | Resource: 'arn:aws:states:::aws-sdk:ec2:startInstances', 161 | Next: 'DescribeInstanceStatus', 162 | }, 163 | 'DescribeInstanceStatus': { 164 | Type: 'Task', 165 | Next: 'Evaluate Instance Status', 166 | Parameters: { 167 | InstanceIds: ['INSTANCE_ID'], 168 | }, 169 | Resource: 'arn:aws:states:::aws-sdk:ec2:describeInstanceStatus', 170 | }, 171 | 'Evaluate Instance Status': { 172 | Type: 'Choice', 173 | Choices: [ 174 | { 175 | And: [ 176 | { 177 | Variable: '$.InstanceStatuses[0].InstanceState.Name', 178 | StringEquals: 'running', 179 | }, 180 | { 181 | Variable: '$.InstanceStatuses[0].SystemStatus.Details[0].Status', 182 | StringEquals: 'passed', 183 | }, 184 | { 185 | Variable: '$.InstanceStatuses[0].InstanceStatus.Details[0].Status', 186 | StringEquals: 'passed', 187 | }, 188 | ], 189 | Next: 'EC2 Pass', 190 | }, 191 | ], 192 | Default: 'EC2 Wait', 193 | }, 194 | 'EC2 Pass': { 195 | Type: 'Pass', 196 | End: true, 197 | }, 198 | 'EC2 Wait': { 199 | Type: 'Wait', 200 | Seconds: 5, 201 | Next: 'DescribeInstanceStatus', 202 | }, 203 | }, 204 | }, 205 | ], 206 | }, 207 | }), 208 | }); 209 | }); 210 | -------------------------------------------------------------------------------- /test/StateMachineAutoDiscover.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { mkdtempSync } from 'fs'; 3 | import * as os from 'os'; 4 | import * as path from 'path'; 5 | import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk'; 6 | import { synthSnapshot } from 'projen/lib/util/synth'; 7 | import { StepFunctionsAutoDiscover } from '../src'; 8 | import { AWS_RECOMMENDED_JSON_EXT, AWS_RECOMMENDED_YAML_EXT, JSON_STEPFUNCTION_EXT } from '../src/StepFunctionsAutoDiscover'; 9 | 10 | 11 | function setupTestProject(testFile: string, extension: string = JSON_STEPFUNCTION_EXT, srcFile: string = 'test.workflow.json') { 12 | const tempDir = mkdtempSync(path.join(os.tmpdir(), 'test2')); 13 | const project = new AwsCdkTypeScriptApp({ 14 | name: 'test', 15 | defaultReleaseBranch: 'main', 16 | cdkVersion: '2.53.0', 17 | outdir: tempDir, 18 | }); 19 | 20 | // add a test file... 21 | fs.mkdirSync(path.join(project.outdir, project.srcdir)); 22 | fs.copyFileSync( 23 | path.join(__dirname, 'step-functions', testFile), 24 | path.join(project.outdir, project.srcdir, srcFile), 25 | ); 26 | new StepFunctionsAutoDiscover(project, { extension: extension }); 27 | return project; 28 | } 29 | 30 | 31 | describe('Json', () => { 32 | test('simple case', () => { 33 | const project = setupTestProject('test.workflow.json'); 34 | const snap = synthSnapshot(project); 35 | expect(snap['src/test-statemachine.ts']).toMatchSnapshot(); 36 | }); 37 | 38 | test('more complex case', async () => { 39 | const project = setupTestProject('test2.workflow.json'); 40 | const snap = synthSnapshot(project); 41 | expect(snap['src/test-statemachine.ts']).toMatchSnapshot(); 42 | }); 43 | }); 44 | 45 | describe('yaml', () => { 46 | test('handles yaml file', async () => { 47 | const project = setupTestProject('test.yaml.asl', AWS_RECOMMENDED_YAML_EXT, 'test.yaml.asl' ); 48 | const snap = synthSnapshot(project); 49 | expect(snap['src/test-statemachine.ts']).toMatchSnapshot(); 50 | }); 51 | 52 | test('handles yml file', async () => { 53 | const project = setupTestProject('test.yaml.asl', '.yml.asl', 'test.yml.asl' ); 54 | const snap = synthSnapshot(project); 55 | expect(snap['src/test-statemachine.ts']).toMatchSnapshot(); 56 | }); 57 | 58 | }); 59 | 60 | describe('Extension parameter', () => { 61 | test('takes another extension', async () => { 62 | const project = setupTestProject('test.json.asl', AWS_RECOMMENDED_JSON_EXT, 'test.json.asl'); 63 | const snap = synthSnapshot(project); 64 | expect(snap['src/test-statemachine.ts']).toMatchSnapshot(); 65 | }); 66 | 67 | test('fails when extension does not start with a .', () => { 68 | expect(() => setupTestProject('test.json.asl', 'asdfasdf', 'test.json.asl')).toThrow('extension must start with a .'); 69 | }); 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /test/__snapshots__/StateMachine.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`simple snapshot 1`] = ` 4 | Object { 5 | "Mappings": Object { 6 | "ServiceprincipalMap": Object { 7 | "af-south-1": Object { 8 | "states": "states.af-south-1.amazonaws.com", 9 | }, 10 | "ap-east-1": Object { 11 | "states": "states.ap-east-1.amazonaws.com", 12 | }, 13 | "ap-northeast-1": Object { 14 | "states": "states.ap-northeast-1.amazonaws.com", 15 | }, 16 | "ap-northeast-2": Object { 17 | "states": "states.ap-northeast-2.amazonaws.com", 18 | }, 19 | "ap-northeast-3": Object { 20 | "states": "states.ap-northeast-3.amazonaws.com", 21 | }, 22 | "ap-south-1": Object { 23 | "states": "states.ap-south-1.amazonaws.com", 24 | }, 25 | "ap-south-2": Object { 26 | "states": "states.ap-south-2.amazonaws.com", 27 | }, 28 | "ap-southeast-1": Object { 29 | "states": "states.ap-southeast-1.amazonaws.com", 30 | }, 31 | "ap-southeast-2": Object { 32 | "states": "states.ap-southeast-2.amazonaws.com", 33 | }, 34 | "ap-southeast-3": Object { 35 | "states": "states.ap-southeast-3.amazonaws.com", 36 | }, 37 | "ca-central-1": Object { 38 | "states": "states.ca-central-1.amazonaws.com", 39 | }, 40 | "cn-north-1": Object { 41 | "states": "states.cn-north-1.amazonaws.com", 42 | }, 43 | "cn-northwest-1": Object { 44 | "states": "states.cn-northwest-1.amazonaws.com", 45 | }, 46 | "eu-central-1": Object { 47 | "states": "states.eu-central-1.amazonaws.com", 48 | }, 49 | "eu-central-2": Object { 50 | "states": "states.eu-central-2.amazonaws.com", 51 | }, 52 | "eu-north-1": Object { 53 | "states": "states.eu-north-1.amazonaws.com", 54 | }, 55 | "eu-south-1": Object { 56 | "states": "states.eu-south-1.amazonaws.com", 57 | }, 58 | "eu-south-2": Object { 59 | "states": "states.eu-south-2.amazonaws.com", 60 | }, 61 | "eu-west-1": Object { 62 | "states": "states.eu-west-1.amazonaws.com", 63 | }, 64 | "eu-west-2": Object { 65 | "states": "states.eu-west-2.amazonaws.com", 66 | }, 67 | "eu-west-3": Object { 68 | "states": "states.eu-west-3.amazonaws.com", 69 | }, 70 | "me-central-1": Object { 71 | "states": "states.me-central-1.amazonaws.com", 72 | }, 73 | "me-south-1": Object { 74 | "states": "states.me-south-1.amazonaws.com", 75 | }, 76 | "sa-east-1": Object { 77 | "states": "states.sa-east-1.amazonaws.com", 78 | }, 79 | "us-east-1": Object { 80 | "states": "states.us-east-1.amazonaws.com", 81 | }, 82 | "us-east-2": Object { 83 | "states": "states.us-east-2.amazonaws.com", 84 | }, 85 | "us-gov-east-1": Object { 86 | "states": "states.us-gov-east-1.amazonaws.com", 87 | }, 88 | "us-gov-west-1": Object { 89 | "states": "states.us-gov-west-1.amazonaws.com", 90 | }, 91 | "us-iso-east-1": Object { 92 | "states": "states.amazonaws.com", 93 | }, 94 | "us-iso-west-1": Object { 95 | "states": "states.amazonaws.com", 96 | }, 97 | "us-isob-east-1": Object { 98 | "states": "states.amazonaws.com", 99 | }, 100 | "us-west-1": Object { 101 | "states": "states.us-west-1.amazonaws.com", 102 | }, 103 | "us-west-2": Object { 104 | "states": "states.us-west-2.amazonaws.com", 105 | }, 106 | }, 107 | }, 108 | "Parameters": Object { 109 | "BootstrapVersion": Object { 110 | "Default": "/cdk-bootstrap/hnb659fds/version", 111 | "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", 112 | "Type": "AWS::SSM::Parameter::Value", 113 | }, 114 | }, 115 | "Resources": Object { 116 | "Test7BFAF513": Object { 117 | "DeletionPolicy": "Delete", 118 | "DependsOn": Array [ 119 | "TestRole17AB2208", 120 | ], 121 | "Properties": Object { 122 | "DefinitionString": "{\\"States\\":{\\"one\\":{\\"Parameters\\":{\\"thingOne\\":\\"test passed\\"}}}}", 123 | "RoleArn": Object { 124 | "Fn::GetAtt": Array [ 125 | "TestRole17AB2208", 126 | "Arn", 127 | ], 128 | }, 129 | "StateMachineName": "A-nice-state-machine", 130 | "StateMachineType": "STANDARD", 131 | }, 132 | "Type": "AWS::StepFunctions::StateMachine", 133 | "UpdateReplacePolicy": "Delete", 134 | }, 135 | "TestRole17AB2208": Object { 136 | "Properties": Object { 137 | "AssumeRolePolicyDocument": Object { 138 | "Statement": Array [ 139 | Object { 140 | "Action": "sts:AssumeRole", 141 | "Effect": "Allow", 142 | "Principal": Object { 143 | "Service": Object { 144 | "Fn::FindInMap": Array [ 145 | "ServiceprincipalMap", 146 | Object { 147 | "Ref": "AWS::Region", 148 | }, 149 | "states", 150 | ], 151 | }, 152 | }, 153 | }, 154 | ], 155 | "Version": "2012-10-17", 156 | }, 157 | }, 158 | "Type": "AWS::IAM::Role", 159 | }, 160 | }, 161 | "Rules": Object { 162 | "CheckBootstrapVersion": Object { 163 | "Assertions": Array [ 164 | Object { 165 | "Assert": Object { 166 | "Fn::Not": Array [ 167 | Object { 168 | "Fn::Contains": Array [ 169 | Array [ 170 | "1", 171 | "2", 172 | "3", 173 | "4", 174 | "5", 175 | ], 176 | Object { 177 | "Ref": "BootstrapVersion", 178 | }, 179 | ], 180 | }, 181 | ], 182 | }, 183 | "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", 184 | }, 185 | ], 186 | }, 187 | }, 188 | } 189 | `; 190 | -------------------------------------------------------------------------------- /test/__snapshots__/StateMachineAutoDiscover.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Extension parameter takes another extension 1`] = ` 4 | "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine'; 8 | import { Construct } from 'constructs'; 9 | export interface TestStateMachineOverrides { 10 | readonly 'Which database?': Partial<{ 11 | 'Type': string; 12 | 'Choices': Partial<[Partial<{ 13 | 'Variable': string; 14 | 'StringMatches': string; 15 | 'Next': string; 16 | }>, Partial<{ 17 | 'Variable': string; 18 | 'StringMatches': string; 19 | 'Next': string; 20 | }>, Partial<{ 21 | 'Variable': string; 22 | 'StringMatches': string; 23 | 'Next': string; 24 | }>]>; 25 | }>; 26 | readonly 'Get belle secret': Partial<{ 27 | 'Type': string; 28 | 'Next': string; 29 | 'Parameters': Partial<{ 30 | 'SecretId': string; 31 | }>; 32 | 'Resource': string; 33 | 'ResultPath': string; 34 | }>; 35 | readonly 'Parse belle secret value': Partial<{ 36 | 'Type': string; 37 | 'Parameters': Partial<{ 38 | 'ParsedValue.$': string; 39 | }>; 40 | 'ResultPath': string; 41 | 'Next': string; 42 | }>; 43 | readonly 'Create new database username and password': Partial<{ 44 | 'Type': string; 45 | 'Resource': string; 46 | 'Parameters': Partial<{ 47 | 'Payload.$': string; 48 | 'FunctionName': string; 49 | }>; 50 | 'Retry': Partial<[Partial<{ 51 | 'ErrorEquals': Partial<[string, string, string]>; 52 | 'IntervalSeconds': number; 53 | 'MaxAttempts': number; 54 | 'BackoffRate': number; 55 | }>]>; 56 | 'ResultPath': string; 57 | 'Comment': string; 58 | 'Next': string; 59 | 'ResultSelector': Partial<{ 60 | 'username.$': string; 61 | 'password.$': string; 62 | 'otsUrl.$': string; 63 | }>; 64 | }>; 65 | readonly 'Create user on the database': Partial<{ 66 | 'Type': string; 67 | 'Resource': string; 68 | 'Parameters': Partial<{ 69 | 'FunctionName': string; 70 | 'Payload': Partial<{ 71 | 'script.$': string; 72 | 'databaseName.$': string; 73 | }>; 74 | }>; 75 | 'Retry': Partial<[Partial<{ 76 | 'ErrorEquals': Partial<[string, string, string]>; 77 | 'IntervalSeconds': number; 78 | 'MaxAttempts': number; 79 | 'BackoffRate': number; 80 | }>]>; 81 | 'ResultPath': string; 82 | 'Next': string; 83 | }>; 84 | readonly 'Send credentials to user': Partial<{ 85 | 'Type': string; 86 | 'Parameters': Partial<{ 87 | 'Destination': Partial<{ 88 | 'ToAddresses.$': string; 89 | }>; 90 | 'Message': Partial<{ 91 | 'Body': Partial<{ 92 | 'Text': Partial<{ 93 | 'Data.$': string; 94 | }>; 95 | }>; 96 | 'Subject': Partial<{ 97 | 'Data': string; 98 | }>; 99 | }>; 100 | 'Source': string; 101 | }>; 102 | 'Resource': string; 103 | 'End': Partial<{ 104 | }>; 105 | }>; 106 | readonly 'Get engines secret': Partial<{ 107 | 'Type': string; 108 | 'Parameters': Partial<{ 109 | 'SecretId': string; 110 | }>; 111 | 'Resource': string; 112 | 'ResultPath': string; 113 | 'Next': string; 114 | }>; 115 | readonly 'Parse engines secret value': Partial<{ 116 | 'Type': string; 117 | 'Parameters': Partial<{ 118 | 'ParsedValue.$': string; 119 | }>; 120 | 'ResultPath': string; 121 | 'Next': string; 122 | }>; 123 | readonly 'Create new database username and password for engines': Partial<{ 124 | 'Type': string; 125 | 'Resource': string; 126 | 'Parameters': Partial<{ 127 | 'Payload.$': string; 128 | 'FunctionName': string; 129 | }>; 130 | 'Retry': Partial<[Partial<{ 131 | 'ErrorEquals': Partial<[string, string, string]>; 132 | 'IntervalSeconds': number; 133 | 'MaxAttempts': number; 134 | 'BackoffRate': number; 135 | }>]>; 136 | 'ResultPath': string; 137 | 'Comment': string; 138 | 'ResultSelector': Partial<{ 139 | 'username.$': string; 140 | 'password.$': string; 141 | 'otsUrl.$': string; 142 | }>; 143 | 'Next': string; 144 | }>; 145 | readonly 'Create user on jasmine': Partial<{ 146 | 'Type': string; 147 | 'Resource': string; 148 | 'Parameters': Partial<{ 149 | 'FunctionName': string; 150 | 'Payload': Partial<{ 151 | 'script.$': string; 152 | 'databaseName.$': string; 153 | }>; 154 | }>; 155 | 'Retry': Partial<[Partial<{ 156 | 'ErrorEquals': Partial<[string, string, string]>; 157 | 'IntervalSeconds': number; 158 | 'MaxAttempts': number; 159 | 'BackoffRate': number; 160 | }>]>; 161 | 'ResultPath': string; 162 | 'Next': string; 163 | }>; 164 | readonly 'Send credentials to jasmine user': Partial<{ 165 | 'Type': string; 166 | 'Parameters': Partial<{ 167 | 'Destination': Partial<{ 168 | 'ToAddresses.$': string; 169 | }>; 170 | 'Message': Partial<{ 171 | 'Body': Partial<{ 172 | 'Text': Partial<{ 173 | 'Data.$': string; 174 | }>; 175 | }>; 176 | 'Subject': Partial<{ 177 | 'Data': string; 178 | }>; 179 | }>; 180 | 'Source': string; 181 | }>; 182 | 'Resource': string; 183 | 'End': Partial<{ 184 | }>; 185 | }>; 186 | readonly 'Get anna secret': Partial<{ 187 | 'Type': string; 188 | 'Next': string; 189 | 'Parameters': Partial<{ 190 | 'SecretId': string; 191 | }>; 192 | 'Resource': string; 193 | 'ResultPath': string; 194 | }>; 195 | readonly 'Parse anna secret value': Partial<{ 196 | 'Type': string; 197 | 'Parameters': Partial<{ 198 | 'ParsedValue.$': string; 199 | }>; 200 | 'ResultPath': string; 201 | 'Next': string; 202 | }>; 203 | readonly 'Create new database username and password for anna': Partial<{ 204 | 'Type': string; 205 | 'Resource': string; 206 | 'Parameters': Partial<{ 207 | 'Payload.$': string; 208 | 'FunctionName': string; 209 | }>; 210 | 'Retry': Partial<[Partial<{ 211 | 'ErrorEquals': Partial<[string, string, string]>; 212 | 'IntervalSeconds': number; 213 | 'MaxAttempts': number; 214 | 'BackoffRate': number; 215 | }>]>; 216 | 'ResultPath': string; 217 | 'Comment': string; 218 | 'Next': string; 219 | 'ResultSelector': Partial<{ 220 | 'username.$': string; 221 | 'password.$': string; 222 | 'otsUrl.$': string; 223 | }>; 224 | }>; 225 | readonly 'Create user on the anna database': Partial<{ 226 | 'Type': string; 227 | 'Resource': string; 228 | 'Parameters': Partial<{ 229 | 'FunctionName': string; 230 | 'Payload': Partial<{ 231 | 'script.$': string; 232 | 'databaseName.$': string; 233 | }>; 234 | }>; 235 | 'Retry': Partial<[Partial<{ 236 | 'ErrorEquals': Partial<[string, string, string]>; 237 | 'IntervalSeconds': number; 238 | 'MaxAttempts': number; 239 | 'BackoffRate': number; 240 | }>]>; 241 | 'ResultPath': string; 242 | 'Next': string; 243 | }>; 244 | readonly 'Send credentials to user for anna': Partial<{ 245 | 'Type': string; 246 | 'Parameters': Partial<{ 247 | 'Destination': Partial<{ 248 | 'ToAddresses.$': string; 249 | }>; 250 | 'Message': Partial<{ 251 | 'Body': Partial<{ 252 | 'Text': Partial<{ 253 | 'Data.$': string; 254 | }>; 255 | }>; 256 | 'Subject': Partial<{ 257 | 'Data': string; 258 | }>; 259 | }>; 260 | 'Source': string; 261 | }>; 262 | 'Resource': string; 263 | 'End': Partial<{ 264 | }>; 265 | }>; 266 | } 267 | export interface TestStateMachineProps extends Omit { 268 | readonly overrides: Partial; 269 | } 270 | export class TestStateMachine extends StateMachine { 271 | constructor(scope: Construct, id: string, props: TestStateMachineProps) { 272 | super(scope, id, { 273 | ...props, 274 | definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.json.asl')).toString()), 275 | }); 276 | } 277 | }" 278 | `; 279 | 280 | exports[`Json more complex case 1`] = ` 281 | "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". 282 | import fs from 'fs'; 283 | import path from 'path'; 284 | import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine'; 285 | import { Construct } from 'constructs'; 286 | export interface TestStateMachineOverrides { 287 | readonly 'Get Pod To Execute On': Partial<{ 288 | 'Type': string; 289 | 'Resource': string; 290 | 'Parameters': Partial<{ 291 | 'FunctionName': string; 292 | 'Payload': Partial<{ 293 | 'commands': Partial<[string, string, string, string]>; 294 | }>; 295 | }>; 296 | 'Retry': Partial<[Partial<{ 297 | 'ErrorEquals': Partial<[string, string, string]>; 298 | 'IntervalSeconds': number; 299 | 'MaxAttempts': number; 300 | 'BackoffRate': number; 301 | }>]>; 302 | 'ResultSelector': Partial<{ 303 | 'belle.$': string; 304 | }>; 305 | 'ResultPath': string; 306 | 'Next': string; 307 | 'Catch': Partial<[Partial<{ 308 | 'ErrorEquals': Partial<[string]>; 309 | 'Next': string; 310 | 'ResultPath': string; 311 | }>]>; 312 | }>; 313 | readonly 'Generate Commands': Partial<{ 314 | 'Type': string; 315 | 'Resource': string; 316 | 'Parameters': Partial<{ 317 | 'Payload.$': string; 318 | 'FunctionName': string; 319 | }>; 320 | 'Retry': Partial<[Partial<{ 321 | 'ErrorEquals': Partial<[string, string, string]>; 322 | 'IntervalSeconds': number; 323 | 'MaxAttempts': number; 324 | 'BackoffRate': number; 325 | }>]>; 326 | 'ResultSelector': Partial<{ 327 | 'commands.$': string; 328 | }>; 329 | 'ResultPath': string; 330 | 'Next': string; 331 | 'Catch': Partial<[Partial<{ 332 | 'ErrorEquals': Partial<[string]>; 333 | 'Next': string; 334 | 'ResultPath': string; 335 | }>]>; 336 | }>; 337 | readonly 'Run Command': Partial<{ 338 | 'Type': string; 339 | 'Resource': string; 340 | 'Parameters': Partial<{ 341 | 'Payload': Partial<{ 342 | 'commands.$': string; 343 | }>; 344 | 'FunctionName': string; 345 | }>; 346 | 'Retry': Partial<[Partial<{ 347 | 'ErrorEquals': Partial<[string, string, string]>; 348 | 'IntervalSeconds': number; 349 | 'MaxAttempts': number; 350 | 'BackoffRate': number; 351 | }>]>; 352 | 'ResultPath': string; 353 | 'Next': string; 354 | 'Catch': Partial<[Partial<{ 355 | 'ErrorEquals': Partial<[string]>; 356 | 'Next': string; 357 | 'ResultPath': string; 358 | }>]>; 359 | }>; 360 | readonly 'Notify Slack': Partial<{ 361 | 'Type': string; 362 | 'Resource': string; 363 | 'Parameters': Partial<{ 364 | 'Payload.$': string; 365 | 'FunctionName': string; 366 | }>; 367 | 'Retry': Partial<[Partial<{ 368 | 'ErrorEquals': Partial<[string, string, string]>; 369 | 'IntervalSeconds': number; 370 | 'MaxAttempts': number; 371 | 'BackoffRate': number; 372 | }>]>; 373 | 'ResultPath': any; 374 | 'End': Partial<{ 375 | }>; 376 | 'Catch': Partial<[Partial<{ 377 | 'ErrorEquals': Partial<[string]>; 378 | 'Next': string; 379 | 'ResultPath': string; 380 | }>]>; 381 | }>; 382 | readonly 'Notify Slack of Error': Partial<{ 383 | 'Type': string; 384 | 'Resource': string; 385 | 'Parameters': Partial<{ 386 | 'Payload.$': string; 387 | 'FunctionName': string; 388 | }>; 389 | 'Retry': Partial<[Partial<{ 390 | 'ErrorEquals': Partial<[string, string, string]>; 391 | 'IntervalSeconds': number; 392 | 'MaxAttempts': number; 393 | 'BackoffRate': number; 394 | }>]>; 395 | 'End': Partial<{ 396 | }>; 397 | 'ResultPath': any; 398 | }>; 399 | } 400 | export interface TestStateMachineProps extends Omit { 401 | readonly overrides: Partial; 402 | } 403 | export class TestStateMachine extends StateMachine { 404 | constructor(scope: Construct, id: string, props: TestStateMachineProps) { 405 | super(scope, id, { 406 | ...props, 407 | definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.workflow.json')).toString()), 408 | }); 409 | } 410 | }" 411 | `; 412 | 413 | exports[`Json simple case 1`] = ` 414 | "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". 415 | import fs from 'fs'; 416 | import path from 'path'; 417 | import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine'; 418 | import { Construct } from 'constructs'; 419 | export interface TestStateMachineOverrides { 420 | readonly 'Which database?': Partial<{ 421 | 'Type': string; 422 | 'Choices': Partial<[Partial<{ 423 | 'Variable': string; 424 | 'StringMatches': string; 425 | 'Next': string; 426 | }>, Partial<{ 427 | 'Variable': string; 428 | 'StringMatches': string; 429 | 'Next': string; 430 | }>, Partial<{ 431 | 'Variable': string; 432 | 'StringMatches': string; 433 | 'Next': string; 434 | }>]>; 435 | }>; 436 | readonly 'Get belle secret': Partial<{ 437 | 'Type': string; 438 | 'Next': string; 439 | 'Parameters': Partial<{ 440 | 'SecretId': string; 441 | }>; 442 | 'Resource': string; 443 | 'ResultPath': string; 444 | }>; 445 | readonly 'Parse belle secret value': Partial<{ 446 | 'Type': string; 447 | 'Parameters': Partial<{ 448 | 'ParsedValue.$': string; 449 | }>; 450 | 'ResultPath': string; 451 | 'Next': string; 452 | }>; 453 | readonly 'Create new database username and password': Partial<{ 454 | 'Type': string; 455 | 'Resource': string; 456 | 'Parameters': Partial<{ 457 | 'Payload.$': string; 458 | 'FunctionName': string; 459 | }>; 460 | 'Retry': Partial<[Partial<{ 461 | 'ErrorEquals': Partial<[string, string, string]>; 462 | 'IntervalSeconds': number; 463 | 'MaxAttempts': number; 464 | 'BackoffRate': number; 465 | }>]>; 466 | 'ResultPath': string; 467 | 'Comment': string; 468 | 'Next': string; 469 | 'ResultSelector': Partial<{ 470 | 'username.$': string; 471 | 'password.$': string; 472 | 'otsUrl.$': string; 473 | }>; 474 | }>; 475 | readonly 'Create user on the database': Partial<{ 476 | 'Type': string; 477 | 'Resource': string; 478 | 'Parameters': Partial<{ 479 | 'FunctionName': string; 480 | 'Payload': Partial<{ 481 | 'script.$': string; 482 | 'databaseName.$': string; 483 | }>; 484 | }>; 485 | 'Retry': Partial<[Partial<{ 486 | 'ErrorEquals': Partial<[string, string, string]>; 487 | 'IntervalSeconds': number; 488 | 'MaxAttempts': number; 489 | 'BackoffRate': number; 490 | }>]>; 491 | 'ResultPath': string; 492 | 'Next': string; 493 | }>; 494 | readonly 'Send credentials to user': Partial<{ 495 | 'Type': string; 496 | 'Parameters': Partial<{ 497 | 'Destination': Partial<{ 498 | 'ToAddresses.$': string; 499 | }>; 500 | 'Message': Partial<{ 501 | 'Body': Partial<{ 502 | 'Text': Partial<{ 503 | 'Data.$': string; 504 | }>; 505 | }>; 506 | 'Subject': Partial<{ 507 | 'Data': string; 508 | }>; 509 | }>; 510 | 'Source': string; 511 | }>; 512 | 'Resource': string; 513 | 'End': Partial<{ 514 | }>; 515 | }>; 516 | readonly 'Get engines secret': Partial<{ 517 | 'Type': string; 518 | 'Parameters': Partial<{ 519 | 'SecretId': string; 520 | }>; 521 | 'Resource': string; 522 | 'ResultPath': string; 523 | 'Next': string; 524 | }>; 525 | readonly 'Parse engines secret value': Partial<{ 526 | 'Type': string; 527 | 'Parameters': Partial<{ 528 | 'ParsedValue.$': string; 529 | }>; 530 | 'ResultPath': string; 531 | 'Next': string; 532 | }>; 533 | readonly 'Create new database username and password for engines': Partial<{ 534 | 'Type': string; 535 | 'Resource': string; 536 | 'Parameters': Partial<{ 537 | 'Payload.$': string; 538 | 'FunctionName': string; 539 | }>; 540 | 'Retry': Partial<[Partial<{ 541 | 'ErrorEquals': Partial<[string, string, string]>; 542 | 'IntervalSeconds': number; 543 | 'MaxAttempts': number; 544 | 'BackoffRate': number; 545 | }>]>; 546 | 'ResultPath': string; 547 | 'Comment': string; 548 | 'ResultSelector': Partial<{ 549 | 'username.$': string; 550 | 'password.$': string; 551 | 'otsUrl.$': string; 552 | }>; 553 | 'Next': string; 554 | }>; 555 | readonly 'Create user on jasmine': Partial<{ 556 | 'Type': string; 557 | 'Resource': string; 558 | 'Parameters': Partial<{ 559 | 'FunctionName': string; 560 | 'Payload': Partial<{ 561 | 'script.$': string; 562 | 'databaseName.$': string; 563 | }>; 564 | }>; 565 | 'Retry': Partial<[Partial<{ 566 | 'ErrorEquals': Partial<[string, string, string]>; 567 | 'IntervalSeconds': number; 568 | 'MaxAttempts': number; 569 | 'BackoffRate': number; 570 | }>]>; 571 | 'ResultPath': string; 572 | 'Next': string; 573 | }>; 574 | readonly 'Send credentials to jasmine user': Partial<{ 575 | 'Type': string; 576 | 'Parameters': Partial<{ 577 | 'Destination': Partial<{ 578 | 'ToAddresses.$': string; 579 | }>; 580 | 'Message': Partial<{ 581 | 'Body': Partial<{ 582 | 'Text': Partial<{ 583 | 'Data.$': string; 584 | }>; 585 | }>; 586 | 'Subject': Partial<{ 587 | 'Data': string; 588 | }>; 589 | }>; 590 | 'Source': string; 591 | }>; 592 | 'Resource': string; 593 | 'End': Partial<{ 594 | }>; 595 | }>; 596 | readonly 'Get anna secret': Partial<{ 597 | 'Type': string; 598 | 'Next': string; 599 | 'Parameters': Partial<{ 600 | 'SecretId': string; 601 | }>; 602 | 'Resource': string; 603 | 'ResultPath': string; 604 | }>; 605 | readonly 'Parse anna secret value': Partial<{ 606 | 'Type': string; 607 | 'Parameters': Partial<{ 608 | 'ParsedValue.$': string; 609 | }>; 610 | 'ResultPath': string; 611 | 'Next': string; 612 | }>; 613 | readonly 'Create new database username and password for anna': Partial<{ 614 | 'Type': string; 615 | 'Resource': string; 616 | 'Parameters': Partial<{ 617 | 'Payload.$': string; 618 | 'FunctionName': string; 619 | }>; 620 | 'Retry': Partial<[Partial<{ 621 | 'ErrorEquals': Partial<[string, string, string]>; 622 | 'IntervalSeconds': number; 623 | 'MaxAttempts': number; 624 | 'BackoffRate': number; 625 | }>]>; 626 | 'ResultPath': string; 627 | 'Comment': string; 628 | 'Next': string; 629 | 'ResultSelector': Partial<{ 630 | 'username.$': string; 631 | 'password.$': string; 632 | 'otsUrl.$': string; 633 | }>; 634 | }>; 635 | readonly 'Create user on the anna database': Partial<{ 636 | 'Type': string; 637 | 'Resource': string; 638 | 'Parameters': Partial<{ 639 | 'FunctionName': string; 640 | 'Payload': Partial<{ 641 | 'script.$': string; 642 | 'databaseName.$': string; 643 | }>; 644 | }>; 645 | 'Retry': Partial<[Partial<{ 646 | 'ErrorEquals': Partial<[string, string, string]>; 647 | 'IntervalSeconds': number; 648 | 'MaxAttempts': number; 649 | 'BackoffRate': number; 650 | }>]>; 651 | 'ResultPath': string; 652 | 'Next': string; 653 | }>; 654 | readonly 'Send credentials to user for anna': Partial<{ 655 | 'Type': string; 656 | 'Parameters': Partial<{ 657 | 'Destination': Partial<{ 658 | 'ToAddresses.$': string; 659 | }>; 660 | 'Message': Partial<{ 661 | 'Body': Partial<{ 662 | 'Text': Partial<{ 663 | 'Data.$': string; 664 | }>; 665 | }>; 666 | 'Subject': Partial<{ 667 | 'Data': string; 668 | }>; 669 | }>; 670 | 'Source': string; 671 | }>; 672 | 'Resource': string; 673 | 'End': Partial<{ 674 | }>; 675 | }>; 676 | } 677 | export interface TestStateMachineProps extends Omit { 678 | readonly overrides: Partial; 679 | } 680 | export class TestStateMachine extends StateMachine { 681 | constructor(scope: Construct, id: string, props: TestStateMachineProps) { 682 | super(scope, id, { 683 | ...props, 684 | definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.workflow.json')).toString()), 685 | }); 686 | } 687 | }" 688 | `; 689 | 690 | exports[`yaml handles yaml file 1`] = ` 691 | "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". 692 | import fs from 'fs'; 693 | import path from 'path'; 694 | import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine'; 695 | import { Construct } from 'constructs'; 696 | import * as yaml from 'js-yaml'; 697 | export interface TestStateMachineOverrides { 698 | readonly 'Check Stock Price': Partial<{ 699 | 'Type': string; 700 | 'Resource': string; 701 | 'Next': string; 702 | }>; 703 | readonly 'Generate Buy/Sell recommendation': Partial<{ 704 | 'Type': string; 705 | 'Resource': string; 706 | 'ResultPath': string; 707 | 'Next': string; 708 | }>; 709 | readonly 'Request Human Approval': Partial<{ 710 | 'Type': string; 711 | 'Resource': string; 712 | 'Parameters': Partial<{ 713 | 'QueueUrl': string; 714 | 'MessageBody': Partial<{ 715 | 'Input.$': string; 716 | 'TaskToken.$': string; 717 | }>; 718 | }>; 719 | 'ResultPath': any; 720 | 'Next': string; 721 | }>; 722 | readonly 'Buy or Sell?': Partial<{ 723 | 'Type': string; 724 | 'Choices': Partial<[Partial<{ 725 | 'Variable': string; 726 | 'StringEquals': string; 727 | 'Next': string; 728 | }>, Partial<{ 729 | 'Variable': string; 730 | 'StringEquals': string; 731 | 'Next': string; 732 | }>]>; 733 | }>; 734 | readonly 'Buy Stock': Partial<{ 735 | 'Type': string; 736 | 'Resource': string; 737 | 'Next': string; 738 | }>; 739 | readonly 'Sell Stock': Partial<{ 740 | 'Type': string; 741 | 'Resource': string; 742 | 'Next': string; 743 | }>; 744 | readonly 'Report Result': Partial<{ 745 | 'Type': string; 746 | 'Resource': string; 747 | 'Parameters': Partial<{ 748 | 'TopicArn': string; 749 | 'Message': Partial<{ 750 | 'Input.$': string; 751 | }>; 752 | }>; 753 | 'End': Partial<{ 754 | }>; 755 | }>; 756 | } 757 | export interface TestStateMachineProps extends Omit { 758 | readonly overrides: Partial; 759 | } 760 | export class TestStateMachine extends StateMachine { 761 | constructor(scope: Construct, id: string, props: TestStateMachineProps) { 762 | super(scope, id, { 763 | ...props, 764 | definition: yaml.load(fs.readFileSync(path.join(__dirname, 'test.yaml.asl')).toString()), 765 | aslYaml: true, 766 | }); 767 | } 768 | }" 769 | `; 770 | 771 | exports[`yaml handles yml file 1`] = ` 772 | "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". 773 | import fs from 'fs'; 774 | import path from 'path'; 775 | import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine'; 776 | import { Construct } from 'constructs'; 777 | import * as yaml from 'js-yaml'; 778 | export interface TestStateMachineOverrides { 779 | readonly 'Check Stock Price': Partial<{ 780 | 'Type': string; 781 | 'Resource': string; 782 | 'Next': string; 783 | }>; 784 | readonly 'Generate Buy/Sell recommendation': Partial<{ 785 | 'Type': string; 786 | 'Resource': string; 787 | 'ResultPath': string; 788 | 'Next': string; 789 | }>; 790 | readonly 'Request Human Approval': Partial<{ 791 | 'Type': string; 792 | 'Resource': string; 793 | 'Parameters': Partial<{ 794 | 'QueueUrl': string; 795 | 'MessageBody': Partial<{ 796 | 'Input.$': string; 797 | 'TaskToken.$': string; 798 | }>; 799 | }>; 800 | 'ResultPath': any; 801 | 'Next': string; 802 | }>; 803 | readonly 'Buy or Sell?': Partial<{ 804 | 'Type': string; 805 | 'Choices': Partial<[Partial<{ 806 | 'Variable': string; 807 | 'StringEquals': string; 808 | 'Next': string; 809 | }>, Partial<{ 810 | 'Variable': string; 811 | 'StringEquals': string; 812 | 'Next': string; 813 | }>]>; 814 | }>; 815 | readonly 'Buy Stock': Partial<{ 816 | 'Type': string; 817 | 'Resource': string; 818 | 'Next': string; 819 | }>; 820 | readonly 'Sell Stock': Partial<{ 821 | 'Type': string; 822 | 'Resource': string; 823 | 'Next': string; 824 | }>; 825 | readonly 'Report Result': Partial<{ 826 | 'Type': string; 827 | 'Resource': string; 828 | 'Parameters': Partial<{ 829 | 'TopicArn': string; 830 | 'Message': Partial<{ 831 | 'Input.$': string; 832 | }>; 833 | }>; 834 | 'End': Partial<{ 835 | }>; 836 | }>; 837 | } 838 | export interface TestStateMachineProps extends Omit { 839 | readonly overrides: Partial; 840 | } 841 | export class TestStateMachine extends StateMachine { 842 | constructor(scope: Construct, id: string, props: TestStateMachineProps) { 843 | super(scope, id, { 844 | ...props, 845 | definition: yaml.load(fs.readFileSync(path.join(__dirname, 'test.yml.asl')).toString()), 846 | aslYaml: true, 847 | }); 848 | } 849 | }" 850 | `; 851 | -------------------------------------------------------------------------------- /test/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Comment": "A description of my state machine", 3 | "StartAt": "Read database credentials secret", 4 | "States": { 5 | "Read database credentials secret": { 6 | "Type": "Task", 7 | "Parameters": { 8 | "SecretId": "DbSecret685A0FA5-GXCINaDfwzla" 9 | }, 10 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 11 | "Next": "Parse SecretString", 12 | "ResultPath": "$.dbSecret" 13 | }, 14 | "Parse SecretString": { 15 | "Type": "Pass", 16 | "Next": "Generate username and password", 17 | "InputPath": "$.dbSecret", 18 | "Parameters": { 19 | "Value.$": "States.StringToJson($.SecretString)" 20 | }, 21 | "ResultPath": "$.dbSecret" 22 | }, 23 | "Generate username and password": { 24 | "Type": "Task", 25 | "Resource": "arn:aws:states:::lambda:invoke", 26 | "Parameters": { 27 | "Payload.$": "$", 28 | "FunctionName": "arn:aws:lambda:us-east-1:536309290949:function:step-functions-with-the-c-GenerateUsernameAndPassw-OZ3vhotMEJPK" 29 | }, 30 | "Retry": [ 31 | { 32 | "ErrorEquals": [ 33 | "Lambda.ServiceException", 34 | "Lambda.AWSLambdaException", 35 | "Lambda.SdkClientException" 36 | ], 37 | "IntervalSeconds": 2, 38 | "MaxAttempts": 6, 39 | "BackoffRate": 2 40 | } 41 | ], 42 | "Next": "Invoke SQL script against database", 43 | "ResultPath": "$.usernameAndPassword", 44 | "ResultSelector": { 45 | "Username.$": "$.Payload.username", 46 | "Password.$": "$.Payload.password" 47 | } 48 | }, 49 | "Invoke SQL script against database": { 50 | "Type": "Task", 51 | "Resource": "arn:aws:states:::lambda:invoke", 52 | "Parameters": { 53 | "Payload.$": "$", 54 | "FunctionName": "arn:aws:lambda:us-east-1:536309290949:function:step-functions-with-the-cdk-ScriptProvider32E26F41-DBeWWSBNMARo" 55 | }, 56 | "Retry": [ 57 | { 58 | "ErrorEquals": [ 59 | "Lambda.ServiceException", 60 | "Lambda.AWSLambdaException", 61 | "Lambda.SdkClientException" 62 | ], 63 | "IntervalSeconds": 2, 64 | "MaxAttempts": 6, 65 | "BackoffRate": 2 66 | } 67 | ], 68 | "Next": "Generate OTS", 69 | "ResultPath": "$.invokeOutput" 70 | }, 71 | "Generate OTS": { 72 | "Type": "Task", 73 | "Resource": "arn:aws:states:::lambda:invoke", 74 | "Parameters": { 75 | "Payload.$": "$", 76 | "FunctionName": "arn:aws:lambda:us-east-1:536309290949:function:step-functions-with-the-cdk-GenerateOtsBE7C09B0-nTIEu0OxGURq" 77 | }, 78 | "Retry": [ 79 | { 80 | "ErrorEquals": [ 81 | "Lambda.ServiceException", 82 | "Lambda.AWSLambdaException", 83 | "Lambda.SdkClientException" 84 | ], 85 | "IntervalSeconds": 2, 86 | "MaxAttempts": 6, 87 | "BackoffRate": 2 88 | } 89 | ], 90 | "Next": "Send email", 91 | "ResultPath": "$.ots", 92 | "ResultSelector": { 93 | "OtsUrl.$": "$.Payload.otsUrl" 94 | } 95 | }, 96 | "Send email": { 97 | "Type": "Task", 98 | "End": true, 99 | "Parameters": { 100 | "FromEmailAddress": "matthew.bonig@gmail.com", 101 | "Content": { 102 | "Simple": { 103 | "Body": { 104 | "Html": { 105 | "Data": "You're getting new creds!" 106 | }, 107 | "Text": { 108 | "Data": "You're getting new creds!" 109 | } 110 | }, 111 | "Subject": { 112 | "Data": "You're getting new creds!" 113 | } 114 | } 115 | }, 116 | "Destination": { 117 | "ToAddresses.$": "States.Array($.email)" 118 | } 119 | }, 120 | "Resource": "arn:aws:states:::aws-sdk:sesv2:sendEmail" 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /test/step-functions/test.json.asl: -------------------------------------------------------------------------------- 1 | { 2 | "StartAt": "Which database?", 3 | "States": { 4 | "Which database?": { 5 | "Type": "Choice", 6 | "Choices": [ 7 | { 8 | "Variable": "$.databaseName", 9 | "StringMatches": "belle*", 10 | "Next": "Get belle secret" 11 | }, 12 | { 13 | "Variable": "$.databaseName", 14 | "StringMatches": "jasmine*", 15 | "Next": "Get engines secret" 16 | }, 17 | { 18 | "Variable": "$.databaseName", 19 | "StringMatches": "anna*", 20 | "Next": "Get anna secret" 21 | } 22 | ] 23 | }, 24 | "Get belle secret": { 25 | "Type": "Task", 26 | "Next": "Parse belle secret value", 27 | "Parameters": { 28 | "SecretId": "qa2/belle-sql-secrets" 29 | }, 30 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 31 | "ResultPath": "$.databaseSecret" 32 | }, 33 | "Parse belle secret value": { 34 | "Type": "Pass", 35 | "Parameters": { 36 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 37 | }, 38 | "ResultPath": "$.databaseSecret", 39 | "Next": "Create new database username and password" 40 | }, 41 | "Create new database username and password": { 42 | "Type": "Task", 43 | "Resource": "arn:aws:states:::lambda:invoke", 44 | "Parameters": { 45 | "Payload.$": "$", 46 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW" 47 | }, 48 | "Retry": [ 49 | { 50 | "ErrorEquals": [ 51 | "Lambda.ServiceException", 52 | "Lambda.AWSLambdaException", 53 | "Lambda.SdkClientException" 54 | ], 55 | "IntervalSeconds": 2, 56 | "MaxAttempts": 6, 57 | "BackoffRate": 2 58 | } 59 | ], 60 | "ResultPath": "$.newCreds", 61 | "Comment": "returns back {username,password}", 62 | "Next": "Create user on the database", 63 | "ResultSelector": { 64 | "username.$": "$.Payload.username", 65 | "password.$": "$.Payload.password", 66 | "otsUrl.$": "$.Payload.otsUrl" 67 | } 68 | }, 69 | "Create user on the database": { 70 | "Type": "Task", 71 | "Resource": "arn:aws:states:::lambda:invoke", 72 | "Parameters": { 73 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 74 | "Payload": { 75 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.SQL_USER,$.newCreds.username)", 76 | "databaseName.$": "$.databaseSecret.ParsedValue.SQL_DATABASE" 77 | } 78 | }, 79 | "Retry": [ 80 | { 81 | "ErrorEquals": [ 82 | "Lambda.ServiceException", 83 | "Lambda.AWSLambdaException", 84 | "Lambda.SdkClientException" 85 | ], 86 | "IntervalSeconds": 2, 87 | "MaxAttempts": 6, 88 | "BackoffRate": 2 89 | } 90 | ], 91 | "ResultPath": "$.databaseResults", 92 | "Next": "Send credentials to user" 93 | }, 94 | "Send credentials to user": { 95 | "Type": "Task", 96 | "Parameters": { 97 | "Destination": { 98 | "ToAddresses.$": "States.Array($.email)" 99 | }, 100 | "Message": { 101 | "Body": { 102 | "Text": { 103 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.SQL_DATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.SQL_HOST)" 104 | } 105 | }, 106 | "Subject": { 107 | "Data": "New database access credentials have been created for you!" 108 | } 109 | }, 110 | "Source": "devops@momnt.com" 111 | }, 112 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 113 | "End": true 114 | }, 115 | "Get engines secret": { 116 | "Type": "Task", 117 | "Parameters": { 118 | "SecretId": "qa2/engines-secrets" 119 | }, 120 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 121 | "ResultPath": "$.databaseSecret", 122 | "Next": "Parse engines secret value" 123 | }, 124 | "Parse engines secret value": { 125 | "Type": "Pass", 126 | "Parameters": { 127 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 128 | }, 129 | "ResultPath": "$.databaseSecret", 130 | "Next": "Create new database username and password for engines" 131 | }, 132 | "Create new database username and password for engines": { 133 | "Type": "Task", 134 | "Resource": "arn:aws:states:::lambda:invoke", 135 | "Parameters": { 136 | "Payload.$": "$", 137 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Database-UserDatabaseCreatorPersonalUsernameGe-dmIs8HXvJAYc" 138 | }, 139 | "Retry": [ 140 | { 141 | "ErrorEquals": [ 142 | "Lambda.ServiceException", 143 | "Lambda.AWSLambdaException", 144 | "Lambda.SdkClientException" 145 | ], 146 | "IntervalSeconds": 2, 147 | "MaxAttempts": 6, 148 | "BackoffRate": 2 149 | } 150 | ], 151 | "ResultPath": "$.newCreds", 152 | "Comment": "returns back {username,password}", 153 | "ResultSelector": { 154 | "username.$": "$.Payload.username", 155 | "password.$": "$.Payload.password", 156 | "otsUrl.$": "$.Payload.otsUrl" 157 | }, 158 | "Next": "Create user on jasmine" 159 | }, 160 | "Create user on jasmine": { 161 | "Type": "Task", 162 | "Resource": "arn:aws:states:::lambda:invoke", 163 | "Parameters": { 164 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 165 | "Payload": { 166 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA engine_rules to {}; GRANT ALL PRIVILEGES ON SCHEMA engine_rules to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)", 167 | "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE" 168 | } 169 | }, 170 | "Retry": [ 171 | { 172 | "ErrorEquals": [ 173 | "Lambda.ServiceException", 174 | "Lambda.AWSLambdaException", 175 | "Lambda.SdkClientException" 176 | ], 177 | "IntervalSeconds": 2, 178 | "MaxAttempts": 6, 179 | "BackoffRate": 2 180 | } 181 | ], 182 | "ResultPath": "$.databaseResults", 183 | "Next": "Send credentials to jasmine user" 184 | }, 185 | "Send credentials to jasmine user": { 186 | "Type": "Task", 187 | "Parameters": { 188 | "Destination": { 189 | "ToAddresses.$": "States.Array($.email)" 190 | }, 191 | "Message": { 192 | "Body": { 193 | "Text": { 194 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)" 195 | } 196 | }, 197 | "Subject": { 198 | "Data": "New database access credentials have been created for you!" 199 | } 200 | }, 201 | "Source": "devops@momnt.com" 202 | }, 203 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 204 | "End": true 205 | }, 206 | 207 | "Get anna secret": { 208 | "Type": "Task", 209 | "Next": "Parse anna secret value", 210 | "Parameters": { 211 | "SecretId": "qa2/anna-sql-secrets" 212 | }, 213 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 214 | "ResultPath": "$.databaseSecret" 215 | }, 216 | "Parse anna secret value": { 217 | "Type": "Pass", 218 | "Parameters": { 219 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 220 | }, 221 | "ResultPath": "$.databaseSecret", 222 | "Next": "Create new database username and password for anna" 223 | }, 224 | "Create new database username and password for anna": { 225 | "Type": "Task", 226 | "Resource": "arn:aws:states:::lambda:invoke", 227 | "Parameters": { 228 | "Payload.$": "$", 229 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW" 230 | }, 231 | "Retry": [ 232 | { 233 | "ErrorEquals": [ 234 | "Lambda.ServiceException", 235 | "Lambda.AWSLambdaException", 236 | "Lambda.SdkClientException" 237 | ], 238 | "IntervalSeconds": 2, 239 | "MaxAttempts": 6, 240 | "BackoffRate": 2 241 | } 242 | ], 243 | "ResultPath": "$.newCreds", 244 | "Comment": "returns back {username,password}", 245 | "Next": "Create user on the anna database", 246 | "ResultSelector": { 247 | "username.$": "$.Payload.username", 248 | "password.$": "$.Payload.password", 249 | "otsUrl.$": "$.Payload.otsUrl" 250 | } 251 | }, 252 | "Create user on the anna database": { 253 | "Type": "Task", 254 | "Resource": "arn:aws:states:::lambda:invoke", 255 | "Parameters": { 256 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 257 | "Payload": { 258 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)", 259 | "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE" 260 | } 261 | }, 262 | "Retry": [ 263 | { 264 | "ErrorEquals": [ 265 | "Lambda.ServiceException", 266 | "Lambda.AWSLambdaException", 267 | "Lambda.SdkClientException" 268 | ], 269 | "IntervalSeconds": 2, 270 | "MaxAttempts": 6, 271 | "BackoffRate": 2 272 | } 273 | ], 274 | "ResultPath": "$.databaseResults", 275 | "Next": "Send credentials to user for anna" 276 | }, 277 | "Send credentials to user for anna": { 278 | "Type": "Task", 279 | "Parameters": { 280 | "Destination": { 281 | "ToAddresses.$": "States.Array($.email)" 282 | }, 283 | "Message": { 284 | "Body": { 285 | "Text": { 286 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)" 287 | } 288 | }, 289 | "Subject": { 290 | "Data": "New database access credentials have been created for you!" 291 | } 292 | }, 293 | "Source": "devops@momnt.com" 294 | }, 295 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 296 | "End": true 297 | } 298 | }, 299 | "Comment": "must provide:\nusername (will be prefix)\ndatabaseName (jasmine or belle, don't have to have full name, but should be able to?)\nemail (the email address to send results to)" 300 | } 301 | -------------------------------------------------------------------------------- /test/step-functions/test.workflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "StartAt": "Which database?", 3 | "States": { 4 | "Which database?": { 5 | "Type": "Choice", 6 | "Choices": [ 7 | { 8 | "Variable": "$.databaseName", 9 | "StringMatches": "belle*", 10 | "Next": "Get belle secret" 11 | }, 12 | { 13 | "Variable": "$.databaseName", 14 | "StringMatches": "jasmine*", 15 | "Next": "Get engines secret" 16 | }, 17 | { 18 | "Variable": "$.databaseName", 19 | "StringMatches": "anna*", 20 | "Next": "Get anna secret" 21 | } 22 | ] 23 | }, 24 | "Get belle secret": { 25 | "Type": "Task", 26 | "Next": "Parse belle secret value", 27 | "Parameters": { 28 | "SecretId": "qa2/belle-sql-secrets" 29 | }, 30 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 31 | "ResultPath": "$.databaseSecret" 32 | }, 33 | "Parse belle secret value": { 34 | "Type": "Pass", 35 | "Parameters": { 36 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 37 | }, 38 | "ResultPath": "$.databaseSecret", 39 | "Next": "Create new database username and password" 40 | }, 41 | "Create new database username and password": { 42 | "Type": "Task", 43 | "Resource": "arn:aws:states:::lambda:invoke", 44 | "Parameters": { 45 | "Payload.$": "$", 46 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW" 47 | }, 48 | "Retry": [ 49 | { 50 | "ErrorEquals": [ 51 | "Lambda.ServiceException", 52 | "Lambda.AWSLambdaException", 53 | "Lambda.SdkClientException" 54 | ], 55 | "IntervalSeconds": 2, 56 | "MaxAttempts": 6, 57 | "BackoffRate": 2 58 | } 59 | ], 60 | "ResultPath": "$.newCreds", 61 | "Comment": "returns back {username,password}", 62 | "Next": "Create user on the database", 63 | "ResultSelector": { 64 | "username.$": "$.Payload.username", 65 | "password.$": "$.Payload.password", 66 | "otsUrl.$": "$.Payload.otsUrl" 67 | } 68 | }, 69 | "Create user on the database": { 70 | "Type": "Task", 71 | "Resource": "arn:aws:states:::lambda:invoke", 72 | "Parameters": { 73 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 74 | "Payload": { 75 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.SQL_USER,$.newCreds.username)", 76 | "databaseName.$": "$.databaseSecret.ParsedValue.SQL_DATABASE" 77 | } 78 | }, 79 | "Retry": [ 80 | { 81 | "ErrorEquals": [ 82 | "Lambda.ServiceException", 83 | "Lambda.AWSLambdaException", 84 | "Lambda.SdkClientException" 85 | ], 86 | "IntervalSeconds": 2, 87 | "MaxAttempts": 6, 88 | "BackoffRate": 2 89 | } 90 | ], 91 | "ResultPath": "$.databaseResults", 92 | "Next": "Send credentials to user" 93 | }, 94 | "Send credentials to user": { 95 | "Type": "Task", 96 | "Parameters": { 97 | "Destination": { 98 | "ToAddresses.$": "States.Array($.email)" 99 | }, 100 | "Message": { 101 | "Body": { 102 | "Text": { 103 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.SQL_DATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.SQL_HOST)" 104 | } 105 | }, 106 | "Subject": { 107 | "Data": "New database access credentials have been created for you!" 108 | } 109 | }, 110 | "Source": "devops@momnt.com" 111 | }, 112 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 113 | "End": true 114 | }, 115 | "Get engines secret": { 116 | "Type": "Task", 117 | "Parameters": { 118 | "SecretId": "qa2/engines-secrets" 119 | }, 120 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 121 | "ResultPath": "$.databaseSecret", 122 | "Next": "Parse engines secret value" 123 | }, 124 | "Parse engines secret value": { 125 | "Type": "Pass", 126 | "Parameters": { 127 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 128 | }, 129 | "ResultPath": "$.databaseSecret", 130 | "Next": "Create new database username and password for engines" 131 | }, 132 | "Create new database username and password for engines": { 133 | "Type": "Task", 134 | "Resource": "arn:aws:states:::lambda:invoke", 135 | "Parameters": { 136 | "Payload.$": "$", 137 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Database-UserDatabaseCreatorPersonalUsernameGe-dmIs8HXvJAYc" 138 | }, 139 | "Retry": [ 140 | { 141 | "ErrorEquals": [ 142 | "Lambda.ServiceException", 143 | "Lambda.AWSLambdaException", 144 | "Lambda.SdkClientException" 145 | ], 146 | "IntervalSeconds": 2, 147 | "MaxAttempts": 6, 148 | "BackoffRate": 2 149 | } 150 | ], 151 | "ResultPath": "$.newCreds", 152 | "Comment": "returns back {username,password}", 153 | "ResultSelector": { 154 | "username.$": "$.Payload.username", 155 | "password.$": "$.Payload.password", 156 | "otsUrl.$": "$.Payload.otsUrl" 157 | }, 158 | "Next": "Create user on jasmine" 159 | }, 160 | "Create user on jasmine": { 161 | "Type": "Task", 162 | "Resource": "arn:aws:states:::lambda:invoke", 163 | "Parameters": { 164 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 165 | "Payload": { 166 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA engine_rules to {}; GRANT ALL PRIVILEGES ON SCHEMA engine_rules to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)", 167 | "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE" 168 | } 169 | }, 170 | "Retry": [ 171 | { 172 | "ErrorEquals": [ 173 | "Lambda.ServiceException", 174 | "Lambda.AWSLambdaException", 175 | "Lambda.SdkClientException" 176 | ], 177 | "IntervalSeconds": 2, 178 | "MaxAttempts": 6, 179 | "BackoffRate": 2 180 | } 181 | ], 182 | "ResultPath": "$.databaseResults", 183 | "Next": "Send credentials to jasmine user" 184 | }, 185 | "Send credentials to jasmine user": { 186 | "Type": "Task", 187 | "Parameters": { 188 | "Destination": { 189 | "ToAddresses.$": "States.Array($.email)" 190 | }, 191 | "Message": { 192 | "Body": { 193 | "Text": { 194 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)" 195 | } 196 | }, 197 | "Subject": { 198 | "Data": "New database access credentials have been created for you!" 199 | } 200 | }, 201 | "Source": "devops@momnt.com" 202 | }, 203 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 204 | "End": true 205 | }, 206 | 207 | "Get anna secret": { 208 | "Type": "Task", 209 | "Next": "Parse anna secret value", 210 | "Parameters": { 211 | "SecretId": "qa2/anna-sql-secrets" 212 | }, 213 | "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue", 214 | "ResultPath": "$.databaseSecret" 215 | }, 216 | "Parse anna secret value": { 217 | "Type": "Pass", 218 | "Parameters": { 219 | "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)" 220 | }, 221 | "ResultPath": "$.databaseSecret", 222 | "Next": "Create new database username and password for anna" 223 | }, 224 | "Create new database username and password for anna": { 225 | "Type": "Task", 226 | "Resource": "arn:aws:states:::lambda:invoke", 227 | "Parameters": { 228 | "Payload.$": "$", 229 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW" 230 | }, 231 | "Retry": [ 232 | { 233 | "ErrorEquals": [ 234 | "Lambda.ServiceException", 235 | "Lambda.AWSLambdaException", 236 | "Lambda.SdkClientException" 237 | ], 238 | "IntervalSeconds": 2, 239 | "MaxAttempts": 6, 240 | "BackoffRate": 2 241 | } 242 | ], 243 | "ResultPath": "$.newCreds", 244 | "Comment": "returns back {username,password}", 245 | "Next": "Create user on the anna database", 246 | "ResultSelector": { 247 | "username.$": "$.Payload.username", 248 | "password.$": "$.Payload.password", 249 | "otsUrl.$": "$.Payload.otsUrl" 250 | } 251 | }, 252 | "Create user on the anna database": { 253 | "Type": "Task", 254 | "Resource": "arn:aws:states:::lambda:invoke", 255 | "Parameters": { 256 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc", 257 | "Payload": { 258 | "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)", 259 | "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE" 260 | } 261 | }, 262 | "Retry": [ 263 | { 264 | "ErrorEquals": [ 265 | "Lambda.ServiceException", 266 | "Lambda.AWSLambdaException", 267 | "Lambda.SdkClientException" 268 | ], 269 | "IntervalSeconds": 2, 270 | "MaxAttempts": 6, 271 | "BackoffRate": 2 272 | } 273 | ], 274 | "ResultPath": "$.databaseResults", 275 | "Next": "Send credentials to user for anna" 276 | }, 277 | "Send credentials to user for anna": { 278 | "Type": "Task", 279 | "Parameters": { 280 | "Destination": { 281 | "ToAddresses.$": "States.Array($.email)" 282 | }, 283 | "Message": { 284 | "Body": { 285 | "Text": { 286 | "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)" 287 | } 288 | }, 289 | "Subject": { 290 | "Data": "New database access credentials have been created for you!" 291 | } 292 | }, 293 | "Source": "devops@momnt.com" 294 | }, 295 | "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail", 296 | "End": true 297 | } 298 | }, 299 | "Comment": "must provide:\nusername (will be prefix)\ndatabaseName (jasmine or belle, don't have to have full name, but should be able to?)\nemail (the email address to send results to)" 300 | } 301 | -------------------------------------------------------------------------------- /test/step-functions/test.yaml.asl: -------------------------------------------------------------------------------- 1 | StartAt: Check Stock Price 2 | States: 3 | Check Stock Price: 4 | Type: Task 5 | Resource: >- 6 | arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLam-CheckStockPriceLambda-9Ql8L0vFGNyX 7 | Next: Generate Buy/Sell recommendation 8 | Generate Buy/Sell recommendation: 9 | Type: Task 10 | Resource: >- 11 | arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-Hello-GenerateBuySellRecommend-RV43mVlWrlRp 12 | ResultPath: $.recommended_type 13 | Next: Request Human Approval 14 | Request Human Approval: 15 | Type: Task 16 | Resource: arn:aws:states:::sqs:sendMessage.waitForTaskToken 17 | Parameters: 18 | QueueUrl: >- 19 | https://sqs.us-east-1.amazonaws.com/843153345658/StepFunctionsSample-HelloLambda1cfd9e64-a1b-RequestHumanApprovalSqs-1ctyDMjtCfTL 20 | MessageBody: 21 | Input.$: $ 22 | TaskToken.$: $$.Task.Token 23 | ResultPath: null 24 | Next: Buy or Sell? 25 | Buy or Sell?: 26 | Type: Choice 27 | Choices: 28 | - Variable: $.recommended_type 29 | StringEquals: buy 30 | Next: Buy Stock 31 | - Variable: $.recommended_type 32 | StringEquals: sell 33 | Next: Sell Stock 34 | Buy Stock: 35 | Type: Task 36 | Resource: >- 37 | arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLambda1cfd-BuyStockLambda-ggejR4EeAftC 38 | Next: Report Result 39 | Sell Stock: 40 | Type: Task 41 | Resource: >- 42 | arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLambda1cf-SellStockLambda-HY85TtYKY9xk 43 | Next: Report Result 44 | Report Result: 45 | Type: Task 46 | Resource: arn:aws:states:::sns:publish 47 | Parameters: 48 | TopicArn: >- 49 | arn:aws:sns:us-east-1:843153345658:StepFunctionsSample-HelloLambda1cfd9e64-a1bb-45f1-b2b0-7e258d8ba55f-ReportResultSnsTopic-GABSbYyOACQR 50 | Message: 51 | Input.$: $ 52 | End: true 53 | -------------------------------------------------------------------------------- /test/step-functions/test2.workflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Comment": "A description of my state machine", 3 | "StartAt": "Get Pod To Execute On", 4 | "States": { 5 | "Get Pod To Execute On": { 6 | "Type": "Task", 7 | "Resource": "arn:aws:states:::lambda:invoke", 8 | "Parameters": { 9 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Cluster-KubectlProviderAD5CE4B2-MASGyPp0MUAp", 10 | "Payload": { 11 | "commands": [ 12 | "get", 13 | "pods", 14 | "--selector=app=belle", 15 | "-ojson" 16 | ] 17 | } 18 | }, 19 | "Retry": [ 20 | { 21 | "ErrorEquals": [ 22 | "Lambda.ServiceException", 23 | "Lambda.AWSLambdaException", 24 | "Lambda.SdkClientException" 25 | ], 26 | "IntervalSeconds": 2, 27 | "MaxAttempts": 6, 28 | "BackoffRate": 2 29 | } 30 | ], 31 | "ResultSelector": { 32 | "belle.$": "$.Payload" 33 | }, 34 | "ResultPath": "$.pods", 35 | "Next": "Generate Commands", 36 | "Catch": [ 37 | { 38 | "ErrorEquals": [ 39 | "States.ALL" 40 | ], 41 | "Next": "Notify Slack of Error", 42 | "ResultPath": "$.error" 43 | } 44 | ] 45 | }, 46 | "Generate Commands": { 47 | "Type": "Task", 48 | "Resource": "arn:aws:states:::lambda:invoke", 49 | "Parameters": { 50 | "Payload.$": "$", 51 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Cluster-GetConfigGetCommands036E8CAC-bfIUgcupKlMI" 52 | }, 53 | "Retry": [ 54 | { 55 | "ErrorEquals": [ 56 | "Lambda.ServiceException", 57 | "Lambda.AWSLambdaException", 58 | "Lambda.SdkClientException" 59 | ], 60 | "IntervalSeconds": 2, 61 | "MaxAttempts": 6, 62 | "BackoffRate": 2 63 | } 64 | ], 65 | "ResultSelector": { 66 | "commands.$": "$.Payload.commands" 67 | }, 68 | "ResultPath": "$.commands", 69 | "Next": "Run Command", 70 | "Catch": [ 71 | { 72 | "ErrorEquals": [ 73 | "States.ALL" 74 | ], 75 | "Next": "Notify Slack of Error", 76 | "ResultPath": "$.error" 77 | } 78 | ] 79 | }, 80 | "Run Command": { 81 | "Type": "Task", 82 | "Resource": "arn:aws:states:::lambda:invoke", 83 | "Parameters": { 84 | "Payload": { 85 | "commands.$": "$.commands.commands" 86 | }, 87 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Cluster-KubectlProviderAD5CE4B2-MASGyPp0MUAp" 88 | }, 89 | "Retry": [ 90 | { 91 | "ErrorEquals": [ 92 | "Lambda.ServiceException", 93 | "Lambda.AWSLambdaException", 94 | "Lambda.SdkClientException" 95 | ], 96 | "IntervalSeconds": 2, 97 | "MaxAttempts": 6, 98 | "BackoffRate": 2 99 | } 100 | ], 101 | "ResultPath": "$.commandResults", 102 | "Next": "Notify Slack", 103 | "Catch": [ 104 | { 105 | "ErrorEquals": [ 106 | "States.ALL" 107 | ], 108 | "Next": "Notify Slack of Error", 109 | "ResultPath": "$.error" 110 | } 111 | ] 112 | }, 113 | "Notify Slack": { 114 | "Type": "Task", 115 | "Resource": "arn:aws:states:::lambda:invoke", 116 | "Parameters": { 117 | "Payload.$": "$", 118 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Cluster-GetConfigNotifySlackHandlerCF56DE5E-flBCIqABv0mk" 119 | }, 120 | "Retry": [ 121 | { 122 | "ErrorEquals": [ 123 | "Lambda.ServiceException", 124 | "Lambda.AWSLambdaException", 125 | "Lambda.SdkClientException" 126 | ], 127 | "IntervalSeconds": 2, 128 | "MaxAttempts": 6, 129 | "BackoffRate": 2 130 | } 131 | ], 132 | "ResultPath": null, 133 | "End": true, 134 | "Catch": [ 135 | { 136 | "ErrorEquals": [ 137 | "States.ALL" 138 | ], 139 | "Next": "Notify Slack of Error", 140 | "ResultPath": "$.error" 141 | } 142 | ] 143 | }, 144 | "Notify Slack of Error": { 145 | "Type": "Task", 146 | "Resource": "arn:aws:states:::lambda:invoke", 147 | "Parameters": { 148 | "Payload.$": "$", 149 | "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Cluster-GetConfigNotifySlackErrorHandlerA0AA40-JVUiGyKvwHRm" 150 | }, 151 | "Retry": [ 152 | { 153 | "ErrorEquals": [ 154 | "Lambda.ServiceException", 155 | "Lambda.AWSLambdaException", 156 | "Lambda.SdkClientException" 157 | ], 158 | "IntervalSeconds": 2, 159 | "MaxAttempts": 6, 160 | "BackoffRate": 2 161 | } 162 | ], 163 | "End": true, 164 | "ResultPath": null 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2019" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2019" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------