├── node-red-on-lambda-with-api-gw-overview.png ├── scripts ├── utils.js ├── deconfigure.js ├── local.js └── configure.js ├── .github ├── FUNDING.yml └── workflows │ └── trivy.yml ├── .gitignore ├── api-gateway-event.json ├── package.json ├── simple-proxy-api.yaml ├── README.md ├── lambda.js ├── packaged-sam.yaml ├── cloudformation.yaml ├── settings.js └── LICENSE /node-red-on-lambda-with-api-gw-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakazuki/aws-serverless-node-red/HEAD/node-red-on-lambda-with-api-gw-overview.png -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | 6 | module.exports.modifyFiles = function modifyFiles(files, replacements) { 7 | files.forEach((file) => { 8 | let fileContentModified = fs.readFileSync(file, 'utf8') 9 | 10 | replacements.forEach((v) => { 11 | fileContentModified = fileContentModified.replace(v.regexp, v.replacement) 12 | }) 13 | 14 | fs.writeFileSync(file, fileContentModified, 'utf8') 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [sakazuki] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /scripts/deconfigure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const modifyFiles = require('./utils').modifyFiles 5 | const packageJson = require('../package.json') 6 | const config = packageJson.config 7 | 8 | modifyFiles(['./simple-proxy-api.yaml', './package.json', './cloudformation.yaml', './settings.js' ], [{ 9 | regexp: new RegExp(config.accountId, 'g'), 10 | replacement: 'YOUR_ACCOUNT_ID' 11 | }, { 12 | regexp: new RegExp(config.region, 'g'), 13 | replacement: 'YOUR_AWS_REGION' 14 | }, { 15 | regexp: new RegExp(config.s3BucketName, 'g'), 16 | replacement: 'YOUR_UNIQUE_BUCKET_NAME' 17 | }, { 18 | regexp: new RegExp(config.functionName, 'g'), 19 | replacement: 'YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME' 20 | }, { 21 | regexp: new RegExp(config.stackName, 'g'), 22 | replacement: 'YOUR_STACK_NAME' 23 | }]) 24 | -------------------------------------------------------------------------------- /scripts/local.js: -------------------------------------------------------------------------------- 1 | const lambdaFunction = require('../lambda.js') 2 | const apiGatewayEvent = require('../api-gateway-event.json') 3 | 4 | const server = lambdaFunction.handler(apiGatewayEvent, { 5 | succeed: v => { 6 | console.log(v) 7 | process.exit(0) 8 | } 9 | }, (e, v) => { 10 | console.error(v) 11 | process.exit(1) 12 | }) 13 | 14 | process.stdin.resume() 15 | 16 | function exitHandler(options, err) { 17 | if (options.cleanup && server && server.close ) { 18 | server.close() 19 | } 20 | 21 | if (err) console.error(err.stack) 22 | if (options.exit) process.exit() 23 | } 24 | 25 | process.on('exit', exitHandler.bind(null, { cleanup: true })) 26 | process.on('SIGINT', exitHandler.bind(null, { exit: true })) // ctrl+c event 27 | process.on('SIGTSTP', exitHandler.bind(null, { exit: true })) // ctrl+v event 28 | process.on('uncaughtException', exitHandler.bind(null, { exit: true })) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/trivy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: trivy 7 | 8 | on: 9 | workflow_dispatch: 10 | push: 11 | branches: [ "master" ] 12 | pull_request: 13 | # The branches below must be a subset of the branches above 14 | branches: [ "master" ] 15 | schedule: 16 | - cron: '28 0 * * 4' 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | build: 23 | permissions: 24 | contents: read # for actions/checkout to fetch code 25 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 26 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 27 | name: Build 28 | runs-on: "ubuntu-20.04" 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | 33 | - name: Run Trivy vulnerability scanner 34 | uses: aquasecurity/trivy-action@7b7aa264d83dc58691451798b4d117d53d21edfe 35 | with: 36 | scan-type: 'fs' 37 | scan-ref: '.' 38 | format: 'sarif' 39 | output: 'trivy-results.sarif' 40 | severity: 'CRITICAL,HIGH' 41 | 42 | - name: Upload Trivy scan results to GitHub Security tab 43 | uses: github/codeql-action/upload-sarif@v2 44 | with: 45 | sarif_file: 'trivy-results.sarif' 46 | -------------------------------------------------------------------------------- /api-gateway-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "GET", 3 | "//body": "{\"name\": \"Sam\"}", 4 | "path": "/users", 5 | "resource": "/{proxy+}", 6 | "queryStringParameters": {}, 7 | "pathParameters": { 8 | "proxy": "users" 9 | }, 10 | "headers": { 11 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 12 | "Accept-Encoding": "gzip, deflate, sdch, br", 13 | "Accept-Language": "en-US,en;q=0.8", 14 | "CloudFront-Forwarded-Proto": "https", 15 | "CloudFront-Is-Desktop-Viewer": "true", 16 | "CloudFront-Is-Mobile-Viewer": "false", 17 | "CloudFront-Is-SmartTV-Viewer": "false", 18 | "CloudFront-Is-Tablet-Viewer": "false", 19 | "CloudFront-Viewer-Country": "US", 20 | "Content-Type": "application/json", 21 | "Host": "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com", 22 | "Upgrade-Insecure-Requests": "1", 23 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", 24 | "Via": "1.1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", 25 | "X-Amz-Cf-Id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxx_xxxx==", 26 | "X-Forwarded-For": "11.111.111.111, 11.111.111.111", 27 | "X-Forwarded-Port": "111", 28 | "X-Forwarded-Proto": "http" 29 | }, 30 | "requestContext": { 31 | "accountId": "111111111111", 32 | "resourceId": "xxxxxx", 33 | "stage": "prod", 34 | "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 35 | "identity": { 36 | "cognitoIdentityPoolId": "", 37 | "accountId": "", 38 | "cognitoIdentityId": "", 39 | "caller": "", 40 | "apiKey": "", 41 | "sourceIp": "11.111.111.111", 42 | "cognitoAuthenticationType": "", 43 | "cognitoAuthenticationProvider": "", 44 | "userArn": "", 45 | "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", 46 | "user": "" 47 | }, 48 | "resourcePath": "/{proxy+}", 49 | "httpMethod": "GET", 50 | "apiId": "xxxxxxxxxx" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scripts/configure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | const exec = require('child_process').execSync 6 | const modifyFiles = require('./utils').modifyFiles 7 | 8 | let minimistHasBeenInstalled = false 9 | 10 | if (!fs.existsSync('./node_modules/minimist')) { 11 | exec('npm install minimist --silent') 12 | minimistHasBeenInstalled = true 13 | } 14 | 15 | const args = require('minimist')(process.argv.slice(2), { 16 | string: [ 17 | 'account-id', 18 | 'bucket-name', 19 | 'function-name', 20 | 'region', 21 | 'stack-name' 22 | ], 23 | default: { 24 | region: 'us-east-1', 25 | 'function-name': 'slsNodeREDFunction', 26 | 'stack-name': 'slsNodeREDStack' 27 | } 28 | }) 29 | 30 | if (minimistHasBeenInstalled) { 31 | exec('npm uninstall minimist --silent') 32 | } 33 | 34 | const accountId = args['account-id'] 35 | const bucketName = args['bucket-name'] 36 | const functionNameOriginal = args['function-name'] 37 | const functionName = functionNameOriginal.replace(/[^A-Za-z0-9]/g, ''); 38 | const region = args.region 39 | const stackName = args['stack-name'] 40 | const availableRegions = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'eu-west-1', 'eu-west-2', 'eu-central-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2'] 41 | 42 | if (!accountId || accountId.length !== 12) { 43 | console.error('You must supply a 12 digit account id as --account-id=""') 44 | return 45 | } 46 | 47 | if (!bucketName) { 48 | console.error('You must supply a bucket name as --bucket-name=""') 49 | return 50 | } 51 | 52 | if (availableRegions.indexOf(region) === -1) { 53 | console.error(`Amazon API Gateway and Lambda are not available in the ${region} region. Available regions: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-northeast-1, ap-northeast-2, ap-southeast-1, ap-southeast-2`) 54 | return 55 | } 56 | 57 | modifyFiles(['./simple-proxy-api.yaml', './package.json', './cloudformation.yaml', './settings.js'], [{ 58 | regexp: /YOUR_ACCOUNT_ID/g, 59 | replacement: accountId 60 | }, { 61 | regexp: /YOUR_AWS_REGION/g, 62 | replacement: region 63 | }, { 64 | regexp: /YOUR_UNIQUE_BUCKET_NAME/g, 65 | replacement: bucketName 66 | }, { 67 | regexp: /YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME/g, 68 | replacement: functionName 69 | }, { 70 | regexp: /YOUR_LAMBDA_FUNCTION_NAME/g, 71 | replacement: functionNameOriginal 72 | }, { 73 | regexp: /YOUR_STACK_NAME/g, 74 | replacement: stackName 75 | }]) 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-serverless-node-red", 3 | "version": "2.1.1", 4 | "description": "Node-RED on AWS Lambda using Amazon API Gateway and S3.", 5 | "main": "lambda.js", 6 | "config": { 7 | "s3BucketName": "YOUR_UNIQUE_BUCKET_NAME", 8 | "region": "YOUR_AWS_REGION", 9 | "cloudFormationStackName": "YOUR_STACK_NAME", 10 | "functionName": "YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME", 11 | "accountId": "YOUR_ACCOUNT_ID" 12 | }, 13 | "scripts": { 14 | "start": "node app.local.js", 15 | "config": "node ./scripts/configure.js", 16 | "deconfig": "node ./scripts/deconfigure.js", 17 | "local": "node scripts/local", 18 | "invoke-lambda": "aws lambda invoke --function-name $npm_package_config_functionName --region $npm_package_config_region --payload file://api-gateway-event.json lambda-invoke-response.json && cat lambda-invoke-response.json", 19 | "create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 20 | "delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 21 | "package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region", 22 | "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region", 23 | "package-deploy": "npm run package && npm run deploy", 24 | "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region", 25 | "setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || npm run create-bucket) && npm run package-deploy", 26 | "win-config": "npm run config", 27 | "win-deconfig": "npm run deconfig", 28 | "win-local": "npm run local", 29 | "win-invoke-lambda": "aws lambda invoke --function-name %npm_package_config_functionName% --region %npm_package_config_region% --payload file://api-gateway-event.json lambda-invoke-response.json && cat lambda-invoke-response.json", 30 | "win-create-bucket": "aws s3 mb s3://%npm_package_config_s3BucketName% --region %npm_package_config_region%", 31 | "win-delete-bucket": "aws s3 rb s3://%npm_package_config_s3BucketName% --region %npm_package_config_region%", 32 | "win-package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket %npm_package_config_s3BucketName% --output-template packaged-sam.yaml --region %npm_package_config_region%", 33 | "win-deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name %npm_package_config_cloudFormationStackName% --capabilities CAPABILITY_IAM --region %npm_package_config_region%", 34 | "win-package-deploy": "npm run win-package && npm run win-deploy", 35 | "win-delete-stack": "aws cloudformation delete-stack --stack-name %npm_package_config_cloudFormationStackName% --region %npm_package_config_region%", 36 | "win-setup": "npm install && (aws s3api get-bucket-location --bucket %npm_package_config_s3BucketName% --region %npm_package_config_region% || npm run win-create-bucket) && npm run win-package-deploy" 37 | }, 38 | "license": "Apache-2.0", 39 | "dependencies": { 40 | "aws-serverless-express": "^3.3.6", 41 | "body-parser": "^1.19.0", 42 | "compression": "^1.7.4", 43 | "cors": "^2.8.5", 44 | "express": "^4.17.1", 45 | "node-red": "^1.0.3", 46 | "node-red-contrib-storage-s3": "git://github.com/sakazuki/node-red-contrib-storage-s3.git" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /simple-proxy-api.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: 2.0 3 | info: 4 | title: AwsServerlessExpressApi 5 | basePath: /prod 6 | schemes: 7 | - https 8 | paths: 9 | /: 10 | x-amazon-apigateway-any-method: 11 | produces: 12 | - application/json 13 | responses: 14 | 200: 15 | description: 200 response 16 | schema: 17 | $ref: "#/definitions/Empty" 18 | x-amazon-apigateway-integration: 19 | responses: 20 | default: 21 | statusCode: 200 22 | uri: arn:aws:apigateway:YOUR_AWS_REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:YOUR_AWS_REGION:YOUR_ACCOUNT_ID:function:${stageVariables.ServerlessExpressLambdaFunctionName}/invocations 23 | passthroughBehavior: when_no_match 24 | httpMethod: POST 25 | type: aws_proxy 26 | options: 27 | consumes: 28 | - application/json 29 | produces: 30 | - application/json 31 | responses: 32 | 200: 33 | description: 200 response 34 | schema: 35 | $ref: "#/definitions/Empty" 36 | headers: 37 | Access-Control-Allow-Origin: 38 | type: string 39 | Access-Control-Allow-Methods: 40 | type: string 41 | Access-Control-Allow-Headers: 42 | type: string 43 | x-amazon-apigateway-integration: 44 | responses: 45 | default: 46 | statusCode: 200 47 | responseParameters: 48 | method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" 49 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" 50 | method.response.header.Access-Control-Allow-Origin: "'https://example.com'" 51 | passthroughBehavior: when_no_match 52 | requestTemplates: 53 | application/json: "{\"statusCode\": 200}" 54 | type: mock 55 | /{proxy+}: 56 | x-amazon-apigateway-any-method: 57 | produces: 58 | - application/json 59 | parameters: 60 | - name: proxy 61 | in: path 62 | required: true 63 | type: string 64 | responses: {} 65 | x-amazon-apigateway-integration: 66 | uri: arn:aws:apigateway:YOUR_AWS_REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:YOUR_AWS_REGION:YOUR_ACCOUNT_ID:function:${stageVariables.ServerlessExpressLambdaFunctionName}/invocations 67 | httpMethod: POST 68 | type: aws_proxy 69 | options: 70 | consumes: 71 | - application/json 72 | produces: 73 | - application/json 74 | responses: 75 | 200: 76 | description: 200 response 77 | schema: 78 | $ref: "#/definitions/Empty" 79 | headers: 80 | Access-Control-Allow-Origin: 81 | type: string 82 | Access-Control-Allow-Methods: 83 | type: string 84 | Access-Control-Allow-Headers: 85 | type: string 86 | x-amazon-apigateway-integration: 87 | responses: 88 | default: 89 | statusCode: 200 90 | responseParameters: 91 | method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" 92 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" 93 | method.response.header.Access-Control-Allow-Origin: "'https://example.com'" 94 | passthroughBehavior: when_no_match 95 | requestTemplates: 96 | application/json: "{\"statusCode\": 200}" 97 | type: mock 98 | x-amazon-apigateway-binary-media-types: 99 | - '*/*' 100 | definitions: 101 | Empty: 102 | type: object 103 | title: Empty Schema 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node-RED on AWS Lambda using Amazon API Gateway and S3 2 | 3 | ## Overview 4 | 5 | ![overview](node-red-on-lambda-with-api-gw-overview.png) 6 | 7 | ## Quick Start 8 | 9 | ### Steps for running the Node-RED on lambda using API Gateway and S3 10 | This guide assumes you have already [set up an AWS account](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/AboutAWSAccounts.html) and have the latest version of the [AWS CLI](https://aws.amazon.com/cli/) installed. 11 | 12 | 1. From your preferred project directory: 13 | 14 | ``` 15 | git clone https://github.com/sakazuki/aws-serverless-node-red.git 16 | cd aws-serverless-node-red 17 | ``` 18 | 19 | 2. Run the following command to configure with your variables. 20 | ``` 21 | AWS_ACCOUNT_ID=123456789012 22 | S3_BUCKET=node-red123 23 | AWS_REGION=us-west-2 24 | AWS_FUNCNAME=funcname123 25 | AWS_STACK_NAME=Node-RED 26 | npm run config -- --account-id="${AWS_ACCOUNT_ID}" \ 27 | --bucket-name="${S3_BUCKET}" \ 28 | --region="${AWS_REGION}" \ 29 | --function-name="${AWS_FUNCNAME}" \ 30 | --stack-name="${AWS_STACK_NAME}" 31 | ``` 32 | 33 | This modifies the following files with your account ID, bucket, region, function name and stack name (region defaults to `us-east-1` and function name defaults to `slsNodeREDFunction`). 34 | - `package.json` 35 | - `simple-proxy-api.yaml` 36 | - `cloudformation.yaml` 37 | - `settings.js` 38 | 39 | If the bucket you specify does not yet exist, the next step will create it for you. This step modifies the existing files in-place; if you wish to make changes to these settings, you will need to modify these files manually. 40 | 41 | 3. Run the following command to installs the node dependencies, creates an S3 bucket (if it does not already exist), packages and deploys your serverless Express application to AWS Lambda, and creates an API Gateway proxy API. 42 | ``` 43 | npm run setup 44 | 45 | ### Windows users: 46 | npm run win-setup 47 | ``` 48 | 49 | 4. Make a Node-RED flow on local PC 50 | 51 | ``` 52 | node node_modules/.bin/node-red -s ./settings.js 53 | ``` 54 | 55 | open `http://localhost:1880` and make a flow using a `HTTP in` node. 56 | You can test it with local PC. 57 | `flow.json` is saved on S3 bucket. 58 | 59 | 5. After the local test, You can test it using API Gateway. 60 | open the AWS CloudFormation console https://console.aws.amazon.com/cloudformation/home and switch to the region you specified. Select the `slsNodeREDStack` stack, then click the `ApiUrl` value under the __Outputs__ section - this will open a new page with your running API. 61 | Or 62 | you can get `ApiUrl` from the output of the following command. 63 | ``` 64 | aws cloudformation describe-stacks --stack-name ${AWS_STACK_NAME} \ 65 | --output json --query "Stacks[*].Outputs" 66 | ``` 67 | 68 | 6. If you would prefer to delete AWS assets that were just created, simply run `npm run delete-stack` to delete the CloudFormation Stack, including the API and Lambda Function. If you specified a new bucket in the `config` command for step 1 and want to delete that bucket, run `npm run delete-bucket`. 69 | 70 | ## Version 71 | - Node.js version 8.12 72 | - Node-RED version 0.20.5 73 | 74 | ## Resource 75 | 76 | this directory includes 77 | 78 | - [Swagger file](http://swagger.io/specification/) 79 | - [CloudFormation template](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) 80 | - [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model) 81 | - helper scripts to help you set up and manage your application. 82 | 83 | ## Reference 84 | See the more details on how to customize an existing (or create a new) Node.js project based on this example. 85 | - [aws-serverless-express](https://github.com/awslabs/aws-serverless-express) 86 | - [Qiita (Japanese)](https://qiita.com/sakazuki/private/9d55ac14432e73524e04) 87 | -------------------------------------------------------------------------------- /lambda.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const awsServerlessExpress = require('aws-serverless-express') 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const cors = require('cors') 6 | const compression = require('compression') 7 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') 8 | const app = express() 9 | const RED = require('node-red') 10 | let server; 11 | 12 | let headless = (process.env.HEADLESS === "true") || false; 13 | let reloadFlow = (process.env.RELOAD_FLOW === "true") || false; 14 | 15 | let settings = { 16 | disableEditor: true, 17 | httpAdminRoot: false, 18 | httpNodeRoot: '/', 19 | // httpStatic: 'public', 20 | awsRegion: process.env.AWS_REGION, 21 | awsS3Bucket: process.env.S3_BUCKET, 22 | awsS3Appname: process.env.AWS_LAMBDA_FUNCTION_NAME, 23 | storageModule: require('node-red-contrib-storage-s3'), 24 | functionGlobalContext: { }, 25 | credentialSecret: process.env.NODE_RED_SECRET || "a-secret-key" 26 | }; 27 | 28 | if (headless) { 29 | settings.httpRoot = false; 30 | settings.httpAdminRoot = false; 31 | settings.httpNodeRoot = false; 32 | } 33 | // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely 34 | // due to a compressed response (e.g. gzip) which has not been handled correctly 35 | // by aws-serverless-express and/or API Gateway. Add the necessary MIME types to 36 | // binaryMimeTypes below, then redeploy (`npm run package-deploy`) 37 | const binaryMimeTypes = [ 38 | 'application/javascript', 39 | 'application/json', 40 | 'application/octet-stream', 41 | 'application/xml', 42 | 'font/eot', 43 | 'font/opentype', 44 | 'font/otf', 45 | 'image/jpeg', 46 | 'image/png', 47 | 'image/svg+xml', 48 | 'text/comma-separated-values', 49 | 'text/css', 50 | 'text/html', 51 | 'text/javascript', 52 | 'text/plain', 53 | 'text/text', 54 | 'text/xml' 55 | ] 56 | 57 | if (!headless) { 58 | app.use(compression()) 59 | app.use(cors()) 60 | app.use(bodyParser.json()) 61 | app.use(bodyParser.urlencoded({ extended: true })) 62 | app.use(awsServerlessExpressMiddleware.eventContext()) 63 | server = awsServerlessExpress.createServer(app, null, binaryMimeTypes) 64 | } 65 | 66 | let init = (() => { 67 | if (headless) { 68 | RED.init(settings) 69 | }else{ 70 | RED.init(server, settings) 71 | //app.use(settings.httpAdminRoot,RED.httpAdmin); 72 | app.use(settings.httpNodeRoot,RED.httpNode); 73 | } 74 | return new Promise((resolve, reject) => { 75 | let deployed; 76 | RED.events.on("runtime-event", deployed = function(data){ 77 | if (data.id === "runtime-deploy") { 78 | RED.events.removeListener("runtime-event", deployed); 79 | // console.log('flow deployed'); 80 | resolve(); 81 | } 82 | }) 83 | RED.start(); 84 | }); 85 | })() 86 | 87 | function setup(){ 88 | return init.then(() => { 89 | return new Promise((resolve, reject) => { 90 | if (reloadFlow) { 91 | RED.nodes.loadFlows().then(() => { resolve() }); 92 | }else{ 93 | resolve(); 94 | } 95 | }); 96 | }); 97 | } 98 | 99 | exports.handler = (event, context, callback) => { 100 | setup().then(()=>{ 101 | if (headless) { 102 | let handlers = {}; 103 | function clearHandlers(){ 104 | for(var key in handlers) RED.events.removeListener(key, handlers[key]); 105 | } 106 | function setHandlers(){ 107 | for(var key in handlers) RED.events.once(key, handlers[key]); 108 | } 109 | handlers['aws:lambda:done:' + context.awsRequestId] = function(msg){ clearHandlers(); callback(null, msg) }; 110 | handlers['aws:lambda:error'] = function(msg){ clearHandlers(); callback(msg) }; 111 | setHandlers(); 112 | RED.events.emit('aws:lambda:invoke', event, context) 113 | }else{ 114 | awsServerlessExpress.proxy(server, event, context) 115 | } 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /packaged-sam.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Serverless Express Application/API powered by API Gateway and Lambda 3 | Outputs: 4 | ApiGatewayApiConsoleUrl: 5 | Description: Console URL for the API Gateway API's Stage. 6 | Value: 7 | Fn::Join: 8 | - '' 9 | - - https:// 10 | - Ref: AWS::Region 11 | - .console.aws.amazon.com/apigateway/home?region= 12 | - Ref: AWS::Region 13 | - '#/apis/' 14 | - Ref: ApiGatewayApi 15 | - /stages/prod 16 | ApiUrl: 17 | Description: Invoke URL for your API. Clicking this link will perform a GET request 18 | on the root resource of your API. 19 | Value: 20 | Fn::Join: 21 | - '' 22 | - - https:// 23 | - Ref: ApiGatewayApi 24 | - .execute-api. 25 | - Ref: AWS::Region 26 | - .amazonaws.com/prod/ 27 | LambdaFunctionConsoleUrl: 28 | Description: Console URL for the Lambda Function. 29 | Value: 30 | Fn::Join: 31 | - '' 32 | - - https:// 33 | - Ref: AWS::Region 34 | - .console.aws.amazon.com/lambda/home?region= 35 | - Ref: AWS::Region 36 | - '#/functions/' 37 | - Ref: api12 38 | Parameters: 39 | S3BUCKET: 40 | Default: nodered12 41 | Type: String 42 | Resources: 43 | ApiGatewayApi: 44 | Properties: 45 | DefinitionUri: s3://nodered12/7257d1129935247e1ec4773106900ae4 46 | StageName: prod 47 | Variables: 48 | ServerlessExpressLambdaFunctionName: 49 | Ref: api12 50 | Type: AWS::Serverless::Api 51 | LambdaApiGatewayExecutionPermission: 52 | Properties: 53 | Action: lambda:InvokeFunction 54 | FunctionName: 55 | Fn::GetAtt: 56 | - api12 57 | - Arn 58 | Principal: apigateway.amazonaws.com 59 | SourceArn: 60 | Fn::Join: 61 | - '' 62 | - - 'arn:aws:execute-api:' 63 | - Ref: AWS::Region 64 | - ':' 65 | - Ref: AWS::AccountId 66 | - ':' 67 | - Ref: ApiGatewayApi 68 | - /*/* 69 | Type: AWS::Lambda::Permission 70 | LambdaExecutionRole: 71 | Properties: 72 | AssumeRolePolicyDocument: 73 | Statement: 74 | Action: sts:AssumeRole 75 | Effect: Allow 76 | Principal: 77 | Service: lambda.amazonaws.com 78 | Version: '2012-10-17' 79 | Path: / 80 | Policies: 81 | - PolicyDocument: 82 | Statement: 83 | - Action: 84 | - logs:CreateLogGroup 85 | - logs:CreateLogStream 86 | - logs:PutLogEvents 87 | Effect: Allow 88 | Resource: arn:aws:logs:*:*:* 89 | Version: '2012-10-17' 90 | PolicyName: root 91 | Type: AWS::IAM::Role 92 | LambdaExecutionRolePolicyAppend: 93 | Properties: 94 | PolicyDocument: 95 | Statement: 96 | - Action: 97 | - s3:CreateBucket 98 | - s3:ListBucket 99 | Effect: Allow 100 | Resource: arn:aws:s3:::nodered12 101 | - Action: 102 | - s3:PutObject 103 | - s3:GetObject 104 | Effect: Allow 105 | Resource: 106 | Fn::Join: 107 | - / 108 | - - arn:aws:s3:::nodered12 109 | - Ref: api12 110 | - '*' 111 | Version: '2012-10-17' 112 | PolicyName: S3_access 113 | Roles: 114 | - Ref: LambdaExecutionRole 115 | Type: AWS::IAM::Policy 116 | api12: 117 | Properties: 118 | CodeUri: s3://nodered12/461c4af826c7ac14282e564f0fdf81b7 119 | Environment: 120 | Variables: 121 | S3_BUCKET: 122 | Ref: S3BUCKET 123 | Events: 124 | ProxyApiGreedy: 125 | Properties: 126 | Method: ANY 127 | Path: /{proxy+} 128 | RestApiId: 129 | Ref: ApiGatewayApi 130 | Type: Api 131 | ProxyApiRoot: 132 | Properties: 133 | Method: ANY 134 | Path: / 135 | RestApiId: 136 | Ref: ApiGatewayApi 137 | Type: Api 138 | FunctionName: api12 139 | Handler: lambda.handler 140 | MemorySize: 1024 141 | Role: 142 | Fn::GetAtt: 143 | - LambdaExecutionRole 144 | - Arn 145 | Runtime: nodejs6.10 146 | Timeout: 30 147 | Type: AWS::Serverless::Function 148 | Transform: AWS::Serverless-2016-10-31 149 | -------------------------------------------------------------------------------- /cloudformation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: Serverless Express Application/API powered by API Gateway and Lambda 5 | Parameters: 6 | S3BUCKET: 7 | Type: "String" 8 | Default: "YOUR_UNIQUE_BUCKET_NAME" 9 | Resources: 10 | ApiGatewayApi: 11 | Type: AWS::Serverless::Api 12 | Properties: 13 | DefinitionUri: ./simple-proxy-api.yaml 14 | StageName: prod 15 | Variables: 16 | ServerlessExpressLambdaFunctionName: !Ref YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME 17 | 18 | LambdaExecutionRole: 19 | Type: AWS::IAM::Role 20 | Properties: 21 | AssumeRolePolicyDocument: 22 | Version: '2012-10-17' 23 | Statement: 24 | Effect: Allow 25 | Principal: 26 | Service: lambda.amazonaws.com 27 | Action: sts:AssumeRole 28 | Path: "/" 29 | Policies: 30 | - PolicyName: root 31 | PolicyDocument: 32 | Version: '2012-10-17' 33 | Statement: 34 | - Effect: Allow 35 | Action: 36 | - logs:CreateLogGroup 37 | - logs:CreateLogStream 38 | - logs:PutLogEvents 39 | Resource: arn:aws:logs:*:*:* 40 | 41 | LambdaExecutionRolePolicyAppend: 42 | Type: "AWS::IAM::Policy" 43 | Properties: 44 | PolicyDocument: 45 | Version: '2012-10-17' 46 | Statement: 47 | - Effect: "Allow" 48 | Action: 49 | - "s3:CreateBucket" 50 | - "s3:ListBucket" 51 | Resource: "arn:aws:s3:::YOUR_UNIQUE_BUCKET_NAME" 52 | - Effect: "Allow" 53 | Action: 54 | - "s3:PutObject" 55 | - "s3:GetObject" 56 | Resource: !Join 57 | - '/' 58 | - - "arn:aws:s3:::YOUR_UNIQUE_BUCKET_NAME" 59 | - !Ref YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME 60 | - '*' 61 | PolicyName: S3_access 62 | Roles: 63 | - !Ref LambdaExecutionRole 64 | 65 | LambdaApiGatewayExecutionPermission: 66 | Type: AWS::Lambda::Permission 67 | Properties: 68 | Action: lambda:InvokeFunction 69 | FunctionName: !GetAtt YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME.Arn 70 | Principal: apigateway.amazonaws.com 71 | SourceArn: !Join 72 | - '' 73 | - - 'arn:aws:execute-api:' 74 | - !Ref AWS::Region 75 | - ":" 76 | - !Ref AWS::AccountId 77 | - ":" 78 | - !Ref ApiGatewayApi 79 | - "/*/*" 80 | 81 | YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME: 82 | Type: AWS::Serverless::Function 83 | Properties: 84 | CodeUri: ./ 85 | FunctionName: YOUR_LAMBDA_FUNCTION_NAME 86 | Handler: lambda.handler 87 | MemorySize: 1024 88 | Role: !GetAtt LambdaExecutionRole.Arn 89 | Runtime: nodejs12.x 90 | Timeout: 30 91 | Environment: 92 | Variables: 93 | "S3_BUCKET": !Ref S3BUCKET 94 | Events: 95 | ProxyApiRoot: 96 | Type: Api 97 | Properties: 98 | RestApiId: !Ref ApiGatewayApi 99 | Path: / 100 | Method: ANY 101 | ProxyApiGreedy: 102 | Type: Api 103 | Properties: 104 | RestApiId: !Ref ApiGatewayApi 105 | Path: /{proxy+} 106 | Method: ANY 107 | 108 | Outputs: 109 | LambdaFunctionConsoleUrl: 110 | Description: Console URL for the Lambda Function. 111 | Value: !Join 112 | - '' 113 | - - https:// 114 | - !Ref AWS::Region 115 | - ".console.aws.amazon.com/lambda/home?region=" 116 | - !Ref AWS::Region 117 | - "#/functions/" 118 | - !Ref YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME 119 | 120 | ApiGatewayApiConsoleUrl: 121 | Description: Console URL for the API Gateway API's Stage. 122 | Value: !Join 123 | - '' 124 | - - https:// 125 | - !Ref AWS::Region 126 | - ".console.aws.amazon.com/apigateway/home?region=" 127 | - !Ref AWS::Region 128 | - "#/apis/" 129 | - !Ref ApiGatewayApi 130 | - "/stages/prod" 131 | 132 | ApiUrl: 133 | Description: Invoke URL for your API. Clicking this link will perform a GET request 134 | on the root resource of your API. 135 | Value: !Join 136 | - '' 137 | - - https:// 138 | - !Ref ApiGatewayApi 139 | - ".execute-api." 140 | - !Ref AWS::Region 141 | - ".amazonaws.com/prod/" 142 | -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright JS Foundation and other contributors, http://js.foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // The `https` setting requires the `fs` module. Uncomment the following 18 | // to make it available: 19 | //var fs = require("fs"); 20 | 21 | module.exports = { 22 | // the tcp port that the Node-RED web server is listening on 23 | uiPort: process.env.PORT || 1880, 24 | awsRegion: 'YOUR_AWS_REGION', 25 | awsS3Bucket: 'YOUR_UNIQUE_BUCKET_NAME', 26 | awsS3Appname: 'YOUR_LAMBDA_FUNCTION_NAME', 27 | storageModule: require('node-red-contrib-storage-s3'), 28 | // By default, the Node-RED UI accepts connections on all IPv4 interfaces. 29 | // The following property can be used to listen on a specific interface. For 30 | // example, the following would only allow connections from the local machine. 31 | //uiHost: "127.0.0.1", 32 | 33 | // Retry time in milliseconds for MQTT connections 34 | mqttReconnectTime: 15000, 35 | 36 | // Retry time in milliseconds for Serial port connections 37 | serialReconnectTime: 15000, 38 | 39 | // Retry time in milliseconds for TCP socket connections 40 | //socketReconnectTime: 10000, 41 | 42 | // Timeout in milliseconds for TCP server socket connections 43 | // defaults to no timeout 44 | //socketTimeout: 120000, 45 | 46 | // Timeout in milliseconds for HTTP request connections 47 | // defaults to 120 seconds 48 | //httpRequestTimeout: 120000, 49 | 50 | // The maximum length, in characters, of any message sent to the debug sidebar tab 51 | debugMaxLength: 1000, 52 | 53 | // Colourise the console output of the debug node 54 | //debugUseColors: true, 55 | 56 | // The file containing the flows. If not set, it defaults to flows_.json 57 | //flowFile: 'flows.json', 58 | 59 | // To enabled pretty-printing of the flow within the flow file, set the following 60 | // property to true: 61 | //flowFilePretty: true, 62 | 63 | // By default, credentials are encrypted in storage using a generated key. To 64 | // specify your own secret, set the following property. 65 | // If you want to disable encryption of credentials, set this property to false. 66 | // Note: once you set this property, do not change it - doing so will prevent 67 | // node-red from being able to decrypt your existing credentials and they will be 68 | // lost. 69 | credentialSecret: process.env.NODE_RED_SECRET || "a-secret-key", 70 | 71 | // By default, all user data is stored in the Node-RED install directory. To 72 | // use a different location, the following property can be used 73 | //userDir: '/home/nol/.node-red/', 74 | 75 | // Node-RED scans the `nodes` directory in the install directory to find nodes. 76 | // The following property can be used to specify an additional directory to scan. 77 | //nodesDir: '/home/nol/.node-red/nodes', 78 | 79 | // By default, the Node-RED UI is available at http://localhost:1880/ 80 | // The following property can be used to specifiy a different root path. 81 | // If set to false, this is disabled. 82 | //httpAdminRoot: '/admin', 83 | 84 | // Some nodes, such as HTTP In, can be used to listen for incoming http requests. 85 | // By default, these are served relative to '/'. The following property 86 | // can be used to specifiy a different root path. If set to false, this is 87 | // disabled. 88 | //httpNodeRoot: '/red-nodes', 89 | 90 | // The following property can be used in place of 'httpAdminRoot' and 'httpNodeRoot', 91 | // to apply the same root to both parts. 92 | //httpRoot: '/red', 93 | 94 | // When httpAdminRoot is used to move the UI to a different root path, the 95 | // following property can be used to identify a directory of static content 96 | // that should be served at http://localhost:1880/. 97 | //httpStatic: '/home/nol/node-red-static/', 98 | 99 | // The maximum size of HTTP request that will be accepted by the runtime api. 100 | // Default: 5mb 101 | //apiMaxLength: '5mb', 102 | 103 | // If you installed the optional node-red-dashboard you can set it's path 104 | // relative to httpRoot 105 | //ui: { path: "ui" }, 106 | 107 | // Securing Node-RED 108 | // ----------------- 109 | // To password protect the Node-RED editor and admin API, the following 110 | // property can be used. See http://nodered.org/docs/security.html for details. 111 | //adminAuth: { 112 | // type: "credentials", 113 | // users: [{ 114 | // username: "admin", 115 | // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", 116 | // permissions: "*" 117 | // }] 118 | //}, 119 | 120 | // To password protect the node-defined HTTP endpoints (httpNodeRoot), or 121 | // the static content (httpStatic), the following properties can be used. 122 | // The pass field is a bcrypt hash of the password. 123 | // See http://nodered.org/docs/security.html#generating-the-password-hash 124 | //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 125 | //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, 126 | 127 | // The following property can be used to enable HTTPS 128 | // See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener 129 | // for details on its contents. 130 | // See the comment at the top of this file on how to load the `fs` module used by 131 | // this setting. 132 | // 133 | //https: { 134 | // key: fs.readFileSync('privatekey.pem'), 135 | // cert: fs.readFileSync('certificate.pem') 136 | //}, 137 | 138 | // The following property can be used to cause insecure HTTP connections to 139 | // be redirected to HTTPS. 140 | //requireHttps: true 141 | 142 | // The following property can be used to disable the editor. The admin API 143 | // is not affected by this option. To disable both the editor and the admin 144 | // API, use either the httpRoot or httpAdminRoot properties 145 | //disableEditor: false, 146 | 147 | // The following property can be used to configure cross-origin resource sharing 148 | // in the HTTP nodes. 149 | // See https://github.com/troygoode/node-cors#configuration-options for 150 | // details on its contents. The following is a basic permissive set of options: 151 | //httpNodeCors: { 152 | // origin: "*", 153 | // methods: "GET,PUT,POST,DELETE" 154 | //}, 155 | 156 | // If you need to set an http proxy please set an environment variable 157 | // called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. 158 | // For example - http_proxy=http://myproxy.com:8080 159 | // (Setting it here will have no effect) 160 | // You may also specify no_proxy (or NO_PROXY) to supply a comma separated 161 | // list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk 162 | 163 | // The following property can be used to add a custom middleware function 164 | // in front of all http in nodes. This allows custom authentication to be 165 | // applied to all http in nodes, or any other sort of common request processing. 166 | //httpNodeMiddleware: function(req,res,next) { 167 | // // Handle/reject the request, or pass it on to the http in node by calling next(); 168 | // // Optionally skip our rawBodyParser by setting this to true; 169 | // //req.skipRawBodyParser = true; 170 | // next(); 171 | //}, 172 | 173 | // Anything in this hash is globally available to all functions. 174 | // It is accessed as context.global. 175 | // eg: 176 | // functionGlobalContext: { os:require('os') } 177 | // can be accessed in a function block as: 178 | // context.global.os 179 | 180 | functionGlobalContext: { 181 | // os:require('os'), 182 | // octalbonescript:require('octalbonescript'), 183 | // jfive:require("johnny-five"), 184 | // j5board:require("johnny-five").Board({repl:false}) 185 | }, 186 | 187 | // The following property can be used to order the categories in the editor 188 | // palette. If a node's category is not in the list, the category will get 189 | // added to the end of the palette. 190 | // If not set, the following default order is used: 191 | //paletteCategories: ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'], 192 | 193 | // Configure the logging output 194 | logging: { 195 | // Only console logging is currently supported 196 | console: { 197 | // Level of logging to be recorded. Options are: 198 | // fatal - only those errors which make the application unusable should be recorded 199 | // error - record errors which are deemed fatal for a particular request + fatal errors 200 | // warn - record problems which are non fatal + errors + fatal errors 201 | // info - record information about the general running of the application + warn + error + fatal errors 202 | // debug - record information which is more verbose than info + info + warn + error + fatal errors 203 | // trace - record very detailed logging + debug + info + warn + error + fatal errors 204 | level: "info", 205 | // Whether or not to include metric events in the log output 206 | metrics: false, 207 | // Whether or not to include audit events in the log output 208 | audit: false 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------