├── .nvmrc ├── index.ts ├── .gitattributes ├── .husky └── pre-commit ├── .gitignore ├── tsconfig.node.json ├── __mocks__ ├── @actions │ └── core.ts └── aws-sdk │ └── clients │ └── lambda.ts ├── tsconfig.eslint.json ├── renovate.json ├── .github └── workflows │ ├── renovate.yml │ ├── lint.yml │ ├── build_project.yml │ ├── unit-tests.yml │ ├── release.yml │ └── rebuild_dist.yml ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── action.yml ├── src └── index.ts ├── package.json ├── __tests__ └── index.spec.ts └── readme.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.9.0 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { main } from './src'; 2 | 3 | main(); 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* linguist-generated linguist-vendored=true 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode/ 3 | # ignore codecoverage output 4 | coverage/ 5 | dist/__mocks__/ -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /__mocks__/@actions/core.ts: -------------------------------------------------------------------------------- 1 | export const setFailed = jest.fn(); 2 | export const getInput = jest.fn(); 3 | export const setOutput = jest.fn(); 4 | export const setSecret = jest.fn(); 5 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/*.ts", 5 | "index.ts", 6 | "__mocks__/**/*.ts", 7 | "__tests__/**/*.ts", 8 | "jest.config.js", 9 | "!dist/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "schedule:earlyMondays"], 3 | "packageRules": [ 4 | { 5 | "matchPackagePatterns": ["^eslint"], 6 | "groupName": "eslint packages" 7 | } 8 | ], 9 | "npm": { 10 | "stabilityDays": 3 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - renovate/* 5 | 6 | name: rebuild dist/ + commit for renovate 7 | 8 | jobs: 9 | getNodeVersion: 10 | uses: gagoar/ts-node-template/.github/workflows/get_node_version.yml@main 11 | 12 | runCommitPush: 13 | needs: getNodeVersion 14 | uses: gagoar/ts-node-template/.github/workflows/run_hook_commit_and_push.yml@main 15 | secrets: 16 | token: ${{ secrets.UPDATE_PR_TOKEN }} 17 | with: 18 | node_version: ${{ needs.getNodeVersion.outputs.node_version }} 19 | hook_to_run: npm i && npm run build 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | getNodeVersion: 11 | uses: gagoar/ts-node-template/.github/workflows/get_node_version.yml@main 12 | 13 | lint: 14 | runs-on: ubuntu-latest 15 | needs: getNodeVersion 16 | steps: 17 | - uses: actions/checkout@main 18 | - name: Setting node version to ${{ needs.getNodeVersion.outputs.node_version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: '${{ needs.getNodeVersion.outputs.node_version }}' 22 | - run: npm install 23 | - run: npm run lint 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | process.env.TZ = 'UTC'; 2 | 3 | module.exports = { 4 | globals: { 5 | 'ts-jest': { 6 | disableSourceMapSupport: true, 7 | }, 8 | }, 9 | verbose: true, 10 | transform: { 11 | '^.+\\.tsx?$': 'ts-jest', 12 | }, 13 | testEnvironmentOptions: { 14 | url: 'http://localhost', 15 | }, 16 | preset: 'ts-jest', 17 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 18 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], 19 | testMatch: ['**/*.spec.ts'], 20 | collectCoverage: true, 21 | collectCoverageFrom: ['src/*.ts', '!./index.ts'], 22 | coverageDirectory: './coverage/', 23 | }; 24 | -------------------------------------------------------------------------------- /.github/workflows/build_project.yml: -------------------------------------------------------------------------------- 1 | name: Build Action 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | getNodeVersion: 11 | uses: gagoar/ts-node-template/.github/workflows/get_node_version.yml@main 12 | 13 | build: 14 | runs-on: ubuntu-latest 15 | needs: getNodeVersion 16 | steps: 17 | - uses: actions/checkout@main 18 | - name: Setting node version to ${{ needs.getNodeVersion.outputs.node_version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: '${{ needs.getNodeVersion.outputs.node_version }}' 22 | - name: Install dependencies 23 | run: npm install 24 | - name: Build 25 | run: npm run build 26 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | getNodeVersion: 11 | uses: gagoar/ts-node-template/.github/workflows/get_node_version.yml@main 12 | 13 | unit-test: 14 | runs-on: ubuntu-latest 15 | needs: getNodeVersion 16 | steps: 17 | - uses: actions/checkout@main 18 | - name: Setting node version to ${{ needs.getNodeVersion.outputs.node_version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: '${{ needs.getNodeVersion.outputs.node_version }}' 22 | - name: Install dependencies 23 | run: npm install 24 | - name: jest 25 | run: npm run test --coverage 26 | - name: Send coverage to codecov 27 | uses: codecov/codecov-action@v3 28 | with: 29 | token: ${{ secrets.CODECOV_TOKEN }} 30 | flags: unittests 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "__test__", "__mocks__", "src/index.ts"], 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "es6", 6 | "lib": ["dom", "esnext"], 7 | "emitDecoratorMetadata": true, 8 | "importHelpers": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "resolveJsonModule": true, 24 | "experimentalDecorators": true, 25 | "baseUrl": "./", 26 | "paths": { 27 | "*": ["node_modules/*"] 28 | }, 29 | "jsx": "react", 30 | "esModuleInterop": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create Release 8 | 9 | jobs: 10 | build: 11 | name: Create Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | - name: Get release notes 17 | id: release_notes 18 | run: echo "notes=$(git log -1 --pretty=format:"%b")" >> $GITHUB_OUTPUT 19 | - name: Create Release 20 | id: create_release 21 | uses: actions/create-release@v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 24 | with: 25 | tag_name: ${{ github.ref }} 26 | release_name: Release ${{ github.ref }} 27 | body: | 28 | Changes in this Release 29 | ${{ steps.release_notes.outputs.notes }} 30 | draft: false 31 | prerelease: false 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__mocks__/aws-sdk/clients/lambda.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 3 | import { Lambda as RealLambda } from 'aws-sdk'; 4 | enum Methods { 5 | invoke = 'invoke', 6 | } 7 | 8 | type LAMBDAMOCKS = Partial any>>; 9 | 10 | let lambdaMocks = {} as LAMBDAMOCKS; 11 | 12 | type Dispatch = (response: Promise, callback?: Function) => { promise: () => Promise } | any 13 | 14 | const dispatch: Dispatch = (response, callback?: Function) => { 15 | if (!callback) { 16 | return { 17 | promise: () => response, 18 | } 19 | } 20 | return response.then((result) => callback(null, result)).catch((err: Error) => callback(err, null)); 21 | } 22 | 23 | const baseHandler = async (method: Methods, props: T): Promise> => { 24 | if (method in lambdaMocks) { 25 | const handler = lambdaMocks[method]; 26 | return handler && handler(props); 27 | } else { 28 | return {} 29 | } 30 | }; 31 | 32 | export const constructorMock = jest.fn(); 33 | export default class Lambda { 34 | static __setResponseForMethods(mock: LAMBDAMOCKS) { 35 | lambdaMocks = { ...lambdaMocks, ...mock }; 36 | } 37 | 38 | static __showMockedPayloads() { 39 | return { ...lambdaMocks }; 40 | } 41 | 42 | constructor(props: RealLambda.Types.ClientConfiguration) { 43 | constructorMock(props); 44 | } 45 | invoke(props: RealLambda.InvokeAsyncRequest, callback?: Function) { 46 | const promise = baseHandler(Methods.invoke, props); 47 | 48 | return dispatch(promise, callback); 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /.github/workflows/rebuild_dist.yml: -------------------------------------------------------------------------------- 1 | name: rebuild dist/ if needed 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | ref: ${{github.head_ref}} 13 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 14 | fetch-depth: 0 # otherwise, you will failed to push refs to dest repo 15 | 16 | - uses: actions/setup-node@v3.5.1 17 | with: 18 | cache: 'npm' 19 | node-version-file: '.nvmrc' 20 | 21 | - run: npm install 22 | - name: build dist/ 23 | run: npm run build 24 | - run: | 25 | STATUS=$(git diff --quiet && echo clean || echo modified) 26 | echo "status=$(echo $STATUS)" >> $GITHUB_OUTPUT 27 | id: gitStatus 28 | - run: | 29 | echo ${{ steps.gitStatus.outputs.status }} 30 | echo ${{ contains(steps.gitStatus.outputs.status, 'modified') }} 31 | - name: Commit dist/ files 32 | if: contains(steps.gitStatus.outputs.status, 'modified') 33 | run: | 34 | set -x 35 | git config --local user.email "action@github.com" 36 | git config --local user.name "GitHub Action" 37 | git commit -m "rebuild dist/" -a 38 | - id: auth 39 | if: contains(steps.gitStatus.outputs.status, 'modified') 40 | uses: jnwng/github-app-installation-token-action@v2 41 | with: 42 | appId: ${{ secrets.COMMIT_BUILD_TO_PR_APP_ID }} 43 | installationId: ${{ secrets.COMMIT_BUILD_TO_PR_INSTALLATION_ID }} 44 | privateKey: ${{ secrets.COMMIT_BUILD_TO_PR_PRIVATE_KEY }} 45 | - name: Push changes 46 | if: contains(steps.gitStatus.outputs.status, 'modified') 47 | uses: ad-m/github-push-action@master 48 | with: 49 | github_token: ${{ steps.auth.outputs.token }} 50 | branch: ${{github.head_ref}} 51 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "invoke-aws-lambda" 2 | description: "Invoke AWS Lambda function" 3 | branding: 4 | icon: arrow-up-right 5 | color: orange 6 | inputs: 7 | AWS_ACCESS_KEY_ID: 8 | description: "AWS_ACCESS_KEY_ID" 9 | required: true 10 | AWS_SECRET_ACCESS_KEY: 11 | description: "AWS_SECRET_ACCESS_KEY" 12 | required: true 13 | AWS_SESSION_TOKEN: 14 | description: "AWS_SESSION_TOKEN" 15 | required: false 16 | REGION: 17 | description: "region where the lambda has been created (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html)" 18 | required: false 19 | default: us-east-1 20 | FunctionName: 21 | description: "The name of the Lambda function, version, or alias" 22 | required: true 23 | InvocationType: 24 | description: "Choose from the following options: RequestResponse, Event, DryRun" 25 | required: false 26 | default: RequestResponse 27 | LogType: 28 | description: "Choose from the following options: None, Tail" 29 | required: false 30 | default: None 31 | ClientContext: 32 | description: "Up to 3583 bytes of base64-encoded data about the invoking client to pass to the function in the context object." 33 | required: false 34 | Payload: 35 | description: "The JSON that you want to provide to your Lambda function as input. (Buffer, Typed Array, Blob, String)" 36 | required: false 37 | Qualifier: 38 | description: "Specify a version or alias to invoke a published version of the function." 39 | required: false 40 | HTTP_TIMEOUT: 41 | description: Sets the socket to timeout after timeout milliseconds of inactivity on the socket. Defaults to two minutes (120000) 42 | required: false 43 | MAX_RETRIES: 44 | description: Returns the maximum amount of retries to perform for a service request. By default this value is calculated by the specific service object that the request is being made to. 45 | required: false 46 | SUCCEED_ON_FUNCTION_FAILURE: 47 | description: Set to true if this action should succeed when the Lambda function executed returns an error 48 | required: false 49 | outputs: 50 | response: # id of output 51 | description: "response from lambda invocation" 52 | runs: 53 | using: "node20" 54 | main: "dist/index.js" 55 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import AWS from 'aws-sdk/global'; 2 | import Lambda from 'aws-sdk/clients/lambda'; 3 | import { getInput, setOutput, setFailed, setSecret } from '@actions/core'; 4 | 5 | const apiVersion = '2015-03-31'; 6 | 7 | export enum ExtraOptions { 8 | HTTP_TIMEOUT = 'HTTP_TIMEOUT', 9 | MAX_RETRIES = 'MAX_RETRIES', 10 | SUCCEED_ON_FUNCTION_FAILURE = 'SUCCEED_ON_FUNCTION_FAILURE', 11 | } 12 | 13 | export enum Credentials { 14 | AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID', 15 | AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY', 16 | AWS_SESSION_TOKEN = 'AWS_SESSION_TOKEN', 17 | } 18 | 19 | export enum Props { 20 | FunctionName = 'FunctionName', 21 | InvocationType = 'InvocationType', 22 | LogType = 'LogType', 23 | ClientContext = 'ClientContext', 24 | Payload = 'Payload', 25 | Qualifier = 'Qualifier', 26 | } 27 | 28 | const setAWSCredentials = () => { 29 | const accessKeyId = getInput(Credentials.AWS_ACCESS_KEY_ID); 30 | setSecret(accessKeyId); 31 | 32 | const secretAccessKey = getInput(Credentials.AWS_SECRET_ACCESS_KEY); 33 | setSecret(secretAccessKey); 34 | 35 | const sessionToken = getInput(Credentials.AWS_SESSION_TOKEN); 36 | // Make sure we only mask if specified 37 | if (sessionToken) { 38 | setSecret(sessionToken); 39 | } 40 | 41 | AWS.config.credentials = { 42 | accessKeyId, 43 | secretAccessKey, 44 | sessionToken, 45 | }; 46 | }; 47 | 48 | const getParams = () => { 49 | return Object.keys(Props).reduce((memo, prop) => { 50 | const value = getInput(prop); 51 | return value ? { ...memo, [prop]: value } : memo; 52 | }, {} as Lambda.InvocationRequest); 53 | }; 54 | 55 | const setAWSConfigOptions = () => { 56 | const httpTimeout = getInput(ExtraOptions.HTTP_TIMEOUT); 57 | 58 | if (httpTimeout) { 59 | AWS.config.httpOptions = { timeout: parseInt(httpTimeout, 10) }; 60 | } 61 | 62 | const maxRetries = getInput(ExtraOptions.MAX_RETRIES); 63 | 64 | if (maxRetries) { 65 | AWS.config.maxRetries = parseInt(maxRetries, 10); 66 | } 67 | }; 68 | 69 | export const main = async () => { 70 | try { 71 | setAWSCredentials(); 72 | 73 | setAWSConfigOptions(); 74 | 75 | const params = getParams(); 76 | 77 | const lambda = new Lambda({ apiVersion, region: getInput('REGION') }); 78 | 79 | const response = await lambda.invoke(params).promise(); 80 | 81 | setOutput('response', response); 82 | 83 | const succeedOnFailure = getInput(ExtraOptions.SUCCEED_ON_FUNCTION_FAILURE).toLowerCase() === 'true'; 84 | if ('FunctionError' in response && !succeedOnFailure) { 85 | throw new Error('Lambda invocation failed! See outputs.response for more information.'); 86 | } 87 | } catch (error) { 88 | setFailed(error instanceof Error ? error.message : JSON.stringify(error)); 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "invoke-aws-lambda", 3 | "repository": "git@github.com:gagoar/invoke-aws-lambda.git", 4 | "license": "MIT", 5 | "version": "3.3.2", 6 | "description": "Invoke AWS Lambda", 7 | "main": "index.ts", 8 | "husky": { 9 | "hooks": { 10 | "pre-commit": "lint-staged" 11 | } 12 | }, 13 | "scripts": { 14 | "test": "jest", 15 | "build": "esbuild --bundle ./index.ts --platform=node --target=node12 --main-fields=main --outdir=dist && pretty-quick dist/index.js", 16 | "lint": "eslint src/* --ext .ts", 17 | "prepare": "husky install" 18 | }, 19 | "engines": { 20 | "node": ">12.0.0" 21 | }, 22 | "keywords": [ 23 | "cli", 24 | "AWS", 25 | "github", 26 | "action" 27 | ], 28 | "lint-staged": { 29 | "*": [ 30 | "pretty-quick --staged" 31 | ], 32 | "*.ts": [ 33 | "eslint --fix", 34 | "bash -c \"npm run build\"", 35 | "git add ." 36 | ] 37 | }, 38 | "prettier": { 39 | "singleQuote": true, 40 | "semi": true, 41 | "printWidth": 120 42 | }, 43 | "eslintConfig": { 44 | "extends": [ 45 | "plugin:@typescript-eslint/recommended", 46 | "plugin:prettier/recommended" 47 | ], 48 | "parser": "@typescript-eslint/parser", 49 | "rules": { 50 | "quotes": [ 51 | 2, 52 | "single", 53 | "avoid-escape" 54 | ], 55 | "no-debugger": "error", 56 | "no-process-env": "off", 57 | "import/prefer-default-export": "off", 58 | "@typescript-eslint/explicit-function-return-type": "off", 59 | "@typescript-eslint/no-unused-vars": [ 60 | "error", 61 | { 62 | "vars": "all", 63 | "args": "after-used", 64 | "ignoreRestSiblings": true 65 | } 66 | ], 67 | "new-cap": [ 68 | "error", 69 | { 70 | "capIsNewExceptions": [ 71 | "Injectable", 72 | "Inject" 73 | ] 74 | } 75 | ], 76 | "prefer-destructuring": [ 77 | "error", 78 | { 79 | "VariableDeclarator": { 80 | "array": false, 81 | "object": true 82 | }, 83 | "AssignmentExpression": { 84 | "array": true, 85 | "object": false 86 | } 87 | }, 88 | { 89 | "enforceForRenamedProperties": false 90 | } 91 | ] 92 | } 93 | }, 94 | "author": "Gago ", 95 | "dependencies": { 96 | "@actions/core": "1.10.0", 97 | "aws-sdk": "^2.681.0" 98 | }, 99 | "devDependencies": { 100 | "@types/jest": "27.5.2", 101 | "@types/node": "16.11.57", 102 | "@typescript-eslint/eslint-plugin": "5.23.0", 103 | "@typescript-eslint/parser": "5.23.0", 104 | "ajv-keywords": "5.1.0", 105 | "bufferutil": "4.0.6", 106 | "esbuild": "0.14.44", 107 | "eslint": "8.23.0", 108 | "eslint-config-prettier": "8.5.0", 109 | "eslint-plugin-prettier": "4.2.1", 110 | "husky": "7.0.4", 111 | "jest": "28.1.1", 112 | "lint-staged": "10.5.3", 113 | "prettier": "2.6.2", 114 | "prettier-eslint": "13.0.0", 115 | "prettier-eslint-cli": "^8.0.1", 116 | "pretty-quick": "3.1.3", 117 | "ts-jest": "28.0.5", 118 | "ts-node": "10.7.0", 119 | "tslib": "2.4.0", 120 | "typescript": "5.5.4", 121 | "utf-8-validate": "5.0.9", 122 | "ws": "7.5.10" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import AWS from 'aws-sdk/global'; 2 | import { main, Props, Credentials, ExtraOptions } from '../src'; 3 | import { setFailed, getInput, setOutput, setSecret } from '../__mocks__/@actions/core'; 4 | import Lambda, { constructorMock } from '../__mocks__/aws-sdk/clients/lambda'; 5 | 6 | describe('invoke-aws-lambda', () => { 7 | const mockedInput = { 8 | [Props.FunctionName]: 'SomeFunction', 9 | [Props.LogType]: 'None', 10 | [Props.Payload]: '{"input": {value: "1"}', 11 | [Props.Qualifier]: 'production', 12 | [ExtraOptions.HTTP_TIMEOUT]: '220000', 13 | [ExtraOptions.MAX_RETRIES]: '3', 14 | [ExtraOptions.SUCCEED_ON_FUNCTION_FAILURE]: 'false', 15 | [Credentials.AWS_ACCESS_KEY_ID]: 'someAccessKey', 16 | [Credentials.AWS_SECRET_ACCESS_KEY]: 'someSecretKey', 17 | REGION: 'us-west-2', 18 | }; 19 | 20 | beforeAll(() => { 21 | getInput.mockImplementation( 22 | (key: Partial) => { 23 | return mockedInput[key]; 24 | } 25 | ); 26 | }); 27 | 28 | afterEach(() => { 29 | getInput.mockClear(); 30 | setFailed.mockClear(); 31 | setOutput.mockClear(); 32 | setSecret.mockClear(); 33 | }); 34 | 35 | it('runs when provided the correct input', async () => { 36 | const handler = jest.fn(() => ({ response: 'ok' })); 37 | 38 | Lambda.__setResponseForMethods({ invoke: handler }); 39 | 40 | await main(); 41 | expect(getInput).toHaveBeenCalledTimes(13); 42 | expect(setFailed).not.toHaveBeenCalled(); 43 | expect(setSecret).toHaveBeenCalledTimes(2); 44 | expect(AWS.config.httpOptions).toMatchInlineSnapshot(` 45 | Object { 46 | "timeout": 220000, 47 | } 48 | `); 49 | expect(constructorMock.mock.calls).toMatchInlineSnapshot(` 50 | Array [ 51 | Array [ 52 | Object { 53 | "apiVersion": "2015-03-31", 54 | "region": "us-west-2", 55 | }, 56 | ], 57 | ] 58 | `); 59 | expect(handler.mock.calls).toMatchInlineSnapshot(` 60 | Array [ 61 | Array [ 62 | Object { 63 | "FunctionName": "SomeFunction", 64 | "LogType": "None", 65 | "Payload": "{\\"input\\": {value: \\"1\\"}", 66 | "Qualifier": "production", 67 | }, 68 | ], 69 | ] 70 | `); 71 | expect(setOutput.mock.calls).toMatchInlineSnapshot(` 72 | Array [ 73 | Array [ 74 | "response", 75 | Object { 76 | "response": "ok", 77 | }, 78 | ], 79 | ] 80 | `); 81 | }); 82 | 83 | it('fails when lambda invocation throws an error', async () => { 84 | const handler = jest.fn(() => { 85 | throw new Error('something went horribly wrong'); 86 | }); 87 | 88 | Lambda.__setResponseForMethods({ invoke: handler }); 89 | 90 | await main(); 91 | 92 | expect(getInput).toHaveBeenCalledTimes(12); 93 | expect(AWS.config.httpOptions).toMatchInlineSnapshot(` 94 | Object { 95 | "timeout": 220000, 96 | } 97 | `); 98 | expect(setFailed).toHaveBeenCalled(); 99 | expect(setOutput).not.toHaveBeenCalled(); 100 | expect(setSecret).toHaveBeenCalledTimes(2); 101 | }); 102 | 103 | describe('when the function returns an error', () => { 104 | beforeEach(() => { 105 | const handler = jest.fn().mockReturnValue({ 106 | FunctionError: 'Unhandled', 107 | }); 108 | 109 | Lambda.__setResponseForMethods({ invoke: handler }); 110 | }); 111 | 112 | it('should fail the action when SUCCEED_ON_FUNCTION_FAILURE is undefined', async () => { 113 | const overriddenMockedInput = { 114 | ...mockedInput, 115 | [ExtraOptions.SUCCEED_ON_FUNCTION_FAILURE]: undefined, 116 | }; 117 | 118 | getInput.mockImplementation( 119 | (key: Partial) => { 120 | return overriddenMockedInput[key]; 121 | } 122 | ); 123 | 124 | await main(); 125 | 126 | expect(setOutput).toHaveBeenCalled(); 127 | expect(setFailed).toHaveBeenCalled(); 128 | expect(setSecret).toHaveBeenCalledTimes(2); 129 | }); 130 | 131 | it('should fail the action when SUCCEED_ON_FUNCTION_FAILURE is false', async () => { 132 | const overriddenMockedInput = { 133 | ...mockedInput, 134 | [ExtraOptions.SUCCEED_ON_FUNCTION_FAILURE]: 'false', 135 | }; 136 | 137 | getInput.mockImplementation( 138 | (key: Partial) => { 139 | return overriddenMockedInput[key]; 140 | } 141 | ); 142 | 143 | await main(); 144 | 145 | expect(setOutput).toHaveBeenCalled(); 146 | expect(setFailed).toHaveBeenCalled(); 147 | expect(setSecret).toHaveBeenCalledTimes(2); 148 | }); 149 | 150 | it('should succeed the action when SUCCEED_ON_FUNCTION_FAILURE is true', async () => { 151 | const overriddenMockedInput = { 152 | ...mockedInput, 153 | [ExtraOptions.SUCCEED_ON_FUNCTION_FAILURE]: 'true', 154 | }; 155 | 156 | getInput.mockImplementation( 157 | (key: Partial) => { 158 | return overriddenMockedInput[key]; 159 | } 160 | ); 161 | 162 | await main(); 163 | 164 | expect(setOutput).toHaveBeenCalled(); 165 | expect(setFailed).not.toHaveBeenCalled(); 166 | expect(setSecret).toHaveBeenCalledTimes(2); 167 | }); 168 | 169 | it("should call setSecret on AWS_SESSION_TOKEN when it's provided", async () => { 170 | const overriddenMockedInput = { 171 | ...mockedInput, 172 | [Credentials.AWS_SESSION_TOKEN]: 'someSessionToken', 173 | }; 174 | 175 | getInput.mockImplementation( 176 | (key: Partial) => { 177 | return overriddenMockedInput[key]; 178 | } 179 | ); 180 | 181 | const handler = jest.fn(() => ({ response: 'ok' })); 182 | 183 | Lambda.__setResponseForMethods({ invoke: handler }); 184 | 185 | await main(); 186 | 187 | expect(getInput).toHaveBeenCalledTimes(13); 188 | expect(setFailed).not.toHaveBeenCalled(); 189 | expect(setSecret).toHaveBeenCalledTimes(3); 190 | }); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | GitHub Marketplace 4 | 5 | 6 | Workflow 7 | 8 | 9 | codecov 10 | 11 | 12 | MIT license 13 | 14 |

15 | 16 | # Invoke AWS Lambda 17 | 18 | This action allows you to synchronously invoke a Lambda function and get the response (if desired). 19 | 20 | ## Table of contents 21 | 22 | - [Invoke AWS Lambda](#invoke-aws-lambda) 23 | - [Table of contents](#table-of-contents) 24 | - [Input parameters](#input-parameters) 25 | - [Credentials](#credentials) 26 | - [Invocation](#invocation) 27 | - [Output](#output) 28 | - [Examples](#examples) 29 | - [Basic example](#basic-example) 30 | - [Using output](#using-output) 31 | - [Specifying alias/version](#specifying-aliasversion) 32 | - [Handling logs](#handling-logs) 33 | - [OpenID Credentials](#openid-credentials) 34 | 35 |
36 | 37 | ## Input parameters 38 | 39 | ### Credentials 40 | 41 | | Key | Type | Required | Description | 42 | | ----------------------- | :------: | :------: | ----------------------------------------------------------------------- | 43 | | `AWS_ACCESS_KEY_ID` | `string` | Yes | Access Key ID | 44 | | `AWS_SECRET_ACCESS_KEY` | `string` | Yes | Secret Access Key | 45 | | `AWS_SESSION_TOKEN` | `string` | No | Session Token | 46 | | `REGION` | `string` | No | Default `us-east-1`. Region where the Lambda function has been created. | 47 | 48 | [AWS Security Credentials](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) reference 49 | [AWS Temporary Credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) reference 50 | 51 | ### Invocation 52 | 53 | | Key | Type | Required | Description | 54 | | ----------------------------- | :------------------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 55 | | `FunctionName` | `string` | Yes | Name of the Lambda function to be invoked. | 56 | | `InvocationType` | `RequestResponse\|`
`Event\|`
`DryRun` | No | Default `RequestResponse`. See the [AWS Javascript SDK docs](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property) for more info. | 57 | | `LogType` | `Tail\|None` | No | Default `None`. Set to `Tail` to include the execution log in the response. | 58 | | `Payload` | `string` | No | JSON that you want to provide to your Lambda function as input. | 59 | | `Qualifier` | `string` | No | Version or alias of the function to be invoked. | 60 | | `ClientContext` | `string` | No | Base64-encoded data about the invoking client to pass to the function. | 61 | | `HTTP_TIMEOUT` | `number` | No | Sets the socket to timeout after timeout milliseconds of inactivity on the socket. Defaults to two minutes (120000). See the [AWS Javascript SDK docs](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html) | 62 | | `MAX_RETRIES` | `number` | No | Returns the maximum amount of retries to perform for a service request. By default this value is calculated by the specific service object that the request is being made to. [AWS Javascript SDK docs](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#maxRetries-property) | 63 | | `SUCCEED_ON_FUNCTION_FAILURE` | `boolean` | No | Set to true if this action should succeed when the Lambda function executed returns an error | 64 | 65 | For more details on the parameters accepted by `Lambda.invoke()`, see the [AWS Javascript SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property) docs 66 | 67 |
68 | 69 | ## Output 70 | 71 | This step will store the JSON response from the Lambda function invocation in `outputs.response`, with the following properties: 72 | 73 | | Property | Type | Description | 74 | | ----------------- | --------- | ----------------------------------------------------------------------------------- | 75 | | `StatusCode` | `integer` | HTTP status code - 200 if successful | 76 | | `ExecutedVersion` | `string` | Version or alias of function that was executed | 77 | | `Payload` | `string` | JSON returned by the function invocation, or error object | 78 | | `LogResult` | `string` | Base64-encoded last 4KB of execution log, if `LogType` was set to `Tail` | 79 | | `FunctionError` | `string` | If present, indicates that an error has occured, with more information in `Payload` | 80 | 81 | Note that you will have to parse the output using the `fromJSON` [function](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#functions) before accessing individual properties. 82 | See the [Using Output](#Using-Output) example for more details. 83 | 84 |
85 | 86 | ## Examples 87 | 88 | ### Basic example 89 | 90 | This step invokes a Lambda function without regard for the invocation output: 91 | 92 | ```yaml 93 | - name: Invoke foobarFunction Lambda 94 | uses: gagoar/invoke-aws-lambda@master 95 | with: 96 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 97 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 98 | FunctionName: foobarFunction 99 | Payload: '{ "myParameter": false }' 100 | ``` 101 | 102 | ### Using output 103 | 104 | These steps process the response payload by using step outputs: 105 | 106 | ```yaml 107 | - name: Invoke foobarFunction Lambda 108 | id: foobar 109 | uses: gagoar/invoke-aws-lambda@master 110 | with: 111 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 112 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 113 | FunctionName: foobarFunction 114 | Payload: '{ "myParameter": false }' 115 | - name: Store response payload to file 116 | run: echo '${{ fromJSON(steps.foobar.outputs.response).Payload }}' > invocation-response.json 117 | ``` 118 | 119 | Notice the addition of the `id` field to the invocation step. 120 | For more information for Github Actions outputs, see their [reference](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjobs_idoutputs). 121 | 122 | ### Specifying alias/version 123 | 124 | This step invokes a Lambda function with the `someAlias` alias: 125 | 126 | ```yaml 127 | - name: Invoke foobarFunction Lambda 128 | uses: gagoar/invoke-aws-lambda@master 129 | with: 130 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 131 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 132 | FunctionName: foobarFunction 133 | Payload: '{ "myParameter": false }' 134 | Qualifier: someAlias 135 | ``` 136 | 137 | Similarly, if we wanted to invoke version 53 in particular, we would use: 138 | 139 | ```yaml 140 | ... 141 | with: 142 | ... 143 | Qualifier: 53 144 | ``` 145 | 146 | ### Handling logs 147 | 148 | These steps process logs returned from the invocation: 149 | 150 | ```yaml 151 | - name: Invoke foobarFunction Lambda 152 | id: foobar 153 | uses: gagoar/invoke-aws-lambda@master 154 | with: 155 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 156 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 157 | FunctionName: foobarFunction 158 | LogType: Tail 159 | Payload: '{ "myParameter": false }' 160 | - name: Store tail logs to file 161 | run: echo "${{ fromJSON(steps.foobar.outputs.response).LogResult }}" > invocation-logs.json 162 | ``` 163 | 164 | ### OpenID Credentials 165 | 166 | You can also use OpenID Credentials via `aws-actions/configure-aws-credentials`: 167 | ```yaml 168 | - name: Assume AWS role 169 | uses: aws-actions/configure-aws-credentials@v1 170 | with: 171 | role-to-assume: the_arn_of_the_role_you_want_to_assume 172 | aws-region: eu-west-1 173 | 174 | - name: Invoke the Lambda 175 | uses: gagoar/invoke-aws-lambda@master 176 | with: 177 | AWS_ACCESS_KEY_ID: ${{env.AWS_ACCESS_KEY_ID}} 178 | AWS_SECRET_ACCESS_KEY: ${{env.AWS_SECRET_ACCESS_KEY}} 179 | AWS_SESSION_TOKEN: ${{env.AWS_SESSION_TOKEN}} 180 | REGION: ${{env.AWS_REGION}} 181 | FunctionName: foobarFunction 182 | Payload: '{ "myParameter": false }' 183 | ``` 184 | --------------------------------------------------------------------------------