Serverless Framework I'm using it, and I wanted to automate work details that were often set manually during deployment, so I created a library that also serves as a knowledge acquisition for creating plug-ins NPM I tried to publish it at
There seems to be a possibility that it will be developed in the future, so I've summarized the steps for creating a Serverless plug-in with TypeScript. While briefly introducing each procedure,I will mainly write articles focusing on the points I was addicted to and devised during that process.
The final project directory structure when the content of this article has been implemented until the end is as follows.
tree -I node_modules -L 2. /
. /
├── example # Folder where sample code for verifying the operation of the library is placed
│ ├── handler.js
│ ├── package.json
│ └ ── serverless.yml
├── lib # folder for arranging the results of compiling files in the src folder (source code group included when used as a library)
│ ├── index.js
│ └── index.js.map
├── package-lock.json
├── package.json
├── src # The folder where the Serverless plug-in source code is placed
│ └── index.ts
└── tsconfig.json
Basically Let's write a Serverless Framework Plugin in TypeScript | developers.io The environment construction itself is possible simply by tracing the steps. Therefore, here I will describe the parts I devised in my own way.
First, install all the packages required for development with the following command.
# Installing packages required for TypeScript development
npm i -D typescript
# Installing TypeScript Type Definition Files
npm i -D @types /node @types /serverless
# This time we will be developing for the AWS provider, so we will install the SDK
npm i --save aws-SDK
The tsconfig.json required when compiling TypeScript was set as follows.
tsconfig.json
{
“compilerOptions”: {
“target”: “es6",
“module”: “commonjs”,
“moduleResolution”: “node”,
“strict”: true,
“strictBindCallApply”: false,
“strictNullChecks”: false,
“outDir”: “lib”,
“sourceMap”: true
},
“include”: [
“src/**/*”
]
}
While setting true to compilerOptions.strict, the TypeScript compilation check was partially removed by setting false to compilerOptions.strictNullChecks, etc.
By specifying lib as outDir, I set the compiled TypeScript files to be output to the lib folder.
src/**/* is explicitly specified for include, and all files in the src folder are subject to compilation.
The contents of package.json are partially excerpted, and items that are likely to need explanation are explained.
If you want to know the whole story Here You can check it out at
package.json
{
“main” :" lib/index.js “,
“files”: [
“lib”
],
“scripts”: {
“build”: “rm -rf lib && tsc”,
“test”: “echo\" Error: no test specified\” && exit 1"
}
}
lib/index.js, which is generated when src/index.ts is compiled, was specified as main. Therefore, the library entry point is set to lib/index.js.
By specifying the lib folder for files, only the results of compiling TypeScript will be imported as library source code.
Now that the development environment is ready, I will immediately write the Serverless Plugin source code. The TypeScript source code is placed in src/index.ts.
src/index.ts
import * as Serverless from 'serverless'
import {
sharedInifileCreations,
config,
} from 'aws-SDK'
/**
* Serverless.yml custom property type definitions
*/
interface Variables {
value1: string
value2: number
value3: boolean
profile? : string
}
export default class Plugin {
serverless: serverless
options: Serverless.Options
hooks: {
[event: string]: () => Promise <void>
}
variables: variables
/**
* Plug-in initialization function.
* Note that variables in serverless.yml are not expanded within the initialization function, so
* Even if you call a value set by $ {ssm: ~}, etc., it will be called without an appropriate value being set.
*/
constructor (serverless: Serverless, options: Serverless.Options) {
this.serverless = serverless
this.options = options
/**
* Description for retrieving specific properties in serverless.service.custom
* Since serverless-typescript was set as the Serverless plug-in name this time,
* Specify a serverless-typescript string as a key.
*/
this.variables = serverless.service.custom ['serverless-typescript']
/**
* Specify the function to which the plug-in hooks. It is also possible to specify multiple ones,
* This time, specify before:package:createDeploymentArtifacts,
* Hook up the pre-packaging process with a defined run function.
*/
this.hooks = {
'before: package: createDeploymentArtifacts': this.run.bind (this),
}
}
/**
* before: package: function executed when createDeploymentArtifacts
*/
async run () {
/**
* If the required fields are not set when the plug-in is executed, processing is skipped
*/
if (! this.variables) {
this.serverless.cli.log (
`serverless-typescript: Set the custom.serverless-typescript field to an appropriate value. `,
)
return
}
/**
* By using the this.serverless.getProvider function,
* You can obtain various information about the account at the time of deployment
*/
const awsProvider = this.serverless.getProvider ('aws')
const region = await AWSProvider.getRegion ()
const accountId = await awsProvider.getAccountId ()
const stage = await AWSProvider.getStage ()
/**
* Whether the values and AWS information specified in serverless.yml have been obtained,
* Standard output for confirmation
*/
this.serverless.cli.log (
`serverless-typescript values: $ {JSON.stringify ({
stage: stage,
region: region,
accountId: accountId,
variables: this.variables,
})} `,
)
/**
* When executing processing within the plug-in, when you want to use a different specific profile,
* You can switch easily by switching using the AWS SDK's sharedInifileCredentials.
* In that case, set the value to process.env.aws_SDK_load_config
*/
if (this.variables.profile) {
process.env.aws_SDK_load_config = 'true'
const credentials = new sharedInifileCredentials ({
profile: this.variables.profile,
})
config.credentials = credentials
}
}
}
module.exports = Plugin
I left a few comments in the source code, but I'll explain some additional points.
By calling serverless.service.custom ['serverless-typescript'], the following description in serverless.yml can be obtained as an Object.
serverless.yml
custom:
# Definitions in custom.serverless-typescript can be retrieved as an Object
serverless-typescript:
value1: “value1"
value2:0
value3: true
# profile: default (optional)
In this.hooks, specify hooks as needed. How to write a hook Official documentation Details are described in. About the types of hooks Gist There was someone who put it together.
By using this.serverless.getProvider ('aws'), it is possible to obtain various account information during deployment. By using this description Serverless Pseudo Parameters You will be able to import syntax like this into your own plug-ins.
Even if it's a plug-in I created serverless.yml at ARN I'm using it when constructing,index.ts I used it inside.
Also,I think there are cases where you want to use a different profile in the plug-in than when deploying. It's the AWS SDK's sharedInifileCreations It was easy to implement by using.1
Next, we will place the code for verifying the operation of the plug-in in the example folder.
We'll create a verification project in the example folder, so we'll create example/package.json as a preliminary preparation.
# Create a package.json file
cd example && npm init -y
Once the example/package.json file is created, the development script is added to example/package.json.
example/package.json
{
“scripts”: {
“prestart”: “cd../&& npm run build”,
“start”: “sls package”,
“test”: “echo\" Error: no test specified\” && exit 1"
}
}
The prestart in scripts is a script that is executed before the start script is executed. When you run npm start, run the plugin build task with prestart, and then you can check the operation of the plug-in by packaging the Serverless Framework.2
Next, place serverless.yml for verifying operation in the example folder.
serverless.yml
service:
name: serverless-typescript
publish: false
# Define settings to be used in plug-ins
custom:
serverless-typescript:
value1: “value1"
value2:0
value3: true
profile: custom_profile
provider:
Name: aws
runtime: nodejs12.x
Region: ap-northeast-1
# Specify the plug-in path and load
plugins:
localPath: ' .. / .. /'
modules:
- serverless-typescript
# Anything is fine, so define a function for verifying operation (the function definition will be described later)
Functions:
HELLO:
handler: handler.hello
Place handler.js in the example folder and define the verification function used in functions.hello.handler.
example/handler.js
'use strict';
//Verification function. Can be referenced in serverless.yml with handler.hello
module.exports.hello = (event, context, callback) => {
callback (null, {
statusCode: 200,
body: “Hello World!”
});
};
As soon as the above work is completed, run cd example && npm start to verify operation.
CD example && npm start
> example @1 .0.0 prestart/users/nika/desktop/serverless-typescript/example
> cd../&& npm run build
> serverless-typescript @1 .0.0 build /users/nika/desktop/serverless-typescript
> rm -rf lib && tsc
> example @1 .0.0 start /users/nika/desktop/serverless-typescript/example
> SLS package
Serverless: Configuration warning at 'service': unpublished property 'publish'
Serverless:
Serverless: Learn more about configuration validation here: http://slss.io/configuration-validation
Serverless:
# output content of this.serverless.cli.log in src/index.ts
# You can confirm that various values have been set correctly
Serverless: serverless-typescript values: {"stage” :"dev”, "region” :"ap-northeast-1", "accountId” :"XXXXXXXXXX”, "variables”: {"value1":" value1", "value2": 0, "value3": true, "profile” :"custom_profile” }}
Serverless: packaging service...
Serverless: excluding development dependencies...
It is OK if it can be confirmed that an appropriate value has been obtained from the log output in the plug-in in the standard output.
Operation verification has not yet been completed regarding profile switching with the Serverless plug-in, so I will check.
The settings for custom.serverless-typescript.profile in serverless.yml are already prepared, so specify an actual Profile name in ~/.aws/credentials.
serverless.yml
custom:
serverless-typescript:
profile: <プラグイン実行時に使用したい Profile 名>
To verify operation, add a description of the log output in src/index.ts.
src/index.ts
import * as Serverless from 'serverless'
import {
sharedInifileCreations,
config,
} from 'aws-SDK'
interface Variables {
value1: string
value2: number
value3: boolean
profile? : string
}
export default class Plugin {
serverless: serverless
options: Serverless.Options
hooks: {
[event: string]: () => Promise <void>
}
variables: variables
constructor (serverless: Serverless, options: Serverless.Options) {
this.serverless = serverless
this.options = options
this.variables = serverless.service.custom ['serverless-typescript']
this.hooks = {
'before: package: createDeploymentArtifacts': this.run.bind (this),
}
}
async run () {
if (! this.variables) {
this.serverless.cli.log (
`serverless-typescript: Set the custom.serverless-typescript field to an appropriate value. `,
)
return
}
const awsProvider = this.serverless.getProvider ('aws')
const region = await AWSProvider.getRegion ()
const accountId = await awsProvider.getAccountId ()
const stage = await AWSProvider.getStage ()
this.serverless.cli.log (
`serverless-typescript values: $ {JSON.stringify ({
stage: stage,
region: region,
accountId: accountId,
variables: this.variables,
})} `,
)
if (this.variables.profile) {
process.env.aws_SDK_load_config = 'true'
const credentials = new sharedInifileCredentials ({
profile: this.variables.profile,
})
config.credentials = credentials
//print a log to check if the profile has been switched
this.serverless.cli.log (`serverless-typescript profile: $ {JSON.stringify (config.credentials)} `);
}
}
}
module.exports = Plugin
I'll run cd example && npm start right away and check if the profile is being switched properly.
# Execution result when successful
CD example && npm start
#...
# The value that exists in ~/.aws/credentials is output in the accessKeyID field
Serverless: serverless-typescript profile: {"expired”: false, “expireTime”: null, “refreshCallbacks”: [], “accessKeyId” :"XXXXXXXXXXXX”, “profile” :"XXXXXXXXXXXX”, “disableAssumeRole”: false,” preferStaticCredentials”: false, “tokencodeFn”: null, “httpOptions”: null}
#...
Incidentally, when a profile that does not exist is specified, the output is as follows.
# Execution result in case of failure
CD example && npm start
#...
# Profile is not set correctly when the accessKeyID field does not exist
Serverless: serverless-typescript profile: {"expired”: false, “expireTime”: null, “refreshCallbacks”: [], “profile” :"custom_profile”, “disableAssumeRole”: false,” tokencodeFn”: null, “httpOptions”: null}
#...
I tried developing a serverless plug-in for the first time, and since I knew that it was easy to do, I felt that I wanted to actively plug in tasks that could be automated.
After plugging it in, not only will it be uploaded to the Git repository,NPM packages and GitHub Packages If you publish it as, it will be useful when using plug-ins later. Also, I recommend watching the library stats publicly because it's surprisingly fun and motivates development.