Serverless Components A
Overview
This project is a prototype of a new concept Serverless has been exploring called "components". Our aim is to introduce highly configurable and composable pieces that allow for multi-cloud & third party resource use cases.
Components are capable of provisioning infrastructure while including both application logic AND lifecycle management. They have a focus on serverless/cloud resources and they also greatly enable reuse, sharing and simplicity.
Table of contents
Getting started
Note: Please make sure that you've installed Node.js and have properly setup our AWS credentials.
- Setup
npm install --global @serverless/serverless-components-a
- Clone this repository
- Configuring
cd src/examples/github-webhook
- Open up the example in your favorite editor (e.g.
atom .
) - Update the
repository
and githubToken
parameters with your values
- Deployment
proto deploy
- Testing
proto test
- Updating
- Change some parameters (e.g. the
event
or httpPath
property) proto deploy
- Removal
proto remove
Concepts
Components
Components are a way to share and re-use pre-built functionality throughout different projects. A project can contain one or more components. Furthermore components can be wrapped and composed together to extend functionality.
Components take parameters as their input and compute outputs when run. One example use-case for an output is the .state.json
file which is created for every project and holds state information for every single component used in the project.
Components can be defined in a serverless.yml
file like this:
components:
myDynamoDbTable:
type: pmuens:aws-dynamodb-table@0.1.0
parameters:
attributeDefinitions:
- attributeName: id
attributeType: S
keySchema:
- attributeName: id
keyType: HASH
provisionedThroughput:
readCapacityUnits: 1
writeCapacityUnits: 1
tableName: my-dynamodb-table
In this example we're using the aws-dynamodb-table
component at version 0.1.0
which was authored by pmuens
. We pass in different parameters to configure the infrastructure this component will setup. In our case we set the attributeDefinitions
, the keySchame
, the provisionedThroughput
as well as the tableName
.
Most components ship with sane defaults so that it's not necessary to provide all parameters.
A components underpinning functionality is provided by a function.
In this PoC implementation a function is equal to a component. This makes it possible to use components in a declarative way as well as through code.
The function which expresses the components functionality above looks like this:
module.exports = (context, params, options) => {
const {
attributeDefinitions,
keySchema,
provisionedThroughput,
tableName
} = params
async function createTable(tableName) {
return sdk('DynamoDB', 'createTable', {
TableName: tableName
})
}
async function deleteTable(tableName) {
return sdk('DynamoDB', 'deleteTable', {
TableName: tableName
})
}
async function deploy() {
await createTable(params.tableName)
}
async function remove() {
await deleteTable(params.tableName)
}
return {
deploy,
remove
}
}
The components parameters and CLI options are passed in to the function and are accessible via params
and options
. The returned functions at the bottom of the function are exposed and accessible via CLI commands.
Treating a component and the corresponding function equally makes it possible to pull in pre-written components in code and therefore re-use them to write parent components which orchestrate different child components. Take a look at the code for the github-webhook-receiver
component to see how existing components can be re-used in code.
Serverless Registry
The "Serverless Registry" is a core part in this implementation since it makes it possible to discover, publish and share existing components.
The registry is not only constrained to serve components. Since components are functions it's possible to wrap existing business logic into functions and publish them to the registry as well.
While implementing this prototype we've created two util functions / components called util-aws-request
and util-zipper
. Both are used in various components to steamline AWS SDK calls and .zip
archive creation.
Looking into the future it could be possible to serve functions which are written in different languages through the registry.
Docs
aws-api-gateway-lambda-endpoint
Creates / Removes an API endpoint which is exposed via AWS API Gateway and connects directly to an AWS Lambda function.
Parameters
Name | Description | Type |
---|
path | The HTTP path | String |
method | The HTTP method | String |
lambdaArn | The AWS Lambda functions arn which should be connected via the API Gateway | String |
roleArn | The role arn which is used to give the API Gateway the permission to call the AWS Lambda function | String |
Example
type: pmuens:aws-api-gateway-lambda-endpoint@0.1.0
parameters:
path: my-path
method: GET
lambdaArn: arn:aws:lambda:us-east-1:XXXXX:function:some-lambda-function
roleArn: arn:aws:iam::XXXXX:role/some-api-gateway-role
aws-dynamodb-table
Creates / Removes an AWS DynamoDB table.
Parameters
Name | Description | Type |
---|
attributeDefinitions | The tables attribute definitions | Array |
keySchema | The tables key schema | Array |
provisionedThroughput | The tables provisioned throughput | Object |
tableName | The tables name | String |
Example
type: pmuens:aws-dynamodb-table@0.1.0
parameters:
attributeDefinitions:
- attributeName: id
attributeType: S
keySchema:
- attributeName: id
keyType: HASH
provisionedThroughput:
readCapacityUnits: 1
writeCapacityUnits: 1
tableName: my-dynamodb-table
aws-iam-role
Creates / Removes an AWS IAM role.
Parameters
Name | Description | Type |
---|
roleName | The name for the IAM role | String |
service | The service the role targets | String |
policyArn | The policy arn this role should assume | String |
Example
type: pmuens:aws-iam-role@0.1.0
parameters:
roleName: my-ec2-role
service: ec2.amazonaws.com
policyArn: arn:aws:iam::aws:policy/AdministratorAccess
aws-lambda-function
Creates / Removes an AWS Lambda function.
Parameters
Name | Description | Type |
---|
codeDirPath | Path to the location where the functions code is located | String |
functionName | The functions name | String |
handler | The functions handler | String |
roleArn | The role arn the lambda should use | String |
runtime | The functions runtime | String |
environment | The functions environment variables | Object |
memorySize | The functions memory size | Number |
timeout | The functions timeout | Number |
Example
type: pmuens:aws-lambda-function@0.1.0
parameters:
codeDirPath: ./code
functionName: my-lambda-function
handler: index.handler
roleArn: arn:aws:iam::XXXXX:role/some-lambda-role
runtime: nodejs6.10
environment:
variables:
foo: bar
memorySize: 512
timeout: 60
github-webhook-receiver
Creates / Removes a GitHub Webhook. Uses AWS as the cloud provider.
AWS resource names created by this component:
Resource | Name |
---|
Lambda function name | ${name}-lambda-function |
DynamoDB table name | ${name}-logging-table |
Lambda role name | ${name}-role-lambda |
API Gateway role name | ${name}-role-api-gateway |
Parameters
Name | Description | Type |
---|
name | The webhooks name (Note: this name will be used as a prefix for all AWS resources (see above)) | String |
codeDirPath | Path to the location where the functions code is located | String |
httpPath | The HTTP path | String |
repository | Name of the GitHub repository the Webhook should be created for | String |
event | The Webhooks event | String |
githubToken | The GitHub token which is used to create the Webhook on your behalf | String |
Example
type: pmuens:github-webhook-receiver@0.1.0
parameters:
name: my-github-webhook
codeDirPath: ./code
httpPath: webhook
repository: jdoe/some-repo
event: issue_comment
githubToken: s0me3g1thu8t0k3n
util-aws-request
Helper which wraps the AWS SDK and provides some convenience functions.
Parameters
Example
const request = loadFunction(context, 'pmuens:util-aws-request@0.1.0', params, options)
const res = await request.send('Lambda', 'deleteFunction', {
FunctionName: params.functionName
})
util-zipper
Helper which provides functionality to create a .zip
archive.
Parameters
Example
const zipper = loadFunction(context, 'pmuens:util-zipper@0.1.0', params, options)
const artifactFilePath = await zipper.zip(codeDirPath, context.componentRootPath)