Alibaba Cloud Function Computeのトリガーを全種試す

# Alibaba Cloud Function Computeのトリガーを全種試す
# はじめに
本稿は[株式会社オープンストリーム Advent Calendar 2019](https://qiita.com/advent-calendar/2019/opst)の14日目です。
図らずも二年連続で14日目だったので今年はあえてこの日を選びました。とはいえ深い意味はありません。

[Alibaba CloudのFunction Compute](https://jp.alibabacloud.com/product/function-compute)はフルマネージ型の関数実行サービスです。AWSでいうところのLambdaですね。
さまざまなイベントトリガーをサポートしているので全種試してみたいと思います。
それぞれイベントがどのような形式で送信されるのか見ていきましょう。

# トリガー一覧
Function Computeの関数の設定から指定できるトリガーの種類は下記のとおりです。
- OSSトリガー
- Log Serviceトリガー
- タイムトリガー
- CDNトリガー
- Table Storeトリガー
- MNSトリガー

これらに加え、作成時のみ設定可能なHTTPトリガーと、連携元の方で設定されるAPI Gatewayトリガーがあるため全部で8種類です。

# 関数
今回用意した関数は以下の通りです。

```js:index.js
'use strict';

exports.dump = (event, context, callback) => {
  console.log('event:', event.toString());
  const response = {
    statusCode: 200,
    body: event.toString(),
  };
  callback(null, response);
};
```

イベントをログ出力するだけの単純なものです。Node.jsの場合``event``はBufferで渡される場合もあるので``toString()``を呼んでいます。``response``はAPI Gatewayトリガーを意識してのものです。他のトリガーでは使いません。
この関数をServerless Frameworkでデプロイしていきます。

```yaml:serverless.yml
service: myproject
provider:
  name: aliyun
  runtime: nodejs10
  credentials: ~/.aliyuncli/credentials
  stage: dev
  region: ${env:ALIYUN_REGION}

plugins:
  - serverless-aliyun-function-compute

functions:
  dump:
    handler: index.dump
    events:
      - http:
          path: /dump
          method: get
      - oss:
          sourceArn: acs:oss:${env:ALIYUN_REGION}:${env:ALIYUN_ACCOUNT_ID}:${env:OSS_BUCKET_NAME}
          triggerConfig:
            events:
              - oss:ObjectCreated:*
```
今のところAPI GatewayトリガーとOSSトリガーのみサポートされているので他は手動で設定します。[^1]

# 実践
## API Gatewayトリガー
API Gatewayトリガーは当然リクエストの情報がイベントとして送信されます。

```
$ curl -s http://<api-gateway-id>-ap-northeast-1.alicloudapi.com/dump | jq
```

```json
{
  "body": "",
  "headers": {
    "X-Ca-Api-Gateway": "01234567-89AB-CDEF-0123-456789ABCDEF",
    "X-Real-IP": "xxx.xxx.xxx.xxx",
    "X-Forwarded-Proto": "http",
    "X-Forwarded-For": "xxx.xxx.xxx.xxx",
    "User-Agent": "curl/7.29.0",
    "Accept": "*/*",
    "CA-Host": "00112233445566778899aabbccddeeff-ap-northeast-1.alicloudapi.com"
  },
  "httpMethod": "GET",
  "isBase64Encoded": true,
  "path": "/dump",
  "pathParameters": {},
  "queryParameters": {}
}
```
## OSSトリガー
testというファイルがあると思って読んでください。

```
aliyun oss cp test oss://<bucket-name>/test
```
出力されたイベントを整形したものが以下になります。

```json
{
  "events": [
    {
      "eventName": "ObjectCreated:PostObject",
      "eventSource": "acs:oss",
      "eventTime": "2019-12-13T07:58:49.000Z",
      "eventVersion": "1.0",
      "oss": {
        "bucket": {
          "arn": "acs:oss:ap-northeast-1:<account-id>:<bucket-name>",
          "name": "<bucket-name>",
          "ownerIdentity": "<owner-account-id>",
          "virtualBucket": ""
        },
        "object": {
          "deltaSize": 210,
          "eTag": "00112233445566778899AABBCCDDEEFF",
          "key": "test",
          "size": 10
        },
        "ossSchemaVersion": "1.0",
        "ruleId": "00112233445566778899aabbccddeeff01234567"
      },
      "region": "ap-northeast-1",
      "requestParameters": {
        "sourceIPAddress": "xxx.xxx.xxx.xxx"
      },
      "responseElements": {
        "requestId": "00112233445566778899AABB"
      },
      "userIdentity": {
        "principalId": "<principal-id>"
      }
    }
  ]
}
```
## Log Serviceトリガー
出力されたイベントを整形したものが以下になります。ログの内容は届かないので改めてSDKで取得する必要があるようです。

```json
{
  "parameter": {
    "source": {
      "endpoint": "http://ap-northeast-1.log.aliyuncs.com"
    },
    "target": {
      "endpoint": "http://ap-northeast-1.log.aliyuncs.com",
      "logstoreName": "<log-store-name>",
      "projectName": "<log-project-name>"
    }
  },
  "source": {
    "endpoint": "http://ap-northeast-1.log.aliyuncs.com",
    "projectName": "<log-project-name>",
    "logstoreName": "<log-store-name>",
    "shardId": 0,
    "beginCursor": "MTU3NjIyNjEzOTEyNTAwMjE2NA==",
    "endCursor": "MTU3NjIyNjEzOTEyNTAwMjE2NQ=="
  },
  "jobName": "00112233445566778899aabbccddeeff01234567",
  "taskId": "00112233-4455-6677-8899-aabbccddeeff",
  "cursorTime": 1576228992
}
```
## タイムトリガー
出力されたイベントを整形したものが以下になります。``payload``はトリガーに設定した文字列がそのまま届きます。

```json
{
  "triggerTime": "2019-12-13T09:50:00Z",
  "triggerName": "<trigger-name>",
  "payload": "<paylod>"
}
```
## CDNトリガー
出力されたイベントを整形したものが以下になります。CDNのイベントで何か処理をしたいというシチュエーションが想像できませんでした。

```json
{
  "events": [
    {
      "eventName": "CachedObjectsRefreshed",
      "eventSource": "cdn",
      "region": "cn-hangzhou",
      "eventVersion": "1.0.0",
      "eventTime": "2019-12-13T18:21:40+08:00",
      "userIdentity": {
        "aliUid": "<account-id>"
      },
      "eventParameter": {
        "completeTime": 1576232499,
        "createTime": 1576232492,
        "domain": "cdn.example.com",
        "objectPath": [
          "/index.html"
        ],
        "objectType": "File",
        "taskId": 1234567890
      },
      "traceId": "00112233-4455-6677-8899-aabbccddeeff"
    }
  ]
}
```
## Table Storeトリガー
トリガーは発動したのですが何故かJSONではありませんでした。
どうやらTable StoreトリガーのイベントはCBORという形式らしいです。[^2]
ちょっと調査が追いつかないので後回しにします。

```
 \ufffdgVersiongSync-v1gRecords\ufffd\ufffdgColumns\ufffd\ufffdjColumnNamednameiTimestamp\u001b
 ```
## MNSトリガー
出力されたイベントを整形したものが以下になります。

```json
{
  "TopicOwner": "<owner-account-id>",
  "Message": "<message-payload>",
  "Subscriber": "<subscriber-account-id>",
  "PublishTime": 1576235089696,
  "SubscriptionName": "<subscription-name>",
  "MessageMD5": "00112233445566778899AABBCCDDEEFF",
  "TopicName": "<topic-name>",
  "MessageId": "00112233445566778899AABBCCDDEEFF"
}
```
## HTTPトリガー
HTTPトリガーは他のトリガーと異なり関数の作成時にしか設定できません。また、呼び出される関数の引数も独特です。以下は関数作成時に生成されたソースコードから不要な箇所を取り除いたものですが、引数がどのようなものかは把握できるかと思われます。

```js
 var getRawBody = require('raw-body');

 module.exports.handler = function(req, resp, context) {
    console.log('hello world');

    var params = {
        path: req.path,
        queries: req.queries,
        headers: req.headers,
        method : req.method,
        requestURI : req.url,
        clientIP : req.clientIP,
    }

    getRawBody(req, function(err, body) {
        for (var key in req.queries) {
          var value = req.queries[key];
          resp.setHeader(key, value);
        }
        params.body = body.toString();
        resp.send(JSON.stringify(params, null, '    '));
    });
}
```

# まとめ
AWS Lambdaと同様にさまざまなイベントを起点に関数を実行できるFunction Computeの各種イベントの形式を見てきました。いつかトリガーを利用する際に参照できるようにイベント形式を並べておきましたが、皆様の参考にもなれば幸いです。

# 余談
試す上で詰まったのがトリガーの権限でした。ロールを選択または作成するのですが、難しいことは考えずに「クイック承認」を使用するのが最も無難な選択だということに気づくまでに多くの時間がかかってしまいました。

[^1]: https://serverless.com/framework/docs/providers/aliyun/events/
[^2]: https://jp.alibabacloud.com/help/doc-detail/100092.htm