Commit 0e38e320 authored by GitLab's avatar GitLab Committed by Alex Ives
Browse files

Initialized from 'Serverless Framework/JS' project template

Template repository: https://gitlab.com/gitlab-org/project-templates/serverless-framework
Commit SHA: 410cbb3d966e2292f76e411539bb02c0dba985e4
parents
node_modules
build
npm-debug.log
.env
.serverless/
.eslintcache
.npm
logs
*.log
./stack.json
image: node:latest
variables:
AWS_REGION: us-east-1
stages:
- test
- deploy_function
- test_deployed_function
- deploy_pages
test:
stage: test
script:
- npm install
- npm test
production:
stage: deploy_function
before_script:
- FAILURE_MESSAGE="Must define \$AWS_ACCESS_KEY_ID and \$AWS_SECRET_ACCESS_KEY. Add keys to $CI_PROJECT_URL/-/settings/ci_cd"
- test -z "$AWS_SECRET_ACCESS_KEY" && echo $FAILURE_MESSAGE && exit 1
- test -z "$AWS_ACCESS_KEY_ID" && echo $FAILURE_MESSAGE && exit 1
script:
- npm install
- npm run deploy -- --stage production --verbose
environment: production
only:
- master
artifacts:
paths:
- stack.json
expire_in: 2 weeks
postdeploy_test:
stage: test_deployed_function
only:
- master
script:
- npm install
- STACK_JSON_FILE=./stack.json npm test featureTests
pages:
stage: deploy_pages
script:
- cp stack.json ./public/stack.json
environment: production_pages
only:
- master
artifacts:
paths:
- ./public/
# Serverless Framework with JavaScript
---
Example project using the [Serverless Framework](https://serverless.com), JavaScript, AWS Lambda, AWS API Gateway and GitLab Pages.
---
## Deployment
### Secrets
Secrets are injected into your functions using environment variables. By defining variables in the provider section of the `serverless.yml` you add them to the environment of the deployed function. From there, you can reference them in your functions as well.
So you would add something like:
```yml
provider:
environment:
A_VARIABLE: ${env:A_VARIABLE}
```
to your `serverless.yml`, and then you can add `A_VARIABLE` to your GitLab Ci variables and it will get picked up and deployed with your function.
For local development, we suggest installing something like [dotenv](https://www.npmjs.com/package/dotenv) to manage environment variables.
### Setting Up AWS
1. Create AWS credentials including the following IAM policies: `AWSLambdaFullAccess`, `AmazonAPIGatewayAdministrator` and `AWSCloudFormationFullAccess`.
1. Set the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` variables in the GitLab CI/CD settings. `Settings > CI/CD > Variables`.
### Accessing Page
To view your page go to `Settings > Pages` and click on the given link.
## Development
### Running Locally
Install dependencies with:
```sh
npm install
```
Run backend server with:
```sh
npm start
```
This runs the serverless function locally using `serverless-offline` plugin.
Run frontend with:
```sh
npm run pages
```
The frontend should be available at `http://localhost:8080`
### Running Tests
```sh
npm test
```
#### Unit Tests
For the serverless backend, unit tests live with the src files as `srcFile.test.js`. The unit tests use the `serverless-jest-plugin` and lambda wrapper to simulate events to the functions and validate their outputs.
#### Feature Tests
Feature tests live in the folder `featureTests`. Those tests allow us to spin up serverless offline as a service and make requests against it and validate the results of those requests.
Feature tests double as post deploy tests when the environment variable `STACK_JSON_FILE` is specified with the path to the file generated on deployment (`stack.json`), see in `gitlab-ci.yml`.
A typical feature test will look something like:
```javascript
// This helper provides access to the serverless process and an axios instance
// to make requests against the running service.
const { serverlessProcess, serverlessService } = require('./helper.js')
describe('some_function', () => {
beforeAll(async () => {
// serverlessProcess.start starts serverless offline in a child process
await serverlessProcess.start()
})
afterAll(() => {
// serverlessProcess.stop kills the child process at the end of the test
serverlessProcess.stop()
})
it('responds to a request', async () => {
// The axios instance has the base url and port already, so you just have
// to provide a route and any paramters or headers. See the axios project
// for details.
let response = await serverlessService.get('/some_route?param=here')
expect(response.data.info).toEqual('amazing')
})
});
```
## Additional information
### Getting the Endpoint URL
This project is setup with the `serverless-stack-output plugin` which is configured to output a JSON file to `./stack.json`. See [this github repo](https://github.com/sbstjn/serverless-stack-output) for more details.
const { serverlessProcess, serverlessService } = require('./helper.js')
describe('hello', () => {
beforeAll(async () => {
// serverlessProcess.start starts serverless offline in a child process
// unless the test is pointed at a deployed service
await serverlessProcess.start()
})
afterAll(() => {
// serverlessProcess.stop kills the child process at the end of the test
// unless the test is pointed at a deployed service
serverlessProcess.stop()
})
it('makes a request to the serverless process', async () => {
// serverlessService is an axios instance pointed at your serverless offline
// unless the test is pointed at a deployed service, then it uses that.
let response = await serverlessService.get('/hello')
expect(response.data.message).toEqual('Your function executed successfully!')
expect(response.data.params).toEqual(null)
})
it('makes a request that should have a different kind of response', async () => {
let response = await serverlessService.get('/hello?tomorrow=amazing')
expect(response.data.params.tomorrow).toEqual('amazing')
})
});
const { spawn } = require('child_process')
const axios = require('axios')
const url = require('url')
const waitOn = require('wait-on')
const fs = require('fs')
let basePort = process.env.PORT || '3000'
let baseUrl = url.format({
protocol : process.env.PROTOCOL || 'http',
hostname : process.env.HOST || 'localhost',
port : process.env.PORT || basePort
})
let useLocalService = true
// Uses a TCP for determining availability of the service
const baseTcp = url.format({
protocol : process.env.PROTOCOL || 'tcp',
hostname : process.env.HOST || 'localhost',
port : process.env.PORT || basePort
})
const stackFile = process.env.STACK_JSON_FILE
if (fs.existsSync(stackFile)) {
const stackInfo = JSON.parse(fs.readFileSync(stackFile))
useLocalService = false
baseUrl = stackInfo.ServiceEndpoint
}
let serverlessService = axios.create({
baseURL: baseUrl,
timeout: 5000
})
let serverlessProcess = {
serverless_process: null,
start: async () => {
if(useLocalService) {
this.serverless_process = spawn('serverless', ['offline', '--port', basePort])
await waitOn({resources: [baseTcp]})
}
},
stop: () => {
if(useLocalService) {
this.serverless_process.kill('SIGINT')
}
}
}
module.exports = {
serverlessProcess: serverlessProcess,
serverlessService: serverlessService
}
This diff is collapsed.
{
"name": "serverless-framework-with-js",
"version": "1.0.0",
"description": "Example project using the Serverless Framework, JavaScript, AWS Lambda, AWS API Gateway and GitLab Pages.",
"dependencies": {
"serverless": "^1.56.1",
"serverless-jest-plugin": "^0.2.1",
"serverless-offline": "^5.12.0",
"serverless-stack-output": "^0.2.3"
},
"devDependencies": {
"axios": "^0.19.0",
"http-server": "^0.11.1",
"wait-on": "^3.3.0"
},
"scripts": {
"test": "jest",
"deploy": "serverless deploy",
"start": "serverless offline",
"pages": "http-server"
},
"jest": {
"testMatch": [
"**/?(*.)+(spec|test).[jt]s?(x)"
]
}
}
<!DOCTYPE html>
<html>
<head>
<title>GitLab Serverless Framework example</title>
</head>
<body>
<p>Click on the button to run your function. You can send a parameter too!</p>
<label>
Param value:
<input type="text" id="param" placeholder="Input your param value" name="yourParam">
</label>
<br>
<button>Run function</button>
<p>Function Output:</p>
<p id="functionOutput"></p>
<script>
fetch('./stack.json').then((response) => {
response.json().then((myJson) => {
const functionUrl = myJson.ServiceEndpoint + "/hello"
document.querySelector('button').addEventListener('click', () => {
const paramValue = document.querySelector('#param').value
fetch(functionUrl + '?myParam=' + paramValue)
.then((response) => response.json())
.then((json) => {
document.querySelector('#functionOutput').textContent = JSON.stringify(json)
})
})
})
});
</script>
</body>
</html>
{
"ServiceEndpoint": "http://localhost:3000"
}
\ No newline at end of file
service: gitlab-example
provider:
name: aws
region: ${env:AWS_REGION}
runtime: nodejs10.x
environment:
A_VARIABLE: ${env:A_VARIABLE}
plugins:
- serverless-offline
- serverless-jest-plugin
- serverless-stack-output # Allows us to output endpoint url to json file
functions:
hello:
handler: src/handler.hello
events:
- http:
path: hello
method: get
cors: true
custom:
output:
handler: src/handler.hello
file: stack.json
'use strict';
module.exports.hello = async function(event) {
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(
{
message: 'Your function executed successfully!',
params: event.queryStringParameters,
secret: process.env.A_VARIABLE
},
null,
2
),
};
};
'use strict';
const mod = require('./handler');
const jestPlugin = require('serverless-jest-plugin');
const lambdaWrapper = jestPlugin.lambdaWrapper;
const wrapped = lambdaWrapper.wrap(mod, { handler: 'hello' });
describe('hello', () => {
it('returns parameters in the body', () => {
return wrapped.run({queryStringParameters: { a: 'b'}}).then((response) => {
expect(response.statusCode).toEqual(200);
expect(JSON.parse(response.body).message).toEqual("Your function executed successfully!")
expect(JSON.parse(response.body).params).toEqual({"a": "b"})
});
});
it('references an environment variable', ()=> {
let originalEnv = process.env.A_VARIABLE
process.env.A_VARIABLE = 'Test'
return wrapped.run({}).then((response) => {
expect(JSON.parse(response.body).secret).toEqual("Test")
})
})
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment