Serverless Frameworkで特定のタイミングで独自の処理を行うプラグインを作成する

2021.07.14

背景

Serverless Frameworkでデプロイ等を行う際になにか独自の処理をはさみたいということがあると思います。この課題をServerless Frameworkのプラグインを作って解決します。

環境

下記の環境で試しています。

  • Node.js: v14.16.1
  • Serverless Framework: v2.50.0

サンプルプロジェクトの作成

まず、serverlessコマンドでexampleというサンプルのプロジェクトを作成します。

$ serverless

 What do you want to make? AWS - Node.js - Starter
 What do you want to call this project? example

Downloading "aws-node" template...

Project successfully created in example2 folder

You are not logged in or you do not have a Serverless account.
 Do you want to login/register to Serverless Dashboard? No
 Do you want to deploy your project? No

... 略

プラグインの作成

次に単純に標準出力するだけのプラグインを作成してみます。

プラグイン用のディレクトリを作成してnpm initします。

$ mkdir plugin
$ npm init

package.jsonではmainindex.jsを指定するようにします。

{
  "name": "example-plugin",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {},
  "author": "",
  "license": "ISC"
}

この段階でディレクトリ構成は下記のようになります。

.
├── example                --> メインのコードが入ったディレクトリ
│   ├── handler.js
│   ├── package-lock.json
│   ├── package.json
│   └── serverless.yml
└── plugin                 --> プラグイン向けのディレクトリ
    └── package.json

次にindex.jsにプラグインを実装します。

$ cd plugin
$ touch index.js

まずはコンストラクタで標準出力するだけのコードを書いてみます。

// plugin/index.js
class ExamplePlugin {
  constructor() {
    console.log('this is a example-plugin');
  };
}

module.exports = ExamplePlugin;

このプラグインをserverless.ymlpluginsに指定します。通常はnpmパッケージの依存関係に追加した上でnpm installする必要がありますが、それ以外にもパス指定で追加することができます。

service: main

frameworkVersion: '2'

plugins:
  # プラグインは記述した順番に実行される
  # v1系だとこのパス指定する書き方はできない模様
  # https://www.serverless.com/framework/docs/providers/aws/guide/plugins#service-local-plugin
  - ../plugin

... 略

指定方法についての詳細は下記のドキュメントを参照ください。

Service local plugin

この状態で何かしらのコマンドを実行すると、コンストラクタに書いたthis is a example-pluginが標準出力されます。

$ sls deploy
this is a example-plugin
Serverless: Packaging service...
Serverless: Excluding development dependencies...

デプロイ前に確認を促すようにしてみる

もう少し具体的なサンプルを作ってみます。具体的には下記のようなものを目指します。

  • デプロイ前にデプロイする環境を表示する
  • デプロイするかどうかy/nで入力させる
  • y以外を入力するとデプロイを中止する

これを実現するためにはhooksという機能を使用します。

Advanced Plugin Development - Extending The Serverless Core Lifecycle

HyperBrain/package-plugin-lifecycle.md

標準入力にはNode.jsreadlineを使います。

14.x readline

先程のplugin/index.jsのコードを下記のように書き換えます。

// https://nodejs.org/docs/latest-v14.x/api/readline.html
const readline = require('readline');

class ExamplePlugin {
  constructor(serverless, options) {
    this.options = options;
    this.serverless = serverless;
    // AWS向けなので注意
    this.stage = this.serverless.providers.aws.getStage().toLowerCase();

    // coreライフサイクルを拡張する
    // https://www.serverless.com/blog/advanced-plugin-development-extending-the-core-lifecycle
    this.hooks = {
      'before:deploy:deploy': () => this.confirmDeploy(this.stage)
      // serverless frameworkがserverless.ymlに記述したリソースのチェックなどを行う前に実行したい前ならinitialize
      // 'before:deploy:initialize': () => this.confirmationDialog(this.stage)
    }
  };

  // readlineで入力を待ち受けるためのPromiseを返す関数
  confirmationDialog(stage) {
    const readLine = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    return new Promise((resolve, reject) =>
      readLine.question(`${stage} : Do you wanna deploy? [y/n]`, (answer) => {
      readLine.close();
      if (answer.toLocaleLowerCase() !== 'y') {
        // reject するとエラーで止まる
        reject("Deploy aborted!");
      }
      resolve();
    }));
  }
}

module.exports = ExamplePlugin;

これでプラグインの変更が終わったのでデプロイ時にメッセージが表示されるかどうか確認します。y以外の値を入力するとデプロイが中断されるという挙動になっていることが確認できました。

$ sls deploy --stage dev
dev : Do you wanna deploy? [y/n]n

 Exception -----------------------------------------------

  'Deploy aborted!'

     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

... 略

まとめ

思っていたよりも簡単に独自の処理を挟むことができました。プラグインもpackage.jsonに依存関係を指定しなくてもserverless.ymlにパス指定すれば動作するなど、開発も容易だと感じました。

参考

Plugins

Advanced Plugin Development - Extending The Serverless Core Lifecycle

Service local plugin

Node.js v14.17.3 documentation - readline

HyperBrain/package-plugin-lifecycle.md