From 18ca3735eadb8db0f5f2fddf9ae75133dda1d20d Mon Sep 17 00:00:00 2001 From: Enzodtz Date: Wed, 22 Feb 2023 15:49:30 -0300 Subject: [PATCH] Adding example for AWS SAM --- examples/aws-sam/.gitignore | 207 ++++++++++++++++++ examples/aws-sam/.gitkeep | 0 examples/aws-sam/README.md | 24 ++ examples/aws-sam/hello-world/.eslintignore | 2 + examples/aws-sam/hello-world/.eslintrc.js | 15 ++ examples/aws-sam/hello-world/.npmignore | 1 + examples/aws-sam/hello-world/.prettierrc.js | 7 + examples/aws-sam/hello-world/app.ts | 47 ++++ examples/aws-sam/hello-world/jest.config.ts | 15 ++ examples/aws-sam/hello-world/package.json | 35 +++ .../tests/unit/test-handler.test.ts | 59 +++++ examples/aws-sam/hello-world/tsconfig.json | 15 ++ examples/aws-sam/template.yaml | 52 +++++ 13 files changed, 479 insertions(+) create mode 100644 examples/aws-sam/.gitignore delete mode 100644 examples/aws-sam/.gitkeep create mode 100644 examples/aws-sam/README.md create mode 100644 examples/aws-sam/hello-world/.eslintignore create mode 100644 examples/aws-sam/hello-world/.eslintrc.js create mode 100644 examples/aws-sam/hello-world/.npmignore create mode 100644 examples/aws-sam/hello-world/.prettierrc.js create mode 100644 examples/aws-sam/hello-world/app.ts create mode 100644 examples/aws-sam/hello-world/jest.config.ts create mode 100644 examples/aws-sam/hello-world/package.json create mode 100644 examples/aws-sam/hello-world/tests/unit/test-handler.test.ts create mode 100644 examples/aws-sam/hello-world/tsconfig.json create mode 100644 examples/aws-sam/template.yaml diff --git a/examples/aws-sam/.gitignore b/examples/aws-sam/.gitignore new file mode 100644 index 0000000..5854f05 --- /dev/null +++ b/examples/aws-sam/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/examples/aws-sam/.gitkeep b/examples/aws-sam/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/aws-sam/README.md b/examples/aws-sam/README.md new file mode 100644 index 0000000..f2ea254 --- /dev/null +++ b/examples/aws-sam/README.md @@ -0,0 +1,24 @@ +# aws-sam-example + +This project is an example of using chromium inside aws sam written from sam init Hello World template with nodeJS 16 as runtime. + +### Changes from hello world template + +- Installed puppeteer and @sparticuz/chromium + - Note: it must not be in dev-dependencies +- Modified the handler to instantiate the browser pointing to the AWS layer directory (`/opt/nodejs/node_modules/@sparticuz/chromium/bin`) +- Added layer in template and fixed timeout and +- Running same example as README + +### Configuration + +You must create a layer with the chromium binary and replace its arn inside `template.yaml`. You can see an example on how to create it in the main project README. + +### Running + +Build and invoke the function. + +```bash +sam build +sam local invoke +``` diff --git a/examples/aws-sam/hello-world/.eslintignore b/examples/aws-sam/hello-world/.eslintignore new file mode 100644 index 0000000..512d4cb --- /dev/null +++ b/examples/aws-sam/hello-world/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.aws-sam \ No newline at end of file diff --git a/examples/aws-sam/hello-world/.eslintrc.js b/examples/aws-sam/hello-world/.eslintrc.js new file mode 100644 index 0000000..5da871f --- /dev/null +++ b/examples/aws-sam/hello-world/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module" + }, + extends: [ + "plugin:@typescript-eslint/recommended", // recommended rules from the @typescript-eslint/eslint-plugin + "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + } + }; \ No newline at end of file diff --git a/examples/aws-sam/hello-world/.npmignore b/examples/aws-sam/hello-world/.npmignore new file mode 100644 index 0000000..e7e1fb0 --- /dev/null +++ b/examples/aws-sam/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/examples/aws-sam/hello-world/.prettierrc.js b/examples/aws-sam/hello-world/.prettierrc.js new file mode 100644 index 0000000..4c2c6c7 --- /dev/null +++ b/examples/aws-sam/hello-world/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 4 + }; \ No newline at end of file diff --git a/examples/aws-sam/hello-world/app.ts b/examples/aws-sam/hello-world/app.ts new file mode 100644 index 0000000..383b792 --- /dev/null +++ b/examples/aws-sam/hello-world/app.ts @@ -0,0 +1,47 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import chromium from '@sparticuz/chromium'; +import puppeteer from 'puppeteer-core'; +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ + +export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise => { + const browser = await puppeteer.launch({ + args: chromium.args, + defaultViewport: chromium.defaultViewport, + executablePath: await chromium.executablePath('/opt/nodejs/node_modules/@sparticuz/chromium/bin'), + headless: chromium.headless, + ignoreHTTPSErrors: true, + }); + + const page = await browser.newPage(); + await page.goto("https://example.com"); + const pageTitle = await page.title(); + await browser.close(); + let response: APIGatewayProxyResult; + + try { + response = { + statusCode: 200, + body: JSON.stringify({ + message: pageTitle, + }), + }; + } catch (err: unknown) { + console.error(err); + response = { + statusCode: 500, + body: JSON.stringify({ + message: err instanceof Error ? err.message : 'some error happened', + }), + }; + } + + return response; +}; diff --git a/examples/aws-sam/hello-world/jest.config.ts b/examples/aws-sam/hello-world/jest.config.ts new file mode 100644 index 0000000..21d6c7c --- /dev/null +++ b/examples/aws-sam/hello-world/jest.config.ts @@ -0,0 +1,15 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + transform: { + '^.+\\.ts?$': 'esbuild-jest', + }, + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + coverageProvider: 'v8', + testMatch: ['**/tests/unit/*.test.ts'], +}; diff --git a/examples/aws-sam/hello-world/package.json b/examples/aws-sam/hello-world/package.json new file mode 100644 index 0000000..cef2545 --- /dev/null +++ b/examples/aws-sam/hello-world/package.json @@ -0,0 +1,35 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "@sparticuz/chromium": "110.0.0", + "esbuild": "^0.14.14", + "puppeteer-core": "19.6.3" + }, + "scripts": { + "unit": "jest", + "lint": "eslint '*.ts' --quiet --fix", + "compile": "tsc", + "test": "npm run compile && npm run unit" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.13", + "@typescript-eslint/eslint-plugin": "^5.10.2", + "@typescript-eslint/parser": "^5.10.2", + "esbuild-jest": "^0.5.0", + "eslint": "^8.8.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^27.5.0", + "prettier": "^2.5.1", + "ts-node": "^10.4.0", + "typescript": "^4.5.5" + } +} diff --git a/examples/aws-sam/hello-world/tests/unit/test-handler.test.ts b/examples/aws-sam/hello-world/tests/unit/test-handler.test.ts new file mode 100644 index 0000000..2ee5843 --- /dev/null +++ b/examples/aws-sam/hello-world/tests/unit/test-handler.test.ts @@ -0,0 +1,59 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { lambdaHandler } from '../../app'; + +describe('Unit test for app handler', function () { + it('verifies successful response', async () => { + const event: APIGatewayProxyEvent = { + httpMethod: 'get', + body: '', + headers: {}, + isBase64Encoded: false, + multiValueHeaders: {}, + multiValueQueryStringParameters: {}, + path: '/hello', + pathParameters: {}, + queryStringParameters: {}, + requestContext: { + accountId: '123456789012', + apiId: '1234', + authorizer: {}, + httpMethod: 'get', + identity: { + accessKey: '', + accountId: '', + apiKey: '', + apiKeyId: '', + caller: '', + clientCert: { + clientCertPem: '', + issuerDN: '', + serialNumber: '', + subjectDN: '', + validity: { notAfter: '', notBefore: '' }, + }, + cognitoAuthenticationProvider: '', + cognitoAuthenticationType: '', + cognitoIdentityId: '', + cognitoIdentityPoolId: '', + principalOrgId: '', + sourceIp: '', + user: '', + userAgent: '', + userArn: '', + }, + path: '/hello', + protocol: 'HTTP/1.1', + requestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', + requestTimeEpoch: 1428582896000, + resourceId: '123456', + resourcePath: '/hello', + stage: 'dev', + }, + resource: '', + stageVariables: {}, + }; + const result: APIGatewayProxyResult = await lambdaHandler(event); + + expect(result.statusCode).toEqual(200); + }); +}); diff --git a/examples/aws-sam/hello-world/tsconfig.json b/examples/aws-sam/hello-world/tsconfig.json new file mode 100644 index 0000000..ffaf193 --- /dev/null +++ b/examples/aws-sam/hello-world/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "strict": true, + "preserveConstEnums": true, + "noEmit": true, + "sourceMap": false, + "module":"es2015", + "moduleResolution":"node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + }, + "exclude": ["node_modules", "**/*.test.ts"] + } \ No newline at end of file diff --git a/examples/aws-sam/template.yaml b/examples/aws-sam/template.yaml new file mode 100644 index 0000000..ef28af3 --- /dev/null +++ b/examples/aws-sam/template.yaml @@ -0,0 +1,52 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + aws-sam + + Sample SAM Template for aws-sam + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 90 + MemorySize: 512 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + Layers: + - + CodeUri: hello-world/ + Handler: app.lambdaHandler + Runtime: nodejs16.x + Architectures: + - x86_64 + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + Metadata: # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + # Sourcemap: true # Enabling source maps will create the required NODE_OPTIONS environment variables on your lambda function during sam build + EntryPoints: + - app.ts + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn