Serverless Framework v1.0.0-rc.1でdeployコマンドをフックするプラグインを作る

Serverless Frameworkもv1.0.0-rc.1が出てきて、いよいよ正式リリースが待ち遠いですね。
今回は、開発を進めていく中でデプロイ時にフレームワーク内で生成されたものに対して手を加えたい。といった場合を想定して、そのためのプラグインを作ってみたいと思います。

# 動作環境
* Serverless v1.0.0-rc.1
* nodejs(v4.3.2)

# 今回の目標
1. deployコマンドをフックする。
2. serverlessのdeployコマンド内で作られたCloudFormationテンプレートに手を加える。
3. APIGatewayのステージ変数を設定する。
※ステージ変数は、serverless.yml の中で

```
custom:
  stageVariables:
    stageValue1: value1
    stageValue2: value2
```
のようにカスタム変数で定義されているstageVariablesを設定します。

上記のことをやっていこうと思います。

# deployコマンドをフックする
## クラスの作成
プラグインに関する基本的な情報は[公式ドキュメント](https://github.com/serverless/serverless/blob/ffe3008fde7e47dc2a9e018662be6fb1d29ae86b/docs/04-extending-serverless/01-creating-plugins.md)に載っています。
まずは枠のクラスだけ作ってしまいましょう。

```
'use strict';

const _ = require('lodash');

class StageVariables {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;
    this.provider = 'aws';
  }
};

module.exports = StageVariables;
```

## フックのトリガーを決める
deployのフックを行いたいため、deployコマンドの[ライフサイクル](https://github.com/serverless/serverless/blob/ffe3008fde7e47dc2a9e018662be6fb1d29ae86b/docs/04-extending-serverless/02-creating-provider-plugins.md#deployment-lifecycle)を確認します。

* deploy:initialize
* deploy:setupProviderConfiguration
* deploy:compileFunctions
* deploy:compileEvents
* deploy:createDeploymentArtifacts
* deploy:deploy

いろいろと書いてありますが、
deploy:compileEvents

```
After that the events which are defined in the serverless.yml file on a per function basis should be compiled to provider specific resources and also stored into memory.
```
こちらが良さそうです。

## フックする処理の記述
compileEventsをフックする処理をconstructor内に書き、そこから呼び出される関数を書きます。

```
  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;
    this.provider = 'aws';

    this.hooks = {
      'after:deploy:compileEvents': this.compileStageVariables.bind(this),
    };
  compileStageVariables() {
    // ここに実際の処理を書きます
    console.log('hook deploy:compileEvents');
  }
```

これでcompileEventsがフックできます。
一度実行してみましょう

実際にデプロイされてしまうと既存の環境を壊してしまう可能性があるので、 `--noDeploy` オプションを忘れずに

```
% sls deploy --stage pluginTest -v --noDeploy
Serverless: Packaging service...
hook deploy:compileEvents
Serverless: Did not deploy due to --noDeploy
```

deployコマンドの中でコンソールログが出力されることが確認できました。うまくフックできているようです。

# serverlessのdeployコマンド内で作られたCloudFormationテンプレートに手を加える
CloudFormationテンプレートを格納しているおブジェクトがどこか、ざっとドキュメントを読んだだけでは見当たらなかったのですが`serverless/lib/plugins/aws/deploy/compile/events/apiGateway/lib/deployment.js`を読んだところ、 `this.serverless.service.provider.compiledCloudFormationTemplate` というオブジェクトに格納されているようです。
今回はリソースの変更をする予定なので、 `compiledCloudFormationTemplate.Resources` に手を加えます

# APIGatewayのステージ変数を設定する
## 使用するテンプレートの確認
APIGatewayでのステージ変数が設定可能なCloudFormationを確認したところ、`AWS::ApiGateway::Deployment` と `AWS::ApiGateway::Stage` で可能なようです。
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-deploymentid

今回は`AWS::ApiGateway::Deployment` に手を加えていきます。
このテンプレート内の [StageDescription](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-deployment-stagedescription.html) に値を設定すればいいはずです。

## Serverless Framework内での命名の確認
Serverless Framework内で使用されているリソース名は[Cloudformation Resource Reference](https://github.com/serverless/serverless/blob/ae3d71198edecf7362b6b05aa8aaeb40731dff7e/docs/02-providers/aws/04-resource-names-reference.md) で確認できます。

`AWS::ApiGateway::Deployment` の項目を見ると、`ApiGatewayDeployment{randomNumber}` と書いてありますね。
内部でランダムな数字を振っているようです。

## ApiGatewayDeployment の値を書き換える
まずはステージ変数を格納するオブジェクトを作成しておきます。

```
const stageVariables = {
  Variables : {}
};
_.forEach(this.serverless.service.custom.stageVariables, (val, key) => {
  stageVariables.Variables[key] = val;
});
stageVariables.StageName = this.options.stage;
```

次に、CloudFormation内で`ApiGatewayDeployment`で始まるキーを探して値を書き換えます。

```
_.forEach(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, (val, key) => {
  if (key.startsWith('ApiGatewayDeployment')) {
    if (!val.Properties.StageDescription) {
      val.Properties.StageDescription = stageVariables;
    }
  }
});
```

これでステージ変数の格納が可能なはずです。最後に実行してみましょう。

```
% sls deploy --stage pluginTest -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
CloudFormation - CREATE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Policy - IamPolicyLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1474550078492
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Deployment - ApiGatewayDeployment1474550078492
CloudFormation - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - plugin-test-pluginTest
CloudFormation - UPDATE_COMPLETE - AWS::CloudFormation::Stack - plugin-test-pluginTest
Serverless: Stack update finished...

Service Information
service: plugin-test
stage: pluginTest
region: ap-northeast-1
```

上手くいったようです。APIGatewayの管理画面も確認してみます。

![スクリーンショット 2016-09-22 22.24.36.png](https://qiita-image-store.s3.amazonaws.com/0/63155/4ece5f95-2e87-596b-0586-9e98570df8f5.png)

きちんとステージ変数が設定されていますね。

# 最後に
荒削りな部分も多いServerless　Frameworkですが、内部的にはCloudFormationのテンプレートを作ってそこから反映。という形をとってるので、CloudFormationの書き方さえ覚えてしまえば、どんどん拡張していけそうです。
serverlessという構成を取るために考えることや、専用の設計などやることはたくさんありますが、かなり実用的になってきているように思えます。
今回のコードはgithubでも公開していますので、プラグインを作る際の参考にでもなればと思います。
[github](https://github.com/quartette/serverless-plugins-sample/blob/master/stage-variables-plugin.js)