diff --git a/aws-golang-simple-http-endpoint/serverless.yml b/aws-golang-simple-http-endpoint/serverless.yml index 4afac44b1..2585a31e1 100644 --- a/aws-golang-simple-http-endpoint/serverless.yml +++ b/aws-golang-simple-http-endpoint/serverless.yml @@ -1,6 +1,5 @@ service: aws-golang-simple-http-endpoint - -frameworkVersion: ">=1.28.0 <2.0.0" +frameworkVersion: '2' provider: name: aws @@ -10,14 +9,14 @@ functions: hello: handler: bin/hello events: - - http: - path: hello + - httpApi: + path: /hello method: get world: handler: bin/world events: - - http: - path: world + - httpApi: + path: /world method: get package: diff --git a/aws-java-simple-http-endpoint/README.md b/aws-java-simple-http-endpoint/README.md index 97bbd1892..f90e0038e 100644 --- a/aws-java-simple-http-endpoint/README.md +++ b/aws-java-simple-http-endpoint/README.md @@ -110,7 +110,7 @@ region: us-east-1 api keys: None endpoints: - GET - https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/ping + GET - https://XXXXXXX.execute-api.us-east-1.amazonaws.com/ping functions: aws-java-simple-http-endpoint-dev-currentTime: arn:aws:lambda:us-east-1:XXXXXXX:function:aws-java-simple-http-endpoint-dev-currentTime @@ -146,7 +146,7 @@ REPORT RequestId: XXXXXXX Duration: 0.51 ms Billed Duration: 100 ms Memory Size Finally you can send an HTTP request directly to the endpoint using a tool like curl ```bash -curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/ping +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/ping ``` The expected result should be similar to: @@ -157,4 +157,4 @@ The expected result should be similar to: ## Scaling -By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). diff --git a/aws-java-simple-http-endpoint/serverless.yml b/aws-java-simple-http-endpoint/serverless.yml index d271c7d01..804b09565 100644 --- a/aws-java-simple-http-endpoint/serverless.yml +++ b/aws-java-simple-http-endpoint/serverless.yml @@ -1,11 +1,10 @@ service: aws-java-simple-http-endpoint - -frameworkVersion: ">=1.2.0 <2.0.0" +frameworkVersion: '2' provider: name: aws runtime: java8 - + package: artifact: build/distributions/aws-java-simple-http-endpoint.zip @@ -13,6 +12,6 @@ functions: currentTime: handler: com.serverless.Handler events: - - http: - path: ping + - httpApi: + path: /ping method: get diff --git a/aws-multiple-runtime/serverless.yml b/aws-multiple-runtime/serverless.yml index 88c1fe949..4c5abf22e 100644 --- a/aws-multiple-runtime/serverless.yml +++ b/aws-multiple-runtime/serverless.yml @@ -6,14 +6,14 @@ functions: hello: runtime: python3.6 events: - - http: + - httpApi: method: get - path: greet + path: /greet handler: web/handler.hello time: runtime: nodejs12.x events: - - http: + - httpApi: method: get - path: time + path: /time handler: api/handler.timestamp diff --git a/aws-node-express-api/README.md b/aws-node-express-api/README.md index 343b2ed68..fea525540 100644 --- a/aws-node-express-api/README.md +++ b/aws-node-express-api/README.md @@ -17,27 +17,19 @@ This template demonstrates how to develop and deploy a simple Node Express API s ## Anatomy of the template -This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `express` framework is responsible for routing and handling requests internally. Implementation takes advantage of `serverless-http` package, which allows you to wrap existing `express` applications. To learn more about `serverless-http`, please refer to corresponding [GitHub repository](https://github.com/dougmoscrop/serverless-http). +This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to the `httpApi` event. To learn more about `httpApi` event configuration options, please refer to [httpApi event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). As the event is configured in a way to accept all incoming requests, `express` framework is responsible for routing and handling requests internally. Implementation takes advantage of `serverless-http` package, which allows you to wrap existing `express` applications. To learn more about `serverless-http`, please refer to corresponding [GitHub repository](https://github.com/dougmoscrop/serverless-http). ## Usage ### Deployment -This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. - -In order to deploy with dashboard, you need to first login with: - -``` -serverless login -``` - -install dependencies with: +Install dependencies with: ``` npm install ``` -and then perform deployment with: +and then deploy with: ``` serverless deploy @@ -69,22 +61,21 @@ resources: 12 api keys: None endpoints: - ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ - ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} + ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ functions: api: aws-node-express-api-dev-api layers: None ``` -_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). +_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [`httpApi` event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). ### Invocation After successful deployment, you can call the created application via HTTP: ```bash -curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ ``` Which should result in the following response: @@ -96,7 +87,7 @@ Which should result in the following response: Calling the `/hello` path with: ```bash -curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello ``` Should result in the following response: @@ -108,7 +99,7 @@ Should result in the following response: If you try to invoke a path or method that does not have a configured handler, e.g. with: ```bash -curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/nonexistent +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/nonexistent ``` You should receive the following response: diff --git a/aws-node-express-api/serverless.template.yml b/aws-node-express-api/serverless.template.yml index 03a3b6860..f29ef7f8e 100644 --- a/aws-node-express-api/serverless.template.yml +++ b/aws-node-express-api/serverless.template.yml @@ -1,6 +1,6 @@ name: aws-node-express-api org: serverlessinc -description: Deploys a Node Express API service with traditional Serverless Framework +description: Deploys a Node Express API service with Serverless Framework keywords: aws, serverless, faas, lambda, node, express repo: https://github.com/serverless/examples/aws-node-express-api license: MIT diff --git a/aws-node-express-api/serverless.yml b/aws-node-express-api/serverless.yml index d2e2f9996..fd97b726a 100644 --- a/aws-node-express-api/serverless.yml +++ b/aws-node-express-api/serverless.yml @@ -1,8 +1,6 @@ service: aws-node-express-api - frameworkVersion: '2' - provider: name: aws runtime: nodejs12.x @@ -12,9 +10,4 @@ functions: api: handler: handler.handler events: - - http: - path: / - method: ANY - - http: - path: /{proxy+} - method: ANY + - httpApi: '*' diff --git a/aws-node-express-dynamodb-api/README.md b/aws-node-express-dynamodb-api/README.md index d54a46ab2..d3fdf78bc 100644 --- a/aws-node-express-dynamodb-api/README.md +++ b/aws-node-express-dynamodb-api/README.md @@ -18,27 +18,19 @@ This template demonstrates how to develop and deploy a simple Node Express API s ## Anatomy of the template -This template configures a single function, `api`, in `serverless.yml` which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `express` framework is responsible for routing and handling requests internally. Implementation takes advantage of `serverless-http` package, which allows you to wrap existing `express` applications. To learn more about `serverless-http`, please refer to corresponding [GitHub repository](https://github.com/dougmoscrop/serverless-http). Additionally, it also handles provisioning of a DynamoDB database that is used for storing data about users. The `express` application exposes two endpoints, `POST /users` and `GET /user/{userId}`, which allow to create and retrieve users. +This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to the `httpApi` event. To learn more about `httpApi` event configuration options, please refer to [httpApi event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). As the event is configured in a way to accept all incoming requests, `express` framework is responsible for routing and handling requests internally. Implementation takes advantage of `serverless-http` package, which allows you to wrap existing `express` applications. To learn more about `serverless-http`, please refer to corresponding [GitHub repository](https://github.com/dougmoscrop/serverless-http). Additionally, it also handles provisioning of a DynamoDB database that is used for storing data about users. The `express` application exposes two endpoints, `POST /users` and `GET /user/{userId}`, which allow to create and retrieve users. ## Usage ### Deployment -This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. - -In order to deploy with dashboard, you need to first login with: - -``` -serverless login -``` - -install dependencies with: +Install dependencies with: ``` npm install ``` -and then perform deployment with: +and then deploy with: ``` serverless deploy @@ -70,22 +62,21 @@ resources: 13 api keys: None endpoints: - ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ - ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} + ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ functions: api: aws-node-express-dynamodb-api-dev-api layers: None ``` -_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). Additionally, in current configuration, DynamoDB Table will be removed when running `serverless remove`. To retain DynamoDB Table even after removal of the stack, add `DeletionPolicy: Retain` to its resource definition. +_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [`httpApi` event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). Additionally, in current configuration, the DynamoDB table will be removed when running `serverless remove`. To retain the DynamoDB table even after removal of the stack, add `DeletionPolicy: Retain` to its resource definition. ### Invocation After successful deployment, you can create a new user by calling the corresponding endpoint: ```bash -curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}' +curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}' ``` Which should result in the following response: @@ -97,7 +88,7 @@ Which should result in the following response: You can later retrieve the user by `userId` by calling the following endpoint: ```bash -curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/someUserId +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/users/someUserId ``` Which should result in the following response: @@ -114,7 +105,7 @@ If you try to retrieve user that does not exist, you should receive the followin ### Local development -It is also possible to emulate DynamodB, API Gateway and Lambda locally by using `serverless-dynamodb-local` and `serverless-offline` plugins. In order to do that, execute the following commands: +It is also possible to emulate DynamoDB, API Gateway and Lambda locally using the `serverless-dynamodb-local` and `serverless-offline` plugins. In order to do that, run: ```bash serverless plugin install -n serverless-dynamodb-local diff --git a/aws-node-express-dynamodb-api/serverless.template.yml b/aws-node-express-dynamodb-api/serverless.template.yml index f66d02d8e..520752083 100644 --- a/aws-node-express-dynamodb-api/serverless.template.yml +++ b/aws-node-express-dynamodb-api/serverless.template.yml @@ -1,6 +1,6 @@ name: aws-node-express-dynamodb-api org: serverlessinc -description: Deploys a Node Express API service backed by DynamoDB with traditional Serverless Framework +description: Deploys a Node Express API service backed by DynamoDB with Serverless Framework keywords: aws, serverless, faas, lambda, node, express, dynamodb repo: https://github.com/serverless/examples/aws-node-express-dynamodb-api license: MIT diff --git a/aws-node-express-dynamodb-api/serverless.yml b/aws-node-express-dynamodb-api/serverless.yml index d244b7594..c103746a0 100644 --- a/aws-node-express-dynamodb-api/serverless.yml +++ b/aws-node-express-dynamodb-api/serverless.yml @@ -1,15 +1,13 @@ service: aws-node-express-dynamodb-api - frameworkVersion: '2' custom: - tableName: 'users-table-${self:provider.stage}' + tableName: 'users-table-${sls:stage}' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' - stage: dev iam: role: statements: @@ -30,12 +28,7 @@ functions: api: handler: handler.handler events: - - http: - path: / - method: ANY - - http: - path: /{proxy+} - method: ANY + - httpApi: '*' resources: Resources: @@ -48,7 +41,5 @@ resources: KeySchema: - AttributeName: userId KeyType: HASH - ProvisionedThroughput: - ReadCapacityUnits: 1 - WriteCapacityUnits: 1 + BillingMode: PAY_PER_REQUEST TableName: ${self:custom.tableName} diff --git a/aws-node-http-api-dynamodb-local/.gitignore b/aws-node-http-api-dynamodb-local/.gitignore new file mode 100644 index 000000000..e0f039d9d --- /dev/null +++ b/aws-node-http-api-dynamodb-local/.gitignore @@ -0,0 +1,2 @@ +.serverless +node_modules diff --git a/aws-node-http-api-dynamodb-local/README.md b/aws-node-http-api-dynamodb-local/README.md new file mode 100644 index 000000000..c1a89aec4 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/README.md @@ -0,0 +1,98 @@ + +# Serverless HTTP API with DynamoDB and offline support + +This example demonstrates how to run a service locally, using the +[serverless-offline](https://github.com/dherault/serverless-offline) plugin. It +provides an HTTP API to manage Todos stored in a DynamoDB, similar to the +[aws-node-http-api-dynamodb](https://github.com/serverless/examples/tree/master/aws-node-http-api-dynamodb) +example. A local DynamoDB instance is provided by the +[serverless-dynamodb-local](https://github.com/99xt/serverless-dynamodb-local) +plugin. + +## Use-case + +Test your service locally, without having to deploy it first. + +## Setup + +```bash +npm install +serverless dynamodb install (or to use a persistent docker dynamodb instead, open a new terminal: cd ./dynamodb && docker-compose up -d) +serverless offline start +serverless dynamodb migrate (this imports schema) +``` + +## Run service offline + +```bash +serverless offline start +``` + +## Usage + +You can create, retrieve, update, or delete todos with the following commands: + +### Create a Todo + +```bash +curl -X POST -H "Content-Type:application/json" http://localhost:3000/todos --data '{ "text": "Learn Serverless" }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### List all Todos + +```bash +curl -H "Content-Type:application/json" http://localhost:3000/todos +``` + +Example output: +```bash +[{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% +``` + +### Get one Todo + +```bash +# Replace the part with a real id from your todos table +curl -H "Content-Type:application/json" http://localhost:3000/todos/ +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### Update a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X PUT -H "Content-Type:application/json" http://localhost:3000/todos/ --data '{ "text": "Learn Serverless", "checked": true }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% +``` + +### Delete a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X DELETE -H "Content-Type:application/json" http://localhost:3000/todos/ +``` + +No output diff --git a/aws-node-http-api-dynamodb-local/dynamodb/Dockerfile b/aws-node-http-api-dynamodb-local/dynamodb/Dockerfile new file mode 100644 index 000000000..17c75923e --- /dev/null +++ b/aws-node-http-api-dynamodb-local/dynamodb/Dockerfile @@ -0,0 +1,8 @@ +FROM amazon/dynamodb-local + +WORKDIR /home/dynamodblocal + +RUN mkdir ./db && chown -R 1000 ./db + +CMD ["-jar", "DynamoDBLocal.jar", "-dbPath", "./db", "-sharedDb"] +VOLUME ["./db"] diff --git a/aws-node-http-api-dynamodb-local/dynamodb/docker-compose.yml b/aws-node-http-api-dynamodb-local/dynamodb/docker-compose.yml new file mode 100644 index 000000000..af6720ae2 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/dynamodb/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3" + +services: + dynamodb: + build: + context: . + dockerfile: Dockerfile + ports: + - 8000:8000 + volumes: + - aws-http-api-dynamodb:/home/dynamodblocal/db + +volumes: + aws-http-api-dynamodb: + driver: local diff --git a/aws-node-http-api-dynamodb-local/offline/migrations/todos.json b/aws-node-http-api-dynamodb-local/offline/migrations/todos.json new file mode 100644 index 000000000..aa05790fa --- /dev/null +++ b/aws-node-http-api-dynamodb-local/offline/migrations/todos.json @@ -0,0 +1,21 @@ +{ + "Table": { + "TableName": "serverless-http-api-dynamodb-local-dev", + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1 + } + } +} diff --git a/aws-node-http-api-dynamodb-local/package.json b/aws-node-http-api-dynamodb-local/package.json new file mode 100644 index 000000000..42eeb45f2 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/package.json @@ -0,0 +1,16 @@ +{ + "name": "serverless-http-api-dynamodb-local", + "version": "1.0.0", + "description": "Serverless HTTP API with DynamoDB and offline support", + "repository": "", + "author": "Christoph Gysin ", + "license": "MIT", + "dependencies": { + "uuid": "^2.0.3" + }, + "devDependencies": { + "aws-sdk": "^2.12.0", + "serverless-dynamodb-local": "^0.2.18", + "serverless-offline": "^6.8.0" + } +} diff --git a/aws-node-http-api-dynamodb-local/serverless.yml b/aws-node-http-api-dynamodb-local/serverless.yml new file mode 100644 index 000000000..bf69bba33 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/serverless.yml @@ -0,0 +1,93 @@ +service: serverless-http-api-dynamodb-local +frameworkVersion: '2' + +plugins: + - serverless-dynamodb-local + - serverless-offline + +custom: + dynamodb: + stages: + - dev + start: + port: 8000 + inMemory: true + migrate: true + # Comment if you don't have a DynamoDB running locally + noStart: true + migration: + dir: offline/migrations + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: '20201221' + environment: + DYNAMODB_TABLE: ${self:service}-${sls:stage} + httpApi: + cors: true + iam: + role: + statements: + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:Scan + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" + +functions: + create: + handler: todos/create.create + events: + - httpApi: + path: /todos + method: post + + list: + handler: todos/list.list + events: + - httpApi: + path: /todos + method: get + + get: + handler: todos/get.get + events: + - httpApi: + path: /todos/{id} + method: get + + update: + handler: todos/update.update + events: + - httpApi: + path: /todos/{id} + method: put + + delete: + handler: todos/delete.delete + events: + - httpApi: + path: /todos/{id} + method: delete + +resources: + Resources: + TodosDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + Properties: + AttributeDefinitions: + - + AttributeName: id + AttributeType: S + KeySchema: + - + AttributeName: id + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: ${self:provider.environment.DYNAMODB_TABLE} diff --git a/aws-node-http-api-dynamodb-local/todos/create.js b/aws-node-http-api-dynamodb-local/todos/create.js new file mode 100644 index 000000000..5077cf66f --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/create.js @@ -0,0 +1,50 @@ +'use strict'; + +const uuid = require('uuid'); +const dynamodb = require('./dynamodb'); + +module.exports.create = (event, context, callback) => { + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + if (typeof data.text !== 'string') { + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the todo item.', + }); + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Item: { + id: uuid.v1(), + text: data.text, + checked: false, + createdAt: timestamp, + updatedAt: timestamp, + }, + }; + + // write the todo to the database + dynamodb.put(params, (error) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(params.Item), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb-local/todos/delete.js b/aws-node-http-api-dynamodb-local/todos/delete.js new file mode 100644 index 000000000..60f7dea13 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/delete.js @@ -0,0 +1,33 @@ +'use strict'; + +const dynamodb = require('./dynamodb'); + +module.exports.delete = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + }; + + // delete the todo from the database + dynamodb.delete(params, (error) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify({}), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb-local/todos/dynamodb.js b/aws-node-http-api-dynamodb-local/todos/dynamodb.js new file mode 100644 index 000000000..2a08ba9c4 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/dynamodb.js @@ -0,0 +1,17 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +let options = {}; + +// connect to local DB if running offline +if (process.env.IS_OFFLINE) { + options = { + region: 'localhost', + endpoint: 'http://localhost:8000', + }; +} + +const client = new AWS.DynamoDB.DocumentClient(options); + +module.exports = client; diff --git a/aws-node-http-api-dynamodb-local/todos/get.js b/aws-node-http-api-dynamodb-local/todos/get.js new file mode 100644 index 000000000..cfe50586c --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/get.js @@ -0,0 +1,33 @@ +'use strict'; + +const dynamodb = require('./dynamodb'); + +module.exports.get = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + }; + + // fetch todo from the database + dynamodb.get(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Item), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb-local/todos/list.js b/aws-node-http-api-dynamodb-local/todos/list.js new file mode 100644 index 000000000..9592c7498 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/list.js @@ -0,0 +1,30 @@ +'use strict'; + +const dynamodb = require('./dynamodb'); + +module.exports.list = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + }; + + // fetch all todos from the database + dynamodb.scan(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Items), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb-local/todos/update.js b/aws-node-http-api-dynamodb-local/todos/update.js new file mode 100644 index 000000000..5a0106fa1 --- /dev/null +++ b/aws-node-http-api-dynamodb-local/todos/update.js @@ -0,0 +1,57 @@ +'use strict'; + +const dynamodb = require('./dynamodb'); + +module.exports.update = (event, context, callback) => { + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + + // validation + if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t update the todo item.', + }); + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + ExpressionAttributeNames: { + '#todo_text': 'text', + }, + ExpressionAttributeValues: { + ':text': data.text, + ':checked': data.checked, + ':updatedAt': timestamp, + }, + UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', + ReturnValues: 'ALL_NEW', + }; + + // update the todo in the database + dynamodb.update(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t update the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Attributes), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb/.gitignore b/aws-node-http-api-dynamodb/.gitignore new file mode 100644 index 000000000..2c4448065 --- /dev/null +++ b/aws-node-http-api-dynamodb/.gitignore @@ -0,0 +1,2 @@ +node_modules +.serverless diff --git a/aws-node-http-api-dynamodb/README.md b/aws-node-http-api-dynamodb/README.md new file mode 100644 index 000000000..5c9197046 --- /dev/null +++ b/aws-node-http-api-dynamodb/README.md @@ -0,0 +1,148 @@ + +# Serverless HTTP API + +This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. + +## Structure + +This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.js`. In each of these files there is exactly one function which is directly attached to `module.exports`. + +The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. + +## Use-cases + +- API for a Web Application +- API for a Mobile Application + +## Setup + +```bash +npm install +``` + +## Deploy + +In order to deploy the endpoint simply run + +```bash +serverless deploy +``` + +The expected result should be similar to: + +```bash +Serverless: Packaging service… +Serverless: Uploading CloudFormation file to S3… +Serverless: Uploading service .zip file to S3… +Serverless: Updating Stack… +Serverless: Checking Stack update progress… +Serverless: Stack update finished… + +Service Information +service: serverless-http-api-dynamodb +stage: dev +region: us-east-1 +api keys: + None +endpoints: + POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} +functions: + serverless-http-api-dynamodb-dev-update: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-update + serverless-http-api-dynamodb-dev-get: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-get + serverless-http-api-dynamodb-dev-list: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-list + serverless-http-api-dynamodb-dev-create: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-create + serverless-http-api-dynamodb-dev-delete: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-delete +``` + +## Usage + +You can create, retrieve, update, or delete todos with the following commands: + +### Create a Todo + +```bash +curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### List all Todos + +```bash +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos +``` + +Example output: +```bash +[{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% +``` + +### Get one Todo + +```bash +# Replace the part with a real id from your todos table +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### Update a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% +``` + +### Delete a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +No output + +## Scaling + +### AWS Lambda + +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). + +### DynamoDB + +When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. + +This is can be done via settings in the `serverless.yml`. + +```yaml + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 +``` + +In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) diff --git a/aws-node-http-api-dynamodb/package.json b/aws-node-http-api-dynamodb/package.json new file mode 100644 index 000000000..55d554043 --- /dev/null +++ b/aws-node-http-api-dynamodb/package.json @@ -0,0 +1,10 @@ +{ + "name": "serverless-http-api-dynamodb", + "version": "1.0.0", + "description": "Serverless CRUD service exposing a REST HTTP interface", + "author": "", + "license": "MIT", + "dependencies": { + "uuid": "^2.0.3" + } +} diff --git a/aws-node-http-api-dynamodb/serverless.yml b/aws-node-http-api-dynamodb/serverless.yml new file mode 100644 index 000000000..d30d42ca0 --- /dev/null +++ b/aws-node-http-api-dynamodb/serverless.yml @@ -0,0 +1,76 @@ +service: serverless-http-api-dynamodb +frameworkVersion: '2' + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: '20201221' + environment: + DYNAMODB_TABLE: ${self:service}-${sls:stage} + httpApi: + cors: true + iam: + role: + statements: + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:Scan + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" + +functions: + create: + handler: todos/create.create + events: + - httpApi: + path: /todos + method: post + + list: + handler: todos/list.list + events: + - httpApi: + path: /todos + method: get + + get: + handler: todos/get.get + events: + - httpApi: + path: /todos/{id} + method: get + + update: + handler: todos/update.update + events: + - httpApi: + path: /todos/{id} + method: put + + delete: + handler: todos/delete.delete + events: + - httpApi: + path: /todos/{id} + method: delete + +resources: + Resources: + TodosDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + Properties: + AttributeDefinitions: + - + AttributeName: id + AttributeType: S + KeySchema: + - + AttributeName: id + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: ${self:provider.environment.DYNAMODB_TABLE} diff --git a/aws-node-http-api-dynamodb/todos/create.js b/aws-node-http-api-dynamodb/todos/create.js new file mode 100644 index 000000000..81ed8df9a --- /dev/null +++ b/aws-node-http-api-dynamodb/todos/create.js @@ -0,0 +1,52 @@ +'use strict'; + +const uuid = require('uuid'); +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +module.exports.create = (event, context, callback) => { + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + if (typeof data.text !== 'string') { + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the todo item.', + }); + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Item: { + id: uuid.v1(), + text: data.text, + checked: false, + createdAt: timestamp, + updatedAt: timestamp, + }, + }; + + // write the todo to the database + dynamoDb.put(params, (error) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(params.Item), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb/todos/delete.js b/aws-node-http-api-dynamodb/todos/delete.js new file mode 100644 index 000000000..4192ac737 --- /dev/null +++ b/aws-node-http-api-dynamodb/todos/delete.js @@ -0,0 +1,35 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +module.exports.delete = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + }; + + // delete the todo from the database + dynamoDb.delete(params, (error) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify({}), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb/todos/get.js b/aws-node-http-api-dynamodb/todos/get.js new file mode 100644 index 000000000..4cc34d307 --- /dev/null +++ b/aws-node-http-api-dynamodb/todos/get.js @@ -0,0 +1,35 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +module.exports.get = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + }; + + // fetch todo from the database + dynamoDb.get(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Item), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb/todos/list.js b/aws-node-http-api-dynamodb/todos/list.js new file mode 100644 index 000000000..5818246cd --- /dev/null +++ b/aws-node-http-api-dynamodb/todos/list.js @@ -0,0 +1,31 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); +const params = { + TableName: process.env.DYNAMODB_TABLE, +}; + +module.exports.list = (event, context, callback) => { + // fetch all todos from the database + dynamoDb.scan(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Items), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-dynamodb/todos/update.js b/aws-node-http-api-dynamodb/todos/update.js new file mode 100644 index 000000000..c852c6202 --- /dev/null +++ b/aws-node-http-api-dynamodb/todos/update.js @@ -0,0 +1,59 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +module.exports.update = (event, context, callback) => { + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + + // validation + if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t update the todo item.', + }); + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + ExpressionAttributeNames: { + '#todo_text': 'text', + }, + ExpressionAttributeValues: { + ':text': data.text, + ':checked': data.checked, + ':updatedAt': timestamp, + }, + UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', + ReturnValues: 'ALL_NEW', + }; + + // update the todo in the database + dynamoDb.update(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Attributes), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-mongodb/.gitignore b/aws-node-http-api-mongodb/.gitignore new file mode 100644 index 000000000..e0f039d9d --- /dev/null +++ b/aws-node-http-api-mongodb/.gitignore @@ -0,0 +1,2 @@ +.serverless +node_modules diff --git a/aws-node-http-api-mongodb/README.md b/aws-node-http-api-mongodb/README.md new file mode 100644 index 000000000..94ed4b780 --- /dev/null +++ b/aws-node-http-api-mongodb/README.md @@ -0,0 +1,89 @@ + + +# Serverless MongoDB HTTP API with Mongoose and Bluebird Promises + +This example demonstrate how to use a MongoDB database with aws and serverless. + +Using Mongoose ODM and Bluebird for Promises. + +## Use Cases + +- NoSQL CRUD API + +## Setup + +``` +npm install +serverless deploy +``` + +## Usage + +In `handler.js` update the `mongoString` with your mongoDB url. + +*Create* + +```bash +curl -XPOST -H "Content-type: application/json" -d '{ + "name" : "John", + "firstname" : "Doe", + "city" : "Toronto", + "birth" : "01/01/1990" +}' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/' +``` +```json +{"id": "590b52ff086041000142cedd"} +``` + +*READ* + +```bash +curl -XGET -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' +``` +```json +[ + { + "_id": "5905e2fbdb55f20001334b3e", + "name": "John", + "firstname": "Doe", + "birth": null, + "city": "Toronto", + "ip": "01/01/1990", + "__v": 0 + } +] +``` + +*UPDATE* + +```bash +curl -XPUT -H "Content-type: application/json" -d '{ + "name" : "William", + "firstname" : "Smith", + "city" : "Miami", + "birth" : "01/01/2000" +}' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' +``` +```json +"Ok" +``` + +*DELETE* + +```bash +curl -XDELETE -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' +``` + +```json +"Ok" +``` diff --git a/aws-node-http-api-mongodb/handler.js b/aws-node-http-api-mongodb/handler.js new file mode 100644 index 000000000..6646222f0 --- /dev/null +++ b/aws-node-http-api-mongodb/handler.js @@ -0,0 +1,106 @@ +const mongoose = require('mongoose'); +const Promise = require('bluebird'); +const validator = require('validator'); +const UserModel = require('./model/User.js'); + +mongoose.Promise = Promise; + +const mongoString = ''; // MongoDB Url + +const createErrorResponse = (statusCode, message) => ({ + statusCode: statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: message || 'Incorrect id', +}); + +const dbExecute = (db, fn) => db.then(fn).finally(() => db.close()); + +function dbConnectAndExecute(dbUrl, fn) { + return dbExecute(mongoose.connect(dbUrl, { useMongoClient: true }), fn); +} + +module.exports.user = (event, context, callback) => { + if (!validator.isAlphanumeric(event.pathParameters.id)) { + callback(null, createErrorResponse(400, 'Incorrect id')); + return; + } + + dbConnectAndExecute(mongoString, () => ( + UserModel + .find({ _id: event.pathParameters.id }) + .then(user => callback(null, { statusCode: 200, body: JSON.stringify(user) })) + .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) + )); +}; + + +module.exports.createUser = (event, context, callback) => { + const data = JSON.parse(event.body); + + const user = new UserModel({ + name: data.name, + firstname: data.firstname, + birth: data.birth, + city: data.city, + ip: event.requestContext.identity.sourceIp, + }); + + if (user.validateSync()) { + callback(null, createErrorResponse(400, 'Incorrect user data')); + return; + } + + dbConnectAndExecute(mongoString, () => ( + user + .save() + .then(() => callback(null, { + statusCode: 200, + body: JSON.stringify({ id: user.id }), + })) + .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) + )); +}; + +module.exports.deleteUser = (event, context, callback) => { + if (!validator.isAlphanumeric(event.pathParameters.id)) { + callback(null, createErrorResponse(400, 'Incorrect id')); + return; + } + + dbConnectAndExecute(mongoString, () => ( + UserModel + .remove({ _id: event.pathParameters.id }) + .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) + .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) + )); +}; + +module.exports.updateUser = (event, context, callback) => { + const data = JSON.parse(event.body); + const id = event.pathParameters.id; + + if (!validator.isAlphanumeric(id)) { + callback(null, createErrorResponse(400, 'Incorrect id')); + return; + } + + const user = new UserModel({ + _id: id, + name: data.name, + firstname: data.firstname, + birth: data.birth, + city: data.city, + ip: event.requestContext.identity.sourceIp, + }); + + if (user.validateSync()) { + callback(null, createErrorResponse(400, 'Incorrect parameter')); + return; + } + + dbConnectAndExecute(mongoString, () => ( + UserModel.findByIdAndUpdate(id, user) + .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) + .catch(err => callback(err, createErrorResponse(err.statusCode, err.message))) + )); +}; diff --git a/aws-node-http-api-mongodb/model/User.js b/aws-node-http-api-mongodb/model/User.js new file mode 100644 index 000000000..9970fa73a --- /dev/null +++ b/aws-node-http-api-mongodb/model/User.js @@ -0,0 +1,47 @@ +const mongoose = require('mongoose'); +const validator = require('validator'); + +const model = mongoose.model('User', { + name: { + type: String, + required: true, + validate: { + validator(name) { + return validator.isAlphanumeric(name); + }, + }, + }, + firstname: { + type: String, + required: true, + validate: { + validator(firstname) { + return validator.isAlphanumeric(firstname); + }, + }, + }, + birth: { + type: Date, + required: true, + }, + city: { + type: String, + required: true, + validate: { + validator(city) { + return validator.isAlphanumeric(city); + }, + }, + }, + ip: { + type: String, + required: true, + validate: { + validator(ip) { + return validator.isIP(ip); + }, + }, + }, +}); + +module.exports = model; diff --git a/aws-node-http-api-mongodb/package.json b/aws-node-http-api-mongodb/package.json new file mode 100644 index 000000000..d05af2b36 --- /dev/null +++ b/aws-node-http-api-mongodb/package.json @@ -0,0 +1,13 @@ +{ + "name": "serverless-http-api-mongodb", + "version": "1.0.0", + "description": "Serverless HTTP API with MongoDB using Mongoose and Bluebird", + "main": "handler.js", + "dependencies": { + "bluebird": "^3.5.0", + "mongoose": "^4.9.6", + "validator": "^7.0.0" + }, + "author": "Quentin Homareau ", + "license": "MIT" +} diff --git a/aws-node-http-api-mongodb/serverless.yml b/aws-node-http-api-mongodb/serverless.yml new file mode 100644 index 000000000..32eb2640a --- /dev/null +++ b/aws-node-http-api-mongodb/serverless.yml @@ -0,0 +1,38 @@ +service: serverless-http-api-mongodb +frameworkVersion: '2' + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: 20201221 + httpApi: + cors: true + +functions: + createUser: + handler: handler.createUser + events: + - httpApi: + path: /user + method: post + + updateUser: + handler: handler.updateUser + events: + - httpApi: + path: /user/{id} + method: put + + deleteUser: + handler: handler.deleteUser + events: + - httpApi: + path: /user/{id} + method: delete + + user: + handler: handler.user + events: + - httpApi: + path: /user/{id} + method: get diff --git a/aws-node-http-api-typescript-dynamodb/.gitignore b/aws-node-http-api-typescript-dynamodb/.gitignore new file mode 100644 index 000000000..a6c7c2852 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/aws-node-http-api-typescript-dynamodb/README.md b/aws-node-http-api-typescript-dynamodb/README.md new file mode 100644 index 000000000..799019fca --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/README.md @@ -0,0 +1,80 @@ + + +# Introduction + +TypeScript (ts) offers type safety which is helpful when working with the AWS SDK, which comes with ts definitions (d.ts) + +# compiling + +You can compile the ts files in this directory by 1st installing typescript via + +`npm install -g typescript` + +then + +`npm i` + +You can then run the compiler by running `tsc` in this directory. It will pull the settings from .tsconfig and extra @types +from package.json. The output create.js file is what will be uploaded by serverless. + +For brevity, I have just demonstrated this to match with the todos/create.js, todos/list.js, todos/get.js and todos/update.js lambda function + +## Usage + +You can create, retrieve, update, or delete todos with the following commands: + +### Create a Todo + +```bash +curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### List all Todos + +```bash +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos +``` + +Example output: +```bash +[{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% +``` + +### Get one Todo + +```bash +# Replace the part with a real id from your todos table +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### Update a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% +``` diff --git a/aws-node-http-api-typescript-dynamodb/package.json b/aws-node-http-api-typescript-dynamodb/package.json new file mode 100644 index 000000000..80ca85df5 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/package.json @@ -0,0 +1,24 @@ +{ + "name": "serverless-http-api-typescript-dynamodb", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "tsc -w", + "lint": "tslint '*.ts'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/aws-sdk": "0.0.42", + "@types/node": "^7.0.5", + "tslint": "^5.5.0", + "tslint-config-standard": "^6.0.1", + "typescript": "^2.4.2" + }, + "dependencies": { + "uuid": "^3.1.0" + } +} diff --git a/aws-node-http-api-typescript-dynamodb/serverless.yml b/aws-node-http-api-typescript-dynamodb/serverless.yml new file mode 100644 index 000000000..590a512bb --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/serverless.yml @@ -0,0 +1,69 @@ +service: serverless-http-api-typescript-dynamodb +frameworkVersion: '2' + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: '20201221' + environment: + DYNAMODB_TABLE: ${self:service}-${sls:stage} + httpApi: + cors: true + iam: + role: + statements: + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:Scan + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" + +functions: + create: + handler: todos/create.create + events: + - httpApi: + path: /todos + method: post + + list: + handler: todos/list.list + events: + - httpApi: + path: /todos + method: get + + get: + handler: todos/get.get + events: + - httpApi: + path: /todos/{id} + method: get + + update: + handler: todos/update.update + events: + - httpApi: + path: /todos/{id} + method: put + +resources: + Resources: + TodosDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + Properties: + AttributeDefinitions: + - + AttributeName: id + AttributeType: S + KeySchema: + - + AttributeName: id + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: ${self:provider.environment.DYNAMODB_TABLE} diff --git a/aws-node-http-api-typescript-dynamodb/todos/create.ts b/aws-node-http-api-typescript-dynamodb/todos/create.ts new file mode 100644 index 000000000..f86d01cfc --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/todos/create.ts @@ -0,0 +1,45 @@ +'use strict' + +import * as uuid from 'uuid' + +import { DynamoDB } from 'aws-sdk' + +const dynamoDb = new DynamoDB.DocumentClient() + +module.exports.create = (event, context, callback) => { + const timestamp = new Date().getTime() + const data = JSON.parse(event.body) + if (typeof data.text !== 'string') { + console.error('Validation Failed') + callback(new Error('Couldn\'t create the todo item.')) + return + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Item: { + id: uuid.v1(), + text: data.text, + checked: false, + createdAt: timestamp, + updatedAt: timestamp + } + } + + // write the todo to the database + dynamoDb.put(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error) + callback(new Error('Couldn\'t create the todo item.')) + return + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(params.Item) + } + callback(null, response) + }) +} diff --git a/aws-node-http-api-typescript-dynamodb/todos/get.ts b/aws-node-http-api-typescript-dynamodb/todos/get.ts new file mode 100644 index 000000000..fa00764b7 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/todos/get.ts @@ -0,0 +1,36 @@ +'use strict'; + +import { DynamoDB } from 'aws-sdk' + +const dynamoDb = new DynamoDB.DocumentClient() + + +module.exports.get = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + }; + + // fetch todo from the database + dynamoDb.get(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Item), + }; + callback(null, response); + }); +}; \ No newline at end of file diff --git a/aws-node-http-api-typescript-dynamodb/todos/list.ts b/aws-node-http-api-typescript-dynamodb/todos/list.ts new file mode 100644 index 000000000..7b3ea316e --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/todos/list.ts @@ -0,0 +1,32 @@ +'use strict'; + +import { DynamoDB } from 'aws-sdk' + +const dynamoDb = new DynamoDB.DocumentClient() +const params = { + TableName: process.env.DYNAMODB_TABLE, +}; + +module.exports.list = (event, context, callback) => { + // fetch all todos from the database + // For production workloads you should design your tables and indexes so that your applications can use Query instead of Scan. + dynamoDb.scan(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo items.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Items), + }; + callback(null, response); + }); +}; diff --git a/aws-node-http-api-typescript-dynamodb/todos/update.ts b/aws-node-http-api-typescript-dynamodb/todos/update.ts new file mode 100644 index 000000000..8b3788141 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/todos/update.ts @@ -0,0 +1,59 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies + +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +module.exports.update = (event, context, callback) => { + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + + // validation + if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t update the todo item.', + }); + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + ExpressionAttributeNames: { + '#todo_text': 'text', + }, + ExpressionAttributeValues: { + ':text': data.text, + ':checked': data.checked, + ':updatedAt': timestamp, + }, + UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', + ReturnValues: 'ALL_NEW', + }; + + // update the todo in the database + dynamoDb.update(params, (error, result) => { + // handle potential errors + if (error) { + console.error(error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todo item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Attributes), + }; + callback(null, response); + }); +}; \ No newline at end of file diff --git a/aws-node-http-api-typescript-dynamodb/tsconfig.json b/aws-node-http-api-typescript-dynamodb/tsconfig.json new file mode 100644 index 000000000..b61445848 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs" + }, + "exclude": [ + "node_modules" + ], + "types": [ + "node", + "aws-sdk" + ] +} diff --git a/aws-node-http-api-typescript-dynamodb/tslint.json b/aws-node-http-api-typescript-dynamodb/tslint.json new file mode 100644 index 000000000..888779856 --- /dev/null +++ b/aws-node-http-api-typescript-dynamodb/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "tslint-config-standard" +} diff --git a/aws-node-http-api-typescript/.gitignore b/aws-node-http-api-typescript/.gitignore new file mode 100644 index 000000000..2b48c8bd5 --- /dev/null +++ b/aws-node-http-api-typescript/.gitignore @@ -0,0 +1,6 @@ +# package directories +node_modules +jspm_packages + +# Serverless directories +.serverless \ No newline at end of file diff --git a/aws-node-http-api-typescript/README.md b/aws-node-http-api-typescript/README.md new file mode 100644 index 000000000..b869893f1 --- /dev/null +++ b/aws-node-http-api-typescript/README.md @@ -0,0 +1,45 @@ + + +# Serverless Framework Node with Typescript HTTP API on AWS + +This template demonstrates how to make a simple HTTP API with Node.js and Typescript running on AWS Lambda and API Gateway using the Serverless Framework v1. + +This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples) which includes Typescript, Mongo, DynamoDB and other examples. + +## Setup + +Run this command to initialize a new project in a new working directory. + +``` +npm install +``` + +## Usage + +**Deploy** + +``` +$ serverless deploy +``` + +**Invoke the function locally.** + +``` +serverless invoke local --function hello +``` + +**Invoke the function** + +``` +curl https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/ +``` diff --git a/aws-node-http-api-typescript/handler.ts b/aws-node-http-api-typescript/handler.ts new file mode 100644 index 000000000..0d2f0e05a --- /dev/null +++ b/aws-node-http-api-typescript/handler.ts @@ -0,0 +1,19 @@ +import { Handler } from 'aws-lambda'; + +export const hello: Handler = (event: any) => { + const response = { + statusCode: 200, + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), + }; + + return new Promise((resolve) => { + resolve(response) + }) +} \ No newline at end of file diff --git a/aws-node-http-api-typescript/package.json b/aws-node-http-api-typescript/package.json new file mode 100644 index 000000000..2e859125a --- /dev/null +++ b/aws-node-http-api-typescript/package.json @@ -0,0 +1,13 @@ +{ + "name": "serverless-http-api-typescript", + "description": "", + "version": "0.1.0", + "dependencies": {}, + "devDependencies": { + "@types/aws-lambda": "^8.10.61", + "serverless": "^1.78.1", + "serverless-offline": "^6.5.0", + "serverless-plugin-typescript": "^1.1.9", + "typescript": "^3.9.7" + } +} diff --git a/aws-node-http-api-typescript/serverless.yml b/aws-node-http-api-typescript/serverless.yml new file mode 100644 index 000000000..8be87264f --- /dev/null +++ b/aws-node-http-api-typescript/serverless.yml @@ -0,0 +1,18 @@ +service: serverless-http-api-typescript +frameworkVersion: '2' + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: '20201221' + +functions: + hello: + handler: handler.hello + events: + - httpApi: + path: / + method: get + +plugins: + - serverless-plugin-typescript diff --git a/aws-node-http-api/.gitignore b/aws-node-http-api/.gitignore new file mode 100644 index 000000000..2b48c8bd5 --- /dev/null +++ b/aws-node-http-api/.gitignore @@ -0,0 +1,6 @@ +# package directories +node_modules +jspm_packages + +# Serverless directories +.serverless \ No newline at end of file diff --git a/aws-node-http-api/README.md b/aws-node-http-api/README.md new file mode 100644 index 000000000..23c71d6dc --- /dev/null +++ b/aws-node-http-api/README.md @@ -0,0 +1,113 @@ + + +# Serverless Framework Node HTTP API on AWS + +This template demonstrates how to make a simple HTTP API with Node.js running on AWS Lambda and API Gateway using the Serverless Framework. + +This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples/) which includes Typescript, Mongo, DynamoDB and other examples. + +## Usage + +### Deployment + +``` +$ serverless deploy +``` + +After deploying, you should see output similar to: + +```bash +Serverless: Packaging service... +Serverless: Excluding development dependencies... +Serverless: Creating Stack... +Serverless: Checking Stack create progress... +........ +Serverless: Stack create finished... +Serverless: Uploading CloudFormation file to S3... +Serverless: Uploading artifacts... +Serverless: Uploading service aws-node-http-api.zip file to S3 (711.23 KB)... +Serverless: Validating template... +Serverless: Updating Stack... +Serverless: Checking Stack update progress... +................................. +Serverless: Stack update finished... +Service Information +service: serverless-http-api +stage: dev +region: us-east-1 +stack: serverless-http-api-dev +resources: 12 +api keys: + None +endpoints: + ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ +functions: + api: serverless-http-api-dev-hello +layers: + None +``` + +_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). + +### Invocation + +After successful deployment, you can call the created application via HTTP: + +```bash +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ +``` + +Which should result in response similar to the following (removed `input` content for brevity): + +```json +{ + "message": "Go Serverless v2.0! Your function executed successfully!", + "input": { + ... + } +} +``` + +### Local development + +You can invoke your function locally by using the following command: + +```bash +serverless invoke local --function hello +``` + +Which should result in response similar to the following: + +``` +{ + "statusCode": 200, + "body": "{\n \"message\": \"Go Serverless v2.0! Your function executed successfully!\",\n \"input\": \"\"\n}" +} +``` + + +Alternatively, it is also possible to emulate API Gateway and Lambda locally by using `serverless-offline` plugin. In order to do that, execute the following command: + +```bash +serverless plugin install -n serverless-offline +``` + +It will add the `serverless-offline` plugin to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. + +After installation, you can start local emulation with: + +``` +serverless offline +``` + +To learn more about the capabilities of `serverless-offline`, please refer to its [GitHub repository](https://github.com/dherault/serverless-offline). diff --git a/aws-node-http-api/handler.js b/aws-node-http-api/handler.js new file mode 100644 index 000000000..69bddf913 --- /dev/null +++ b/aws-node-http-api/handler.js @@ -0,0 +1,15 @@ +"use strict"; + +module.exports.hello = async (event) => { + return { + statusCode: 200, + body: JSON.stringify( + { + message: "Go Serverless v2.0! Your function executed successfully!", + input: event, + }, + null, + 2 + ), + }; +}; diff --git a/aws-node-http-api/serverless.template.yml b/aws-node-http-api/serverless.template.yml new file mode 100644 index 000000000..e6dd8879c --- /dev/null +++ b/aws-node-http-api/serverless.template.yml @@ -0,0 +1,6 @@ +name: aws-node-http-api +org: serverlessinc +description: Deploys a Node HTTP API service with traditional Serverless Framework +keywords: aws, serverless, faas, lambda, node +repo: https://github.com/serverless/examples/aws-node-http-api +license: MIT diff --git a/aws-node-http-api/serverless.yml b/aws-node-http-api/serverless.yml new file mode 100644 index 000000000..808b06bf4 --- /dev/null +++ b/aws-node-http-api/serverless.yml @@ -0,0 +1,15 @@ +service: serverless-http-api +frameworkVersion: '2' + +provider: + name: aws + runtime: nodejs12.x + lambdaHashingVersion: '20201221' + +functions: + hello: + handler: handler.hello + events: + - httpApi: + path: / + method: get diff --git a/aws-node-simple-http-endpoint/README.md b/aws-node-simple-http-endpoint/README.md index 7e631256d..2a59a60ca 100644 --- a/aws-node-simple-http-endpoint/README.md +++ b/aws-node-simple-http-endpoint/README.md @@ -61,7 +61,7 @@ region: us-east-1 api keys: None endpoints: - GET - https://2e16njizla.execute-api.us-east-1.amazonaws.com/dev/ping + GET - https://2e16njizla.execute-api.us-east-1.amazonaws.com/ping functions: serverless-simple-http-endpoint-dev-currentTime: arn:aws:lambda:us-east-1:488110005556:function:serverless-simple-http-endpoint-dev-currentTime ``` @@ -77,9 +77,9 @@ serverless invoke --function currentTime --log or as send an HTTP request directly to the endpoint using a tool like curl ```bash -curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/ping +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/ping ``` ## Scaling -By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). diff --git a/aws-node-simple-http-endpoint/serverless.yml b/aws-node-simple-http-endpoint/serverless.yml index 73f33fd77..f370158d3 100644 --- a/aws-node-simple-http-endpoint/serverless.yml +++ b/aws-node-simple-http-endpoint/serverless.yml @@ -1,6 +1,5 @@ service: serverless-simple-http-endpoint - -frameworkVersion: ">=1.1.0 <2.0.0" +frameworkVersion: '2' provider: name: aws @@ -10,6 +9,6 @@ functions: currentTime: handler: handler.endpoint events: - - http: - path: ping + - httpApi: + path: /ping method: get diff --git a/aws-node-sqs-worker/README.md b/aws-node-sqs-worker/README.md index b9cdb3266..ae30e8a5f 100644 --- a/aws-node-sqs-worker/README.md +++ b/aws-node-sqs-worker/README.md @@ -28,15 +28,13 @@ To learn more: ### Deployment -This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. - -In order to deploy with dashboard, you need to first login with: +Install dependencies with: ``` -serverless login +npm install ``` -and then perform deployment with: +Then deploy: ``` serverless deploy @@ -68,14 +66,14 @@ resources: 17 api keys: None endpoints: - POST - https://xxxx.execute-api.us-east-1.amazonaws.com/dev/produce + POST - https://xxxx.execute-api.us-east-1.amazonaws.com/produce functions: producer: aws-node-sqs-worker-dev-producer jobsWorker: aws-node-sqs-worker-dev-jobsWorker layers: None jobs: - queueUrl: https://sqs.us-east-1.amazonaws.com/xxxxxx/aws-node-sqs-worker-dev-jobs + queueUrl: https://sqs.us-east-1.amazonaws.com/xxxx/aws-node-sqs-worker-dev-jobs ``` @@ -86,7 +84,7 @@ _Note_: In current form, after deployment, your API is public and can be invoked After successful deployment, you can now call the created API endpoint with `POST` request to invoke `producer` function: ```bash -curl --request POST 'https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' +curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' ``` In response, you should see output similar to: diff --git a/aws-node-sqs-worker/package.json b/aws-node-sqs-worker/package.json index 247158080..8398f017c 100644 --- a/aws-node-sqs-worker/package.json +++ b/aws-node-sqs-worker/package.json @@ -4,7 +4,7 @@ "description": "Serverless Framework Node SQS Producer-Consumer on AWS", "author": "", "license": "MIT", - "dependencies": { + "devDependencies": { "serverless-lift": "^1.1.2" } } diff --git a/aws-node-sqs-worker/serverless.yml b/aws-node-sqs-worker/serverless.yml index f611de53d..61d17bca3 100644 --- a/aws-node-sqs-worker/serverless.yml +++ b/aws-node-sqs-worker/serverless.yml @@ -1,13 +1,10 @@ service: aws-node-sqs-worker - frameworkVersion: '2' - provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' - stage: dev constructs: jobs: @@ -19,9 +16,9 @@ functions: producer: handler: handler.producer events: - - http: + - httpApi: method: post - path: produce + path: /produce environment: QUEUE_URL: ${construct:jobs.queueUrl} diff --git a/aws-python-http-api-with-dynamodb/.gitignore b/aws-python-http-api-with-dynamodb/.gitignore new file mode 100644 index 000000000..94fcd47ab --- /dev/null +++ b/aws-python-http-api-with-dynamodb/.gitignore @@ -0,0 +1,3 @@ +.serverless +*.pyc +*.pyo diff --git a/aws-python-http-api-with-dynamodb/README.md b/aws-python-http-api-with-dynamodb/README.md new file mode 100644 index 000000000..234476fa7 --- /dev/null +++ b/aws-python-http-api-with-dynamodb/README.md @@ -0,0 +1,145 @@ + +# Serverless HTTP API + +This example demonstrates how to setup an HTTP API allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. + +## Structure + +This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. + +The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. + +## Use-cases + +- API for a Web Application +- API for a Mobile Application + +## Setup + +```bash +npm install -g serverless +``` + +## Deploy + +In order to deploy the endpoint simply run + +```bash +serverless deploy +``` + +The expected result should be similar to: + +```bash +Serverless: Packaging service… +Serverless: Uploading CloudFormation file to S3… +Serverless: Uploading service .zip file to S3… +Serverless: Updating Stack… +Serverless: Checking Stack update progress… +Serverless: Stack update finished… + +Service Information +service: serverless-http-api-dynamodb +stage: dev +region: us-east-1 +api keys: + None +endpoints: + POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} +functions: + update: serverless-http-api-dynamodb-dev-update + get: serverless-http-api-dynamodb-dev-get + list: serverless-http-api-dynamodb-dev-list + create: serverless-http-api-dynamodb-dev-create + delete: serverless-http-api-dynamodb-dev-delete +``` + +## Usage + +You can create, retrieve, update, or delete todos with the following commands: + +### Create a Todo + +```bash +curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' -H "Content-Type: application/json" +``` + +No output + +### List all Todos + +```bash +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos +``` + +Example output: +```bash +[{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% +``` + +### Get one Todo + +```bash +# Replace the part with a real id from your todos table +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### Update a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' -H "Content-Type: application/json" +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% +``` + +### Delete a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +No output + +## Scaling + +### AWS Lambda + +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). + +### DynamoDB + +When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. + +This is can be done via settings in the `serverless.yml`. + +```yaml + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 +``` + +In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) diff --git a/aws-python-http-api-with-dynamodb/package.json b/aws-python-http-api-with-dynamodb/package.json new file mode 100644 index 000000000..bbf8213ca --- /dev/null +++ b/aws-python-http-api-with-dynamodb/package.json @@ -0,0 +1,7 @@ +{ + "name": "aws-http-with-dynamodb", + "version": "1.0.0", + "description": "Serverless HTTP API", + "author": "", + "license": "MIT" +} diff --git a/aws-python-http-api-with-dynamodb/serverless.yml b/aws-python-http-api-with-dynamodb/serverless.yml new file mode 100644 index 000000000..388a5066d --- /dev/null +++ b/aws-python-http-api-with-dynamodb/serverless.yml @@ -0,0 +1,75 @@ +service: serverless-http-api-dynamodb +frameworkVersion: '2' + +provider: + name: aws + runtime: python3.8 + environment: + DYNAMODB_TABLE: ${self:service}-${sls:stage} + httpApi: + cors: true + iam: + role: + statements: + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:Scan + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" + +functions: + create: + handler: todos/create.create + events: + - httpApi: + path: /todos + method: post + + list: + handler: todos/list.list + events: + - httpApi: + path: /todos + method: get + + get: + handler: todos/get.get + events: + - httpApi: + path: /todos/{id} + method: get + + update: + handler: todos/update.update + events: + - httpApi: + path: /todos/{id} + method: put + + delete: + handler: todos/delete.delete + events: + - httpApi: + path: /todos/{id} + method: delete + +resources: + Resources: + TodosDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + Properties: + AttributeDefinitions: + - + AttributeName: id + AttributeType: S + KeySchema: + - + AttributeName: id + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: ${self:provider.environment.DYNAMODB_TABLE} diff --git a/aws-python-http-api-with-dynamodb/todos/__init__.py b/aws-python-http-api-with-dynamodb/todos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws-python-http-api-with-dynamodb/todos/create.py b/aws-python-http-api-with-dynamodb/todos/create.py new file mode 100644 index 000000000..4fcece062 --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/create.py @@ -0,0 +1,38 @@ +import json +import logging +import os +import time +import uuid + +import boto3 +dynamodb = boto3.resource('dynamodb') + + +def create(event, context): + data = json.loads(event['body']) + if 'text' not in data: + logging.error("Validation Failed") + raise Exception("Couldn't create the todo item.") + + timestamp = str(time.time()) + + table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) + + item = { + 'id': str(uuid.uuid1()), + 'text': data['text'], + 'checked': False, + 'createdAt': timestamp, + 'updatedAt': timestamp, + } + + # write the todo to the database + table.put_item(Item=item) + + # create a response + response = { + "statusCode": 200, + "body": json.dumps(item) + } + + return response diff --git a/aws-python-http-api-with-dynamodb/todos/decimalencoder.py b/aws-python-http-api-with-dynamodb/todos/decimalencoder.py new file mode 100644 index 000000000..192aacfec --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/decimalencoder.py @@ -0,0 +1,10 @@ +import decimal +import json + + +# This is a workaround for: http://bugs.python.org/issue16535 +class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, decimal.Decimal): + return int(obj) + return super(DecimalEncoder, self).default(obj) diff --git a/aws-python-http-api-with-dynamodb/todos/delete.py b/aws-python-http-api-with-dynamodb/todos/delete.py new file mode 100644 index 000000000..cb299f11f --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/delete.py @@ -0,0 +1,22 @@ +import os + +import boto3 +dynamodb = boto3.resource('dynamodb') + + +def delete(event, context): + table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) + + # delete the todo from the database + table.delete_item( + Key={ + 'id': event['pathParameters']['id'] + } + ) + + # create a response + response = { + "statusCode": 200 + } + + return response diff --git a/aws-python-http-api-with-dynamodb/todos/get.py b/aws-python-http-api-with-dynamodb/todos/get.py new file mode 100644 index 000000000..fd369a33f --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/get.py @@ -0,0 +1,26 @@ +import os +import json + +from todos import decimalencoder +import boto3 +dynamodb = boto3.resource('dynamodb') + + +def get(event, context): + table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) + + # fetch todo from the database + result = table.get_item( + Key={ + 'id': event['pathParameters']['id'] + } + ) + + # create a response + response = { + "statusCode": 200, + "body": json.dumps(result['Item'], + cls=decimalencoder.DecimalEncoder) + } + + return response diff --git a/aws-python-http-api-with-dynamodb/todos/list.py b/aws-python-http-api-with-dynamodb/todos/list.py new file mode 100644 index 000000000..b1332105f --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/list.py @@ -0,0 +1,21 @@ +import json +import os + +from todos import decimalencoder +import boto3 +dynamodb = boto3.resource('dynamodb') + + +def list(event, context): + table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) + + # fetch all todos from the database + result = table.scan() + + # create a response + response = { + "statusCode": 200, + "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder) + } + + return response diff --git a/aws-python-http-api-with-dynamodb/todos/update.py b/aws-python-http-api-with-dynamodb/todos/update.py new file mode 100644 index 000000000..a8a26de57 --- /dev/null +++ b/aws-python-http-api-with-dynamodb/todos/update.py @@ -0,0 +1,48 @@ +import json +import time +import logging +import os + +from todos import decimalencoder +import boto3 +dynamodb = boto3.resource('dynamodb') + + +def update(event, context): + data = json.loads(event['body']) + if 'text' not in data or 'checked' not in data: + logging.error("Validation Failed") + raise Exception("Couldn't update the todo item.") + return + + timestamp = int(time.time() * 1000) + + table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) + + # update the todo in the database + result = table.update_item( + Key={ + 'id': event['pathParameters']['id'] + }, + ExpressionAttributeNames={ + '#todo_text': 'text', + }, + ExpressionAttributeValues={ + ':text': data['text'], + ':checked': data['checked'], + ':updatedAt': timestamp, + }, + UpdateExpression='SET #todo_text = :text, ' + 'checked = :checked, ' + 'updatedAt = :updatedAt', + ReturnValues='ALL_NEW', + ) + + # create a response + response = { + "statusCode": 200, + "body": json.dumps(result['Attributes'], + cls=decimalencoder.DecimalEncoder) + } + + return response diff --git a/aws-python-http-api-with-pynamodb/.gitignore b/aws-python-http-api-with-pynamodb/.gitignore new file mode 100644 index 000000000..f95b67692 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/.gitignore @@ -0,0 +1,103 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Serverless +.serverless/ +.requirements diff --git a/aws-python-http-api-with-pynamodb/README.md b/aws-python-http-api-with-pynamodb/README.md new file mode 100644 index 000000000..bed826db6 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/README.md @@ -0,0 +1,145 @@ + +# Serverless HTTP API + +This example demonstrates how to setup an HTTP API allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. + +## Structure + +This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. + +The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. + +## Use-cases + +- API for a Web Application +- API for a Mobile Application + +## Setup + +```bash +npm install +``` + +## Deploy + +In order to deploy the endpoint simply run + +```bash +serverless deploy +``` + +The expected result should be similar to: + +```bash +Serverless: Packaging service… +Serverless: Uploading CloudFormation file to S3… +Serverless: Uploading service .zip file to S3… +Serverless: Updating Stack… +Serverless: Checking Stack update progress… +Serverless: Stack update finished… + +Service Information +service: serverless-http-api-pynamodb +stage: dev +region: us-east-1 +api keys: + None +endpoints: + POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos + GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} + DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} +functions: + update: serverless-http-api-pynamodb-dev-update + get: serverless-http-api-pynamodb-dev-get + list: serverless-http-api-pynamodb-dev-list + create: serverless-http-api-pynamodb-dev-create + delete: serverless-http-api-pynamodb-dev-delete +``` + +## Usage + +You can create, retrieve, update, or delete todos with the following commands: + +### Create a Todo + +```bash +curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' -H "Content-Type: application/json" +``` + +No output + +### List all Todos + +```bash +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos +``` + +Example output: +```bash +[{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% +``` + +### Get one Todo + +```bash +# Replace the part with a real id from your todos table +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% +``` + +### Update a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' -H "Content-Type: application/json" +``` + +Example Result: +```bash +{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% +``` + +### Delete a Todo + +```bash +# Replace the part with a real id from your todos table +curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ +``` + +No output + +## Scaling + +### AWS Lambda + +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). + +### DynamoDB + +When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. + +This is can be done via settings in the `serverless.yml`. + +```yaml + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 +``` + +In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) diff --git a/aws-python-http-api-with-pynamodb/package-lock.json b/aws-python-http-api-with-pynamodb/package-lock.json new file mode 100644 index 000000000..83d44a9e7 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/package-lock.json @@ -0,0 +1,1077 @@ +{ + "name": "aws-http-with-pynamodb", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "aws-http-with-pynamodb", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "serverless-python-requirements": "^5" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/appdirectory": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/appdirectory/-/appdirectory-0.1.0.tgz", + "integrity": "sha1-62yBYyDnsqsW9e2ZfyjYIF31Y3U=" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-all": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.2.1.tgz", + "integrity": "sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw==", + "dependencies": { + "glob": "^7.1.2", + "yargs": "^15.3.1" + }, + "bin": { + "glob-all": "bin/glob-all" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.0.tgz", + "integrity": "sha512-Y2OlFIzrDOPWUnpU0LORIcDn2xN7rC9yKffFM/7pGhQuhO+SUhfm2trkJ/S5amjFvem0Y+1EALz/MEPkvHXVNw==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=" + }, + "node_modules/lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/serverless-python-requirements": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/serverless-python-requirements/-/serverless-python-requirements-5.1.1.tgz", + "integrity": "sha512-frqZhQcqf3kLMpkS1U8VUqiRnbDG4C81eQndlZ150gSFkAqCc521LemedtWEWGrBQQoWSTo4LivugLiQU7s/Sg==", + "dependencies": { + "@iarna/toml": "^2.2.5", + "appdirectory": "^0.1.0", + "bluebird": "^3.7.2", + "fs-extra": "^9.1.0", + "glob-all": "^3.2.1", + "is-wsl": "^2.2.0", + "jszip": "^3.6.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniqby": "^4.7.0", + "lodash.values": "^4.3.0", + "rimraf": "^3.0.2", + "sha256-file": "1.0.0", + "shell-quote": "^1.7.2" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sha256-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sha256-file/-/sha256-file-1.0.0.tgz", + "integrity": "sha512-nqf+g0veqgQAkDx0U2y2Tn2KWyADuuludZTw9A7J3D+61rKlIIl9V5TS4mfnwKuXZOH9B7fQyjYJ9pKRHIsAyg==" + }, + "node_modules/shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "appdirectory": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/appdirectory/-/appdirectory-0.1.0.tgz", + "integrity": "sha1-62yBYyDnsqsW9e2ZfyjYIF31Y3U=" + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-all": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.2.1.tgz", + "integrity": "sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw==", + "requires": { + "glob": "^7.1.2", + "yargs": "^15.3.1" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jszip": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.0.tgz", + "integrity": "sha512-Y2OlFIzrDOPWUnpU0LORIcDn2xN7rC9yKffFM/7pGhQuhO+SUhfm2trkJ/S5amjFvem0Y+1EALz/MEPkvHXVNw==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=" + }, + "lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "serverless-python-requirements": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/serverless-python-requirements/-/serverless-python-requirements-5.1.1.tgz", + "integrity": "sha512-frqZhQcqf3kLMpkS1U8VUqiRnbDG4C81eQndlZ150gSFkAqCc521LemedtWEWGrBQQoWSTo4LivugLiQU7s/Sg==", + "requires": { + "@iarna/toml": "^2.2.5", + "appdirectory": "^0.1.0", + "bluebird": "^3.7.2", + "fs-extra": "^9.1.0", + "glob-all": "^3.2.1", + "is-wsl": "^2.2.0", + "jszip": "^3.6.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniqby": "^4.7.0", + "lodash.values": "^4.3.0", + "rimraf": "^3.0.2", + "sha256-file": "1.0.0", + "shell-quote": "^1.7.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "sha256-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sha256-file/-/sha256-file-1.0.0.tgz", + "integrity": "sha512-nqf+g0veqgQAkDx0U2y2Tn2KWyADuuludZTw9A7J3D+61rKlIIl9V5TS4mfnwKuXZOH9B7fQyjYJ9pKRHIsAyg==" + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/aws-python-http-api-with-pynamodb/package.json b/aws-python-http-api-with-pynamodb/package.json new file mode 100644 index 000000000..a4762418f --- /dev/null +++ b/aws-python-http-api-with-pynamodb/package.json @@ -0,0 +1,10 @@ +{ + "name": "aws-http-with-pynamodb", + "version": "1.0.0", + "description": "Serverless CRUD service exposing an HTTP API", + "author": "", + "license": "MIT", + "dependencies": { + "serverless-python-requirements": "^5" + } +} diff --git a/aws-python-http-api-with-pynamodb/requirements.txt b/aws-python-http-api-with-pynamodb/requirements.txt new file mode 100644 index 000000000..7f4069844 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/requirements.txt @@ -0,0 +1,3 @@ +pynamodb==4.3.1 +boto3 #no-deploy +botocore #no-deploy diff --git a/aws-python-http-api-with-pynamodb/serverless.yml b/aws-python-http-api-with-pynamodb/serverless.yml new file mode 100644 index 000000000..d7f005858 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/serverless.yml @@ -0,0 +1,90 @@ +service: serverless-http-api-pynamodb +frameworkVersion: '2' + +plugins: + - serverless-python-requirements + +package: + exclude: + - node_modules/** + - .idea/** + - .requirements/** + - env/** + - README.md + - package.json + - package-lock.json + - requirements.txt + +provider: + name: aws + runtime: python3.8 + environment: + DYNAMODB_TABLE: ${self:service}-${sls:stage} + httpApi: + cors: true + iam: + role: + statements: + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:Scan + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + - dynamodb:DescribeTable + Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" + +functions: + create: + handler: todos/create.create + events: + - httpApi: + path: /todos + method: post + + list: + handler: todos/list.todo_list + events: + - httpApi: + path: /todos + method: get + + get: + handler: todos/get.get + events: + - httpApi: + path: /todos/{id} + method: get + + update: + handler: todos/update.update + events: + - httpApi: + path: /todos/{todo_id} + method: put + + delete: + handler: todos/delete.delete + events: + - httpApi: + path: /todos/{todo_id} + method: delete + +resources: + Resources: + TodosDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + Properties: + AttributeDefinitions: + - + AttributeName: todo_id + AttributeType: S + KeySchema: + - + AttributeName: todo_id + KeyType: HASH + BillingMode: PAY_PER_REQUEST + TableName: ${self:provider.environment.DYNAMODB_TABLE} diff --git a/aws-python-http-api-with-pynamodb/todos/__init__.py b/aws-python-http-api-with-pynamodb/todos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws-python-http-api-with-pynamodb/todos/create.py b/aws-python-http-api-with-pynamodb/todos/create.py new file mode 100644 index 000000000..f75b2e14d --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/create.py @@ -0,0 +1,31 @@ +import json +import logging +import uuid + +from todos.todo_model import TodoModel + + +def create(event, context): + print(event['body']) + data = json.loads(event['body']) + if 'text' not in data: + logging.error('Validation Failed') + return {'statusCode': 422, + 'body': json.dumps({'error_message': 'Couldn\'t create the todo item.'})} + + if not data['text']: + logging.error('Validation Failed - text was empty. %s', data) + return {'statusCode': 422, + 'body': json.dumps({'error_message': 'Couldn\'t create the todo item. As text was empty.'})} + + a_todo = TodoModel(todo_id=str(uuid.uuid1()), + text=data['text'], + checked=False) + + # write the todo to the database + a_todo.save() + + # create a response + return {'statusCode': 201, + 'body': json.dumps(dict(a_todo))} + diff --git a/aws-python-http-api-with-pynamodb/todos/delete.py b/aws-python-http-api-with-pynamodb/todos/delete.py new file mode 100644 index 000000000..bfa66d76c --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/delete.py @@ -0,0 +1,20 @@ +import json + +from pynamodb.exceptions import DoesNotExist, DeleteError +from todos.todo_model import TodoModel + + +def delete(event, context): + try: + found_todo = TodoModel.get(hash_key=event['path']['todo_id']) + except DoesNotExist: + return {'statusCode': 404, + 'body': json.dumps({'error_message': 'TODO was not found'})} + try: + found_todo.delete() + except DeleteError: + return {'statusCode': 400, + 'body': json.dumps({'error_message': 'Unable to delete the TODO'})} + + # create a response + return {'statusCode': 204} diff --git a/aws-python-http-api-with-pynamodb/todos/get.py b/aws-python-http-api-with-pynamodb/todos/get.py new file mode 100644 index 000000000..8273c110e --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/get.py @@ -0,0 +1,16 @@ +import json + +from pynamodb.exceptions import DoesNotExist +from todos.todo_model import TodoModel + + +def get(event, context): + try: + found_todo = TodoModel.get(hash_key=event['path']['todo_id']) + except DoesNotExist: + return {'statusCode': 404, + 'body': json.dumps({'error_message': 'TODO was not found'})} + + # create a response + return {'statusCode': 200, + 'body': json.dumps(dict(found_todo))} diff --git a/aws-python-http-api-with-pynamodb/todos/list.py b/aws-python-http-api-with-pynamodb/todos/list.py new file mode 100644 index 000000000..ec52c4c90 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/list.py @@ -0,0 +1,12 @@ +import json + +from todos.todo_model import TodoModel + + +def todo_list(event, context): + # fetch all todos from the database + results = TodoModel.scan() + + # create a response + return {'statusCode': 200, + 'body': json.dumps({'items': [dict(result) for result in results]})} diff --git a/aws-python-http-api-with-pynamodb/todos/todo_model.py b/aws-python-http-api-with-pynamodb/todos/todo_model.py new file mode 100644 index 000000000..68f820447 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/todo_model.py @@ -0,0 +1,29 @@ +import os +from datetime import datetime + +from pynamodb.attributes import UnicodeAttribute, BooleanAttribute, UTCDateTimeAttribute +from pynamodb.models import Model + + +class TodoModel(Model): + class Meta: + table_name = os.environ['DYNAMODB_TABLE'] + if 'ENV' in os.environ: + host = 'http://localhost:8000' + else: + region = 'us-east-1' + host = 'https://dynamodb.us-east-1.amazonaws.com' + + todo_id = UnicodeAttribute(hash_key=True, null=False) + text = UnicodeAttribute(null=False) + checked = BooleanAttribute(null=False) + createdAt = UTCDateTimeAttribute(null=False, default=datetime.now()) + updatedAt = UTCDateTimeAttribute(null=False) + + def save(self, conditional_operator=None, **expected_values): + self.updatedAt = datetime.now() + super(TodoModel, self).save() + + def __iter__(self): + for name, attr in self._get_attributes().items(): + yield name, attr.serialize(getattr(self, name)) diff --git a/aws-python-http-api-with-pynamodb/todos/update.py b/aws-python-http-api-with-pynamodb/todos/update.py new file mode 100644 index 000000000..afa4edd47 --- /dev/null +++ b/aws-python-http-api-with-pynamodb/todos/update.py @@ -0,0 +1,40 @@ +import json +import logging + +from pynamodb.exceptions import DoesNotExist +from todos.todo_model import TodoModel + + +def update(event, context): + # TODO: Figure out why this is behaving differently to the other endpoints + # data = json.loads(event['body']) + data = event['body'] + + if 'text' not in data and 'checked' not in data: + logging.error('Validation Failed %s', data) + return {'statusCode': 422, + 'body': json.dumps({'error_message': 'Couldn\'t update the todo item.'})} + + try: + found_todo = TodoModel.get(hash_key=event['path']['todo_id']) + except DoesNotExist: + return {'statusCode': 404, + 'body': json.dumps({'error_message': 'TODO was not found'})} + + todo_changed = False + if 'text' in data and data['text'] != found_todo.text: + found_todo.text = data['text'] + todo_changed = True + if 'checked' in data and data['checked'] != found_todo.checked: + found_todo.checked = data['checked'] + todo_changed = True + + if todo_changed: + found_todo.save() + else: + logging.info('Nothing changed did not update') + + # create a response + return {'statusCode': 200, + 'body': json.dumps(dict(found_todo))} + diff --git a/aws-python-http-api/.gitignore b/aws-python-http-api/.gitignore new file mode 100644 index 000000000..84c61a91b --- /dev/null +++ b/aws-python-http-api/.gitignore @@ -0,0 +1,20 @@ +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Serverless directories +.serverless \ No newline at end of file diff --git a/aws-python-http-api/README.md b/aws-python-http-api/README.md new file mode 100644 index 000000000..80720b5de --- /dev/null +++ b/aws-python-http-api/README.md @@ -0,0 +1,122 @@ + + +# Serverless Framework Python HTTP API on AWS + +This template demonstrates how to make a simple HTTP API with Python running on AWS Lambda and API Gateway using the Serverless Framework. + +This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples/) which includes DynamoDB, Mongo, Fauna and other examples. + +## Usage + +### Deployment + +``` +$ serverless deploy +``` + +After deploying, you should see output similar to: + +```bash +Serverless: Packaging service... +Serverless: Excluding development dependencies... +Serverless: Creating Stack... +Serverless: Checking Stack create progress... +........ +Serverless: Stack create finished... +Serverless: Uploading CloudFormation file to S3... +Serverless: Uploading artifacts... +Serverless: Uploading service aws-python-http-api.zip file to S3 (711.23 KB)... +Serverless: Validating template... +Serverless: Updating Stack... +Serverless: Checking Stack update progress... +................................. +Serverless: Stack update finished... +Service Information +service: aws-python-http-api +stage: dev +region: us-east-1 +stack: aws-python-http-api-dev +resources: 12 +api keys: + None +endpoints: + ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ +functions: + api: aws-python-http-api-dev-hello +layers: + None +``` + +_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). + +### Invocation + +After successful deployment, you can call the created application via HTTP: + +```bash +curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ +``` + +Which should result in response similar to the following (removed `input` content for brevity): + +```json +{ + "message": "Go Serverless v2.0! Your function executed successfully!", + "input": { + ... + } +} +``` + +### Local development + +You can invoke your function locally by using the following command: + +```bash +serverless invoke local --function hello +``` + +Which should result in response similar to the following: + +``` +{ + "statusCode": 200, + "body": "{\n \"message\": \"Go Serverless v2.0! Your function executed successfully!\",\n \"input\": \"\"\n}" +} +``` + +Alternatively, it is also possible to emulate API Gateway and Lambda locally by using `serverless-offline` plugin. In order to do that, execute the following command: + +```bash +serverless plugin install -n serverless-offline +``` + +It will add the `serverless-offline` plugin to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. + +After installation, you can start local emulation with: + +``` +serverless offline +``` + +To learn more about the capabilities of `serverless-offline`, please refer to its [GitHub repository](https://github.com/dherault/serverless-offline). + +### Bundling dependencies + +In case you would like to include 3rd party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: + +```bash +serverless plugin install -n serverless-python-requirements +``` + +Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). diff --git a/aws-python-http-api/handler.py b/aws-python-http-api/handler.py new file mode 100644 index 000000000..57e6447fa --- /dev/null +++ b/aws-python-http-api/handler.py @@ -0,0 +1,21 @@ +import json + + +def hello(event, context): + body = { + "message": "Go Serverless v2.0! Your function executed successfully!", + "input": event, + } + + response = {"statusCode": 200, "body": json.dumps(body)} + + return response + + # Use this code if you don't use the http event with the LAMBDA-PROXY + # integration + """ + return { + "message": "Go Serverless v1.0! Your function executed successfully!", + "event": event + } + """ diff --git a/aws-python-http-api/serverless.template.yml b/aws-python-http-api/serverless.template.yml new file mode 100644 index 000000000..cd3c5fddd --- /dev/null +++ b/aws-python-http-api/serverless.template.yml @@ -0,0 +1,6 @@ +name: aws-python-http-api +org: serverlessinc +description: Deploys a Python HTTP API service with traditional Serverless Framework +keywords: aws, serverless, faas, lambda, python +repo: https://github.com/serverless/examples/aws-python-http-api +license: MIT diff --git a/aws-python-http-api/serverless.yml b/aws-python-http-api/serverless.yml new file mode 100644 index 000000000..be4a20a24 --- /dev/null +++ b/aws-python-http-api/serverless.yml @@ -0,0 +1,15 @@ +service: aws-python-http-api +frameworkVersion: '2' + +provider: + name: aws + runtime: python3.8 + lambdaHashingVersion: '20201221' + +functions: + hello: + handler: handler.hello + events: + - httpApi: + path: / + method: get diff --git a/aws-python-simple-http-endpoint/README.md b/aws-python-simple-http-endpoint/README.md index 0324415ad..6d80955b9 100644 --- a/aws-python-simple-http-endpoint/README.md +++ b/aws-python-simple-http-endpoint/README.md @@ -20,8 +20,6 @@ This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping ## Deploy -In order to deploy the you endpoint simply run - ```bash serverless deploy ``` @@ -44,9 +42,9 @@ region: us-east-1 api keys: None endpoints: - GET - https://f7r5srabr3.execute-api.us-east-1.amazonaws.com/dev/ping + GET - https://f7r5srabr3.execute-api.us-east-1.amazonaws.com/ping functions: - aws-python-simple-http-endpoint-dev-currentTime: arn:aws:lambda:us-east-1:377024778620:function:aws-python-simple-http-endpoint-dev-currentTime + currentTime: aws-python-simple-http-endpoint-dev-currentTime ``` ## Usage @@ -73,7 +71,7 @@ REPORT RequestId: a26699d3-b3ee-11e6-98f33f952e8294 Duration: 0.23 ms Billed Dur Finally you can send an HTTP request directly to the endpoint using a tool like curl ```bash -curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/ping +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/ping ``` The expected result should be similar to: @@ -84,4 +82,4 @@ The expected result should be similar to: ## Scaling -By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). diff --git a/aws-python-simple-http-endpoint/serverless.yml b/aws-python-simple-http-endpoint/serverless.yml index 10c7f2f9d..0ed6bf46b 100644 --- a/aws-python-simple-http-endpoint/serverless.yml +++ b/aws-python-simple-http-endpoint/serverless.yml @@ -1,15 +1,14 @@ service: aws-python-simple-http-endpoint - -frameworkVersion: ">=1.2.0 <2.0.0" +frameworkVersion: '2' provider: name: aws - runtime: python2.7 # or python3.7, supported as of November 2018 + runtime: python3.8 functions: currentTime: handler: handler.endpoint events: - - http: - path: ping + - httpApi: + path: /ping method: get diff --git a/aws-python-sqs-worker/README.md b/aws-python-sqs-worker/README.md index 2f485c33d..ab2772bbc 100644 --- a/aws-python-sqs-worker/README.md +++ b/aws-python-sqs-worker/README.md @@ -28,15 +28,13 @@ To learn more: ### Deployment -This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. - -In order to deploy with dashboard, you need to first login with: +Install dependencies with: ``` -serverless login +npm install ``` -and then perform deployment with: +Then deploy: ``` serverless deploy @@ -68,7 +66,7 @@ resources: 17 api keys: None endpoints: - POST - https://xxx.execute-api.us-east-1.amazonaws.com/dev/produce + POST - https://xxxx.execute-api.us-east-1.amazonaws.com/produce functions: producer: aws-python-sqs-worker-dev-producer jobsWorker: aws-python-sqs-worker-dev-jobsWorker @@ -85,7 +83,7 @@ _Note_: In current form, after deployment, your API is public and can be invoked After successful deployment, you can now call the created API endpoint with `POST` request to invoke `producer` function: ```bash -curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' +curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' ``` In response, you should see output similar to: diff --git a/aws-python-sqs-worker/package.json b/aws-python-sqs-worker/package.json index 856398816..050325a15 100644 --- a/aws-python-sqs-worker/package.json +++ b/aws-python-sqs-worker/package.json @@ -4,7 +4,7 @@ "description": "Serverless Framework Python SQS Producer-Consumer on AWS", "author": "", "license": "MIT", - "dependencies": { + "devDependencies": { "serverless-lift": "^1.1.2" } } diff --git a/aws-python-sqs-worker/serverless.yml b/aws-python-sqs-worker/serverless.yml index 80fa96fde..181040afd 100644 --- a/aws-python-sqs-worker/serverless.yml +++ b/aws-python-sqs-worker/serverless.yml @@ -1,13 +1,10 @@ service: aws-python-sqs-worker - frameworkVersion: '2' - provider: name: aws runtime: python3.8 lambdaHashingVersion: '20201221' - stage: dev constructs: jobs: @@ -19,11 +16,15 @@ functions: producer: handler: handler.producer events: - - http: + - httpApi: method: post - path: produce + path: /produce environment: QUEUE_URL: ${construct:jobs.queueUrl} plugins: - serverless-lift + +package: + patterns: + - '!node_modules/**' diff --git a/aws-ruby-simple-http-endpoint/README.md b/aws-ruby-simple-http-endpoint/README.md index dc06a2d5a..100d8aee0 100644 --- a/aws-ruby-simple-http-endpoint/README.md +++ b/aws-ruby-simple-http-endpoint/README.md @@ -47,7 +47,7 @@ stack: serverless-ruby-simple-http-endpoint-dev api keys: None endpoints: - GET - https://spmfbzc6ja.execute-api.us-east-1.amazonaws.com/dev/ping + GET - https://spmfbzc6ja.execute-api.us-east-1.amazonaws.com/ping functions: current_time: serverless-ruby-simple-http-endpoint-dev-current_time layers: @@ -59,9 +59,9 @@ Serverless: Removing old service artifacts from S3... Send an HTTP request directly to the endpoint using a tool like curl: ```bash -curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/ping +curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/ping ``` ## Scaling -By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). +By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). diff --git a/aws-ruby-simple-http-endpoint/serverless.yml b/aws-ruby-simple-http-endpoint/serverless.yml index 6ebc7b9da..bbabc038c 100644 --- a/aws-ruby-simple-http-endpoint/serverless.yml +++ b/aws-ruby-simple-http-endpoint/serverless.yml @@ -1,6 +1,5 @@ service: serverless-ruby-simple-http-endpoint - -frameworkVersion: ">=2.1.0 <3.0.0" +frameworkVersion: '2' provider: name: aws @@ -10,6 +9,6 @@ functions: current_time: handler: handler.endpoint events: - - http: - path: ping - method: get \ No newline at end of file + - httpApi: + path: /ping + method: get diff --git a/aws-rust-simple-http-endpoint/README.md b/aws-rust-simple-http-endpoint/README.md index 7ef3bfdb3..5063739c9 100644 --- a/aws-rust-simple-http-endpoint/README.md +++ b/aws-rust-simple-http-endpoint/README.md @@ -47,7 +47,7 @@ serverless-state.json ## 4. Invoke deployed function ```bash -$ curl https://***.execute-api.us-east-1.amazonaws.com/dev/test/test +$ curl https://***.execute-api.us-east-1.amazonaws.com/test/test {"message":"Serverless Rust Hello"} ``` diff --git a/aws-rust-simple-http-endpoint/serverless.yml b/aws-rust-simple-http-endpoint/serverless.yml index fb0a5e890..f3fd010f0 100644 --- a/aws-rust-simple-http-endpoint/serverless.yml +++ b/aws-rust-simple-http-endpoint/serverless.yml @@ -1,6 +1,5 @@ service: aws-rust-simple-http-endpoint - -#frameworkVersion: ">=1.28.0 <2.0.0" +frameworkVersion: '2' provider: name: aws @@ -23,8 +22,8 @@ functions: test_test: handler: test events: - - http: - path: test/test + - httpApi: + path: /test/test method: get custom: