├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── API.md ├── LICENSE ├── README.md ├── image ├── .dockerignore ├── Dockerfile └── entrypoint.sh ├── package.json ├── src └── index.ts ├── test-reports └── junit.xml ├── test └── hello.test.ts ├── 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 | "plugin:prettier/recommended" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.ts", 44 | "!projenrc/**/*.ts" 45 | ], 46 | "rules": { 47 | "curly": [ 48 | "error", 49 | "multi-line", 50 | "consistent" 51 | ], 52 | "@typescript-eslint/no-require-imports": "error", 53 | "import/no-extraneous-dependencies": [ 54 | "error", 55 | { 56 | "devDependencies": [ 57 | "**/test/**", 58 | "**/build-tools/**", 59 | ".projenrc.ts", 60 | "projenrc/**/*.ts" 61 | ], 62 | "optionalDependencies": false, 63 | "peerDependencies": true 64 | } 65 | ], 66 | "import/no-unresolved": [ 67 | "error" 68 | ], 69 | "import/order": [ 70 | "warn", 71 | { 72 | "groups": [ 73 | "builtin", 74 | "external" 75 | ], 76 | "alphabetize": { 77 | "order": "asc", 78 | "caseInsensitive": true 79 | } 80 | } 81 | ], 82 | "import/no-duplicates": [ 83 | "error" 84 | ], 85 | "no-shadow": [ 86 | "off" 87 | ], 88 | "@typescript-eslint/no-shadow": "error", 89 | "@typescript-eslint/no-floating-promises": "error", 90 | "no-return-await": [ 91 | "off" 92 | ], 93 | "@typescript-eslint/return-await": "error", 94 | "dot-notation": [ 95 | "error" 96 | ], 97 | "no-bitwise": [ 98 | "error" 99 | ], 100 | "@typescript-eslint/member-ordering": [ 101 | "error", 102 | { 103 | "default": [ 104 | "public-static-field", 105 | "public-static-method", 106 | "protected-static-field", 107 | "protected-static-method", 108 | "private-static-field", 109 | "private-static-method", 110 | "field", 111 | "constructor", 112 | "method" 113 | ] 114 | } 115 | ] 116 | }, 117 | "overrides": [ 118 | { 119 | "files": [ 120 | ".projenrc.ts" 121 | ], 122 | "rules": { 123 | "@typescript-eslint/no-require-imports": "off", 124 | "import/no-extraneous-dependencies": "off" 125 | } 126 | } 127 | ] 128 | } 129 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | /.eslintrc.json linguist-generated 5 | /.gitattributes linguist-generated 6 | /.github/pull_request_template.md linguist-generated 7 | /.github/workflows/auto-approve.yml 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 | /.github/workflows/upgrade-main.yml linguist-generated 12 | /.gitignore linguist-generated 13 | /.mergify.yml linguist-generated 14 | /.npmignore linguist-generated 15 | /.prettierignore linguist-generated 16 | /.prettierrc.json linguist-generated 17 | /.projen/** linguist-generated 18 | /.projen/deps.json linguist-generated 19 | /.projen/files.json linguist-generated 20 | /.projen/tasks.json linguist-generated 21 | /API.md linguist-generated 22 | /LICENSE linguist-generated 23 | /package.json linguist-generated 24 | /tsconfig.dev.json linguist-generated 25 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'nikovirtala') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.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.4.0 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.4.0 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 | -------------------------------------------------------------------------------- /.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 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v5.4.0 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /.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.4.0 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 | runs-on: ubuntu-latest 67 | permissions: 68 | contents: write 69 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 70 | steps: 71 | - uses: actions/setup-node@v4 72 | with: 73 | node-version: lts/* 74 | - name: Download build artifacts 75 | uses: actions/download-artifact@v4 76 | with: 77 | name: build-artifact 78 | path: dist 79 | - name: Restore build artifact permissions 80 | run: cd dist && setfacl --restore=permissions-backup.acl 81 | continue-on-error: true 82 | - name: Release 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 86 | release_npm: 87 | name: Publish to npm 88 | needs: release 89 | runs-on: ubuntu-latest 90 | permissions: 91 | id-token: write 92 | contents: read 93 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 94 | steps: 95 | - uses: actions/setup-node@v4 96 | with: 97 | node-version: lts/* 98 | - name: Download build artifacts 99 | uses: actions/download-artifact@v4 100 | with: 101 | name: build-artifact 102 | path: dist 103 | - name: Restore build artifact permissions 104 | run: cd dist && setfacl --restore=permissions-backup.acl 105 | continue-on-error: true 106 | - name: Checkout 107 | uses: actions/checkout@v4 108 | with: 109 | path: .repo 110 | - name: Install Dependencies 111 | run: cd .repo && yarn install --check-files --frozen-lockfile 112 | - name: Extract build artifact 113 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 114 | - name: Move build artifact out of the way 115 | run: mv dist dist.old 116 | - name: Create js artifact 117 | run: cd .repo && npx projen package:js 118 | - name: Collect js artifact 119 | run: mv .repo/dist dist 120 | - name: Release 121 | env: 122 | NPM_DIST_TAG: latest 123 | NPM_REGISTRY: registry.npmjs.org 124 | NPM_CONFIG_PROVENANCE: "true" 125 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 126 | run: npx -p publib@latest publib-npm 127 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-main" workflow* 80 | branch: github-actions/upgrade-main 81 | title: "chore(deps): upgrade dependencies" 82 | labels: auto-approve,auto-merge 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.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 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | !/.github/workflows/build.yml 35 | /dist/changelog.md 36 | /dist/version.txt 37 | !/.github/workflows/release.yml 38 | !/.mergify.yml 39 | !/.github/workflows/upgrade-main.yml 40 | !/.github/pull_request_template.md 41 | !/.prettierignore 42 | !/.prettierrc.json 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 | -------------------------------------------------------------------------------- /.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 | merge_method: squash 12 | commit_message_template: |- 13 | {{ title }} (#{{ number }}) 14 | 15 | {{ body }} 16 | pull_request_rules: 17 | - name: Automatic merge on approval and successful build 18 | actions: 19 | delete_head_branch: {} 20 | queue: 21 | name: default 22 | conditions: 23 | - "#approved-reviews-by>=1" 24 | - -label~=(do-not-merge) 25 | - status-success=build 26 | - status-success=package-js 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | permissions-backup.acl 4 | /dist/changelog.md 5 | /dist/version.txt 6 | /.mergify.yml 7 | /.prettierignore 8 | /.prettierrc.json 9 | /test/ 10 | /tsconfig.dev.json 11 | /src/ 12 | !/lib/ 13 | !/lib/**/*.js 14 | !/lib/**/*.d.ts 15 | dist 16 | /tsconfig.json 17 | /.github/ 18 | /.vscode/ 19 | /.idea/ 20 | /.projenrc.js 21 | tsconfig.tsbuildinfo 22 | /.eslintrc.json 23 | !.jsii 24 | /.gitattributes 25 | /.projenrc.ts 26 | /projenrc 27 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [] 3 | } 4 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/node", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@typescript-eslint/eslint-plugin", 9 | "version": "^8", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@typescript-eslint/parser", 14 | "version": "^8", 15 | "type": "build" 16 | }, 17 | { 18 | "name": "commit-and-tag-version", 19 | "version": "^12", 20 | "type": "build" 21 | }, 22 | { 23 | "name": "eslint-config-prettier", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "eslint-import-resolver-typescript", 28 | "type": "build" 29 | }, 30 | { 31 | "name": "eslint-plugin-import", 32 | "type": "build" 33 | }, 34 | { 35 | "name": "eslint-plugin-prettier", 36 | "type": "build" 37 | }, 38 | { 39 | "name": "eslint", 40 | "version": "^9", 41 | "type": "build" 42 | }, 43 | { 44 | "name": "jsii-diff", 45 | "type": "build" 46 | }, 47 | { 48 | "name": "jsii-docgen", 49 | "version": "^10.5.0", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "jsii-pacmak", 54 | "type": "build" 55 | }, 56 | { 57 | "name": "jsii-rosetta", 58 | "version": "~5.5.0", 59 | "type": "build" 60 | }, 61 | { 62 | "name": "jsii", 63 | "version": "~5.5.0", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "prettier", 68 | "type": "build" 69 | }, 70 | { 71 | "name": "projen", 72 | "type": "build" 73 | }, 74 | { 75 | "name": "ts-node", 76 | "type": "build" 77 | }, 78 | { 79 | "name": "typescript", 80 | "type": "build" 81 | }, 82 | { 83 | "name": "aws-cdk-lib", 84 | "version": "^2.80.0", 85 | "type": "peer" 86 | }, 87 | { 88 | "name": "constructs", 89 | "version": "^10.0.5", 90 | "type": "peer" 91 | } 92 | ], 93 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 94 | } 95 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/release.yml", 10 | ".github/workflows/upgrade-main.yml", 11 | ".gitignore", 12 | ".mergify.yml", 13 | ".prettierignore", 14 | ".prettierrc.json", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "LICENSE", 19 | "tsconfig.dev.json" 20 | ], 21 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 22 | } 23 | -------------------------------------------------------------------------------- /.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 | "env": { 129 | "ESLINT_USE_FLAT_CONFIG": "false" 130 | }, 131 | "steps": [ 132 | { 133 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 134 | "receiveArgs": true 135 | } 136 | ] 137 | }, 138 | "install": { 139 | "name": "install", 140 | "description": "Install project dependencies and update lockfile (non-frozen)", 141 | "steps": [ 142 | { 143 | "exec": "yarn install --check-files" 144 | } 145 | ] 146 | }, 147 | "install:ci": { 148 | "name": "install:ci", 149 | "description": "Install project dependencies using frozen lockfile", 150 | "steps": [ 151 | { 152 | "exec": "yarn install --check-files --frozen-lockfile" 153 | } 154 | ] 155 | }, 156 | "package": { 157 | "name": "package", 158 | "description": "Creates the distribution package", 159 | "steps": [ 160 | { 161 | "spawn": "package:js", 162 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 163 | }, 164 | { 165 | "spawn": "package-all", 166 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 167 | } 168 | ] 169 | }, 170 | "package-all": { 171 | "name": "package-all", 172 | "description": "Packages artifacts for all target languages", 173 | "steps": [ 174 | { 175 | "spawn": "package:js" 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 | "post-compile": { 189 | "name": "post-compile", 190 | "description": "Runs after successful compilation", 191 | "steps": [ 192 | { 193 | "spawn": "docgen" 194 | } 195 | ] 196 | }, 197 | "post-upgrade": { 198 | "name": "post-upgrade", 199 | "description": "Runs after upgrading dependencies" 200 | }, 201 | "pre-compile": { 202 | "name": "pre-compile", 203 | "description": "Prepare the project for compilation" 204 | }, 205 | "release": { 206 | "name": "release", 207 | "description": "Prepare a release from \"main\" branch", 208 | "env": { 209 | "RELEASE": "true", 210 | "MAJOR": "1" 211 | }, 212 | "steps": [ 213 | { 214 | "exec": "rm -fr dist" 215 | }, 216 | { 217 | "spawn": "bump" 218 | }, 219 | { 220 | "spawn": "build" 221 | }, 222 | { 223 | "spawn": "unbump" 224 | }, 225 | { 226 | "exec": "git diff --ignore-space-at-eol --exit-code" 227 | } 228 | ] 229 | }, 230 | "test": { 231 | "name": "test", 232 | "description": "Run tests", 233 | "steps": [ 234 | { 235 | "spawn": "eslint" 236 | } 237 | ] 238 | }, 239 | "unbump": { 240 | "name": "unbump", 241 | "description": "Restores version to 0.0.0", 242 | "env": { 243 | "OUTFILE": "package.json", 244 | "CHANGELOG": "dist/changelog.md", 245 | "BUMPFILE": "dist/version.txt", 246 | "RELEASETAG": "dist/releasetag.txt", 247 | "RELEASE_TAG_PREFIX": "", 248 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 249 | }, 250 | "steps": [ 251 | { 252 | "builtin": "release/reset-version" 253 | } 254 | ] 255 | }, 256 | "upgrade": { 257 | "name": "upgrade", 258 | "description": "upgrade dependencies", 259 | "env": { 260 | "CI": "0" 261 | }, 262 | "steps": [ 263 | { 264 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@types/node,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-prettier,jsii-diff,jsii-pacmak,prettier,projen,ts-node,typescript" 265 | }, 266 | { 267 | "exec": "yarn install --check-files" 268 | }, 269 | { 270 | "exec": "yarn upgrade @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser commit-and-tag-version eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii prettier projen ts-node typescript aws-cdk-lib constructs" 271 | }, 272 | { 273 | "exec": "npx projen" 274 | }, 275 | { 276 | "spawn": "post-upgrade" 277 | } 278 | ] 279 | }, 280 | "watch": { 281 | "name": "watch", 282 | "description": "Watch & compile in the background", 283 | "steps": [ 284 | { 285 | "exec": "jsii -w --silence-warnings=reserved-word" 286 | } 287 | ] 288 | } 289 | }, 290 | "env": { 291 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 292 | }, 293 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 294 | } 295 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { awscdk } from "projen"; 2 | import { NpmAccess } from "projen/lib/javascript"; 3 | 4 | const project = new awscdk.AwsCdkConstructLibrary({ 5 | name: "@cloudgardener/cdk-aws-fargate-github-actions-runner", 6 | description: 7 | "CDK construct library to deploy GitHub Actions self-hosted runner to AWS Fargate.", 8 | repositoryUrl: 9 | "https://github.com/cloudgardener/cdk-aws-fargate-github-actions-runner.git", 10 | license: "MIT", 11 | majorVersion: 1, 12 | author: "Niko Virtala", 13 | authorAddress: "niko@cloudgardener.dev", 14 | cdkVersion: "2.80.0", 15 | dependabot: false, 16 | defaultReleaseBranch: "main", 17 | jsiiVersion: "~5.5.0", 18 | depsUpgradeOptions: { 19 | workflowOptions: { 20 | labels: ["auto-approve", "auto-merge"], 21 | }, 22 | }, 23 | autoApproveOptions: { 24 | secret: "GITHUB_TOKEN", 25 | allowedUsernames: ["nikovirtala"], 26 | }, 27 | githubOptions: { 28 | mergify: true, 29 | }, 30 | npmAccess: NpmAccess.PUBLIC, 31 | catalog: { 32 | announce: true, 33 | twitter: "nikovirtala", 34 | }, 35 | keywords: [ 36 | "aws", 37 | "cdk", 38 | "aws-cdk", 39 | "fargate", 40 | "github", 41 | "github-actions", 42 | "runner", 43 | ], 44 | prettier: true, 45 | jest: false, 46 | projenrcTs: true, 47 | }); 48 | 49 | project.synth(); 50 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### GithubActionsRunner 6 | 7 | - *Implements:* aws-cdk-lib.aws_ec2.IConnectable 8 | 9 | #### Initializers 10 | 11 | ```typescript 12 | import { GithubActionsRunner } from '@cloudgardener/cdk-aws-fargate-github-actions-runner' 13 | 14 | new GithubActionsRunner(scope: Construct, id: string, props: GithubActionsRunnerProps) 15 | ``` 16 | 17 | | **Name** | **Type** | **Description** | 18 | | --- | --- | --- | 19 | | scope | constructs.Construct | *No description.* | 20 | | id | string | *No description.* | 21 | | props | GithubActionsRunnerProps | *No description.* | 22 | 23 | --- 24 | 25 | ##### `scope`Required 26 | 27 | - *Type:* constructs.Construct 28 | 29 | --- 30 | 31 | ##### `id`Required 32 | 33 | - *Type:* string 34 | 35 | --- 36 | 37 | ##### `props`Required 38 | 39 | - *Type:* GithubActionsRunnerProps 40 | 41 | --- 42 | 43 | #### Methods 44 | 45 | | **Name** | **Description** | 46 | | --- | --- | 47 | | toString | Returns a string representation of this construct. | 48 | 49 | --- 50 | 51 | ##### `toString` 52 | 53 | ```typescript 54 | public toString(): string 55 | ``` 56 | 57 | Returns a string representation of this construct. 58 | 59 | #### Static Functions 60 | 61 | | **Name** | **Description** | 62 | | --- | --- | 63 | | isConstruct | Checks if `x` is a construct. | 64 | 65 | --- 66 | 67 | ##### ~~`isConstruct`~~ 68 | 69 | ```typescript 70 | import { GithubActionsRunner } from '@cloudgardener/cdk-aws-fargate-github-actions-runner' 71 | 72 | GithubActionsRunner.isConstruct(x: any) 73 | ``` 74 | 75 | Checks if `x` is a construct. 76 | 77 | ###### `x`Required 78 | 79 | - *Type:* any 80 | 81 | Any object. 82 | 83 | --- 84 | 85 | #### Properties 86 | 87 | | **Name** | **Type** | **Description** | 88 | | --- | --- | --- | 89 | | node | constructs.Node | The tree node. | 90 | | connections | aws-cdk-lib.aws_ec2.Connections | Makes runner "connectable". | 91 | | role | aws-cdk-lib.aws_iam.IRole | The IAM role associated with this runner. | 92 | 93 | --- 94 | 95 | ##### `node`Required 96 | 97 | ```typescript 98 | public readonly node: Node; 99 | ``` 100 | 101 | - *Type:* constructs.Node 102 | 103 | The tree node. 104 | 105 | --- 106 | 107 | ##### `connections`Required 108 | 109 | ```typescript 110 | public readonly connections: Connections; 111 | ``` 112 | 113 | - *Type:* aws-cdk-lib.aws_ec2.Connections 114 | 115 | Makes runner "connectable". 116 | 117 | --- 118 | 119 | ##### `role`Required 120 | 121 | ```typescript 122 | public readonly role: IRole; 123 | ``` 124 | 125 | - *Type:* aws-cdk-lib.aws_iam.IRole 126 | 127 | The IAM role associated with this runner. 128 | 129 | --- 130 | 131 | 132 | ## Structs 133 | 134 | ### GithubActionsRunnerProps 135 | 136 | Properties of the GithubActionsRunner. 137 | 138 | #### Initializer 139 | 140 | ```typescript 141 | import { GithubActionsRunnerProps } from '@cloudgardener/cdk-aws-fargate-github-actions-runner' 142 | 143 | const githubActionsRunnerProps: GithubActionsRunnerProps = { ... } 144 | ``` 145 | 146 | #### Properties 147 | 148 | | **Name** | **Type** | **Description** | 149 | | --- | --- | --- | 150 | | githubToken | aws-cdk-lib.aws_ecs.Secret | Secret containing a GitHub Personal Access Token to be used for the runner authentication. | 151 | | runnerContext | string | The GitHub repository to use for the runner. | 152 | | cluster | aws-cdk-lib.aws_ecs.ICluster | The ECS cluster to run the task in. | 153 | | logRetentionDays | number | How long to store the GitHub runner logs. | 154 | | useSpotCapacity | boolean | Use Fargate SPOT capacity. | 155 | | vpc | aws-cdk-lib.aws_ec2.IVpc | The VPC to run the cluster in. | 156 | 157 | --- 158 | 159 | ##### `githubToken`Required 160 | 161 | ```typescript 162 | public readonly githubToken: Secret; 163 | ``` 164 | 165 | - *Type:* aws-cdk-lib.aws_ecs.Secret 166 | 167 | Secret containing a GitHub Personal Access Token to be used for the runner authentication. 168 | 169 | --- 170 | 171 | ##### `runnerContext`Required 172 | 173 | ```typescript 174 | public readonly runnerContext: string; 175 | ``` 176 | 177 | - *Type:* string 178 | 179 | The GitHub repository to use for the runner. 180 | 181 | --- 182 | 183 | *Example* 184 | 185 | ```typescript 186 | "https://github.com/cloudgardener/runner-demo" 187 | ``` 188 | 189 | 190 | ##### `cluster`Optional 191 | 192 | ```typescript 193 | public readonly cluster: ICluster; 194 | ``` 195 | 196 | - *Type:* aws-cdk-lib.aws_ecs.ICluster 197 | - *Default:* a new cluster is created 198 | 199 | The ECS cluster to run the task in. 200 | 201 | --- 202 | 203 | ##### `logRetentionDays`Optional 204 | 205 | ```typescript 206 | public readonly logRetentionDays: number; 207 | ``` 208 | 209 | - *Type:* number 210 | - *Default:* 7 days 211 | 212 | How long to store the GitHub runner logs. 213 | 214 | --- 215 | 216 | ##### `useSpotCapacity`Optional 217 | 218 | ```typescript 219 | public readonly useSpotCapacity: boolean; 220 | ``` 221 | 222 | - *Type:* boolean 223 | - *Default:* true 224 | 225 | Use Fargate SPOT capacity. 226 | 227 | --- 228 | 229 | ##### `vpc`Optional 230 | 231 | ```typescript 232 | public readonly vpc: IVpc; 233 | ``` 234 | 235 | - *Type:* aws-cdk-lib.aws_ec2.IVpc 236 | - *Default:* a new VPC is created 237 | 238 | The VPC to run the cluster in. 239 | 240 | --- 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Niko Virtala 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 | # cdk-aws-fargate-github-actions-runner 2 | 3 | [![View on Construct Hub](https://constructs.dev/badge?package=%40cloudgardener%2Fcdk-aws-fargate-github-actions-runner)](https://constructs.dev/packages/@cloudgardener/cdk-aws-fargate-github-actions-runner) 4 | 5 | CDK construct library to deploy [GitHub Actions self-hosted runner](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) to AWS Fargate. 6 | 7 | This is continuation to [`cdk-github-actions-runner`](https://github.com/nikovirtala/cdk-github-actions-runner) proof-of-concept. 8 | 9 | ## Example 10 | 11 | ```ts 12 | import { App, Stack, aws_ecs as ecs, aws_ssm as ssm } from "aws-cdk-lib"; 13 | import { GithubActionsRunner } from "@cloudgardener/cdk-aws-fargate-github-actions-runner"; 14 | 15 | const app = new App(); 16 | const stack = new Stack(app, "stack"); 17 | 18 | // Get GitHub token e.g. from SSM Parameter Store 19 | const token = ecs.Secret.fromSsmParameter( 20 | ssm.StringParameter.fromSecureStringParameterAttributes( 21 | stack, 22 | "GitHubAccessToken", 23 | { 24 | parameterName: "GITHUB_ACCESS_TOKEN", 25 | version: 0, 26 | } 27 | ) 28 | ); 29 | 30 | // Assign runner to repository 31 | const context = "https://github.com/cloudgardener/runner-demo"; 32 | 33 | // Runners can be also assigned to organization 34 | // const context = "https://github.com/cloudgardener"; 35 | 36 | // Deploy the runner 37 | new GithubActionsRunner(stack, "runner", { 38 | githubToken: token, 39 | runnerContext: context, 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /image/.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudgardener/cdk-aws-fargate-github-actions-runner/a94b3b11d8ed73a648a4d50b784c3445862a23a6/image/.dockerignore -------------------------------------------------------------------------------- /image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 ubuntu:rolling 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | curl \ 5 | jq \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | RUN addgroup runner && \ 9 | adduser \ 10 | --system \ 11 | --disabled-password \ 12 | --home /home/runner \ 13 | --ingroup runner \ 14 | runner 15 | 16 | WORKDIR /home/runner 17 | 18 | RUN GITHUB_RUNNER_VERSION=${GITHUB_RUNNER_VERSION:-$(curl -s https://api.github.com/repos/actions/runner/releases/latest | jq -r .tag_name | sed 's/v//g')} \ 19 | && curl -sSLO https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-x64-${GITHUB_RUNNER_VERSION}.tar.gz \ 20 | && tar -zxvf actions-runner-linux-x64-${GITHUB_RUNNER_VERSION}.tar.gz \ 21 | && rm -f actions-runner-linux-x64-${GITHUB_RUNNER_VERSION}.tar.gz \ 22 | && ./bin/installdependencies.sh \ 23 | && chown -R runner:runner /home/runner 24 | 25 | COPY entrypoint.sh entrypoint.sh 26 | 27 | USER runner 28 | 29 | ENTRYPOINT ["./entrypoint.sh"] -------------------------------------------------------------------------------- /image/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RUNNER_NAME=${RUNNER_NAME:-default} 4 | RUNNER_WORKDIR=${RUNNER_WORKDIR:-_work} 5 | 6 | if [[ -z "${GITHUB_ACCESS_TOKEN}" || -z "${GITHUB_ACTIONS_RUNNER_CONTEXT}" ]]; then 7 | echo 'One of the mandatory parameters is missing. Quit!' 8 | exit 1 9 | else 10 | AUTH_HEADER="Authorization: token ${GITHUB_ACCESS_TOKEN}" 11 | USERNAME=$(cut -d/ -f4 <<< ${GITHUB_ACTIONS_RUNNER_CONTEXT}) 12 | REPOSITORY=$(cut -d/ -f5 <<< ${GITHUB_ACTIONS_RUNNER_CONTEXT}) 13 | 14 | if [[ -z "${REPOSITORY}" ]]; then 15 | TOKEN_REGISTRATION_URL="https://api.github.com/orgs/${USERNAME}/actions/runners/registration-token" 16 | else 17 | TOKEN_REGISTRATION_URL="https://api.github.com/repos/${USERNAME}/${REPOSITORY}/actions/runners/registration-token" 18 | fi 19 | 20 | RUNNER_TOKEN="$(curl -XPOST -fsSL \ 21 | -H "Accept: application/vnd.github.v3+json" \ 22 | -H "${AUTH_HEADER}" \ 23 | "${TOKEN_REGISTRATION_URL}" \ 24 | | jq -r '.token')" 25 | fi 26 | 27 | ./config.sh --url "${GITHUB_ACTIONS_RUNNER_CONTEXT}" --token "${RUNNER_TOKEN}" --name "${RUNNER_NAME}" --work "${RUNNER_WORKDIR}" 28 | ./run.sh 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudgardener/cdk-aws-fargate-github-actions-runner", 3 | "description": "CDK construct library to deploy GitHub Actions self-hosted runner to AWS Fargate.", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cloudgardener/cdk-aws-fargate-github-actions-runner.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 | "post-compile": "npx projen post-compile", 22 | "post-upgrade": "npx projen post-upgrade", 23 | "pre-compile": "npx projen pre-compile", 24 | "release": "npx projen release", 25 | "test": "npx projen test", 26 | "unbump": "npx projen unbump", 27 | "upgrade": "npx projen upgrade", 28 | "watch": "npx projen watch", 29 | "projen": "npx projen" 30 | }, 31 | "author": { 32 | "name": "Niko Virtala", 33 | "email": "niko@cloudgardener.dev", 34 | "organization": false 35 | }, 36 | "devDependencies": { 37 | "@types/node": "^16 <= 16.18.78", 38 | "@typescript-eslint/eslint-plugin": "^8", 39 | "@typescript-eslint/parser": "^8", 40 | "aws-cdk-lib": "2.80.0", 41 | "commit-and-tag-version": "^12", 42 | "constructs": "10.0.5", 43 | "eslint": "^9", 44 | "eslint-config-prettier": "^8.10.0", 45 | "eslint-import-resolver-typescript": "^2.7.1", 46 | "eslint-plugin-import": "^2.31.0", 47 | "eslint-plugin-prettier": "^4.2.1", 48 | "jsii": "~5.5.0", 49 | "jsii-diff": "^1.112.0", 50 | "jsii-docgen": "^10.5.0", 51 | "jsii-pacmak": "^1.112.0", 52 | "jsii-rosetta": "~5.5.0", 53 | "prettier": "^2.8.8", 54 | "projen": "^0.92.10", 55 | "ts-node": "^10.9.2", 56 | "typescript": "^4.9.5" 57 | }, 58 | "peerDependencies": { 59 | "aws-cdk-lib": "^2.80.0", 60 | "constructs": "^10.0.5" 61 | }, 62 | "keywords": [ 63 | "aws", 64 | "aws-cdk", 65 | "cdk", 66 | "fargate", 67 | "github", 68 | "github-actions", 69 | "runner" 70 | ], 71 | "main": "lib/index.js", 72 | "license": "MIT", 73 | "publishConfig": { 74 | "access": "public" 75 | }, 76 | "version": "0.0.0", 77 | "types": "lib/index.d.ts", 78 | "stability": "stable", 79 | "jsii": { 80 | "outdir": "dist", 81 | "targets": {}, 82 | "tsc": { 83 | "outDir": "lib", 84 | "rootDir": "src" 85 | } 86 | }, 87 | "awscdkio": { 88 | "twitter": "nikovirtala", 89 | "announce": true 90 | }, 91 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 92 | } 93 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { aws_ec2 as ec2, aws_ecs as ecs, aws_iam as iam } from "aws-cdk-lib"; 3 | import { Construct } from "constructs"; 4 | 5 | /** 6 | * Properties of the GithubActionsRunner 7 | */ 8 | export interface GithubActionsRunnerProps { 9 | /** 10 | * The VPC to run the cluster in 11 | * @default - a new VPC is created 12 | */ 13 | readonly vpc?: ec2.IVpc; 14 | /** 15 | * The ECS cluster to run the task in 16 | * @default - a new cluster is created 17 | */ 18 | readonly cluster?: ecs.ICluster; 19 | /** 20 | * Secret containing 21 | * a GitHub Personal Access Token to be used 22 | * for the runner authentication 23 | */ 24 | readonly githubToken: ecs.Secret; 25 | /** 26 | * The GitHub repository to use for the runner 27 | * @example "https://github.com/cloudgardener/runner-demo" 28 | */ 29 | readonly runnerContext: string; 30 | /** 31 | * How long to store the GitHub runner logs 32 | * @default - 7 days 33 | */ 34 | readonly logRetentionDays?: number; 35 | /** 36 | * Use Fargate SPOT capacity 37 | * @default - true 38 | */ 39 | readonly useSpotCapacity?: boolean; 40 | } 41 | 42 | export class GithubActionsRunner extends Construct implements ec2.IConnectable { 43 | /** 44 | * Makes runner "connectable" 45 | */ 46 | readonly connections: ec2.Connections; 47 | 48 | /** 49 | * The IAM role associated with this runner 50 | */ 51 | readonly role: iam.IRole; 52 | 53 | constructor(scope: Construct, id: string, props: GithubActionsRunnerProps) { 54 | super(scope, id); 55 | 56 | const vpc = props.vpc 57 | ? props.vpc 58 | : new ec2.Vpc(this, "Vpc", { 59 | maxAzs: 1, // Default is all AZs in region 60 | natGateways: 0, 61 | gatewayEndpoints: { 62 | s3: { 63 | service: ec2.GatewayVpcEndpointAwsService.S3, 64 | }, 65 | dynamodb: { 66 | service: ec2.GatewayVpcEndpointAwsService.DYNAMODB, 67 | }, 68 | }, 69 | }); 70 | 71 | const cluster = props.cluster 72 | ? props.cluster 73 | : new ecs.Cluster(this, "Cluster", { 74 | vpc: vpc, 75 | enableFargateCapacityProviders: true, 76 | }); 77 | 78 | const logRetentionDays = props.logRetentionDays ?? 7; 79 | 80 | const useSpotCapacity = props.useSpotCapacity ?? true; 81 | 82 | const taskDefinition = new ecs.FargateTaskDefinition( 83 | this, 84 | "TaskDefinition" 85 | ); 86 | 87 | taskDefinition.addContainer("Container", { 88 | image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, "../image")), 89 | logging: ecs.LogDrivers.awsLogs({ 90 | streamPrefix: "GitHubActionsRunner", 91 | logRetention: logRetentionDays, 92 | }), 93 | environment: { 94 | GITHUB_ACTIONS_RUNNER_CONTEXT: props.runnerContext, 95 | }, 96 | secrets: { 97 | GITHUB_ACCESS_TOKEN: props.githubToken, 98 | }, 99 | }); 100 | 101 | const service = new ecs.FargateService(this, "Service", { 102 | cluster: cluster, 103 | taskDefinition: taskDefinition, 104 | platformVersion: ecs.FargatePlatformVersion.LATEST, 105 | desiredCount: 1, 106 | circuitBreaker: { rollback: true }, 107 | vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, 108 | assignPublicIp: true, 109 | capacityProviderStrategies: [ 110 | { 111 | capacityProvider: "FARGATE_SPOT", 112 | weight: useSpotCapacity ? 1 : 0, 113 | }, 114 | { 115 | capacityProvider: "FARGATE", 116 | weight: useSpotCapacity ? 0 : 1, 117 | }, 118 | ], 119 | }); 120 | 121 | this.connections = service.connections; 122 | 123 | this.role = taskDefinition.taskRole; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test-reports/junit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/hello.test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudgardener/cdk-aws-fargate-github-actions-runner/a94b3b11d8ed73a648a4d50b784c3445862a23a6/test/hello.test.ts -------------------------------------------------------------------------------- /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 | "es2020" 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": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------