IBM Cloud Functions（OpenWhisk）を使いNode-RED Flow定義をバージョン管理する

こんにちは、水津です。
この記事は、[IBM Cloud Advent Calendar 2017](https://qiita.com/advent-calendar/2017/bluemix) 23日目の投稿です。

# はじめに
Node-REDで開発する際に困るのが、フローのバージョン管理。特に複数人で開発していると、間違って更新し他人の更新を消しちゃうことも。今回はそんな悩みを解決するために、IBM Cloud上のFunctionsとNode-REDを活用し、Node-REDのフロー定義更新を自動的にGitに反映する、Node-RED Flowの簡易的なバージョン管理システムを作りたいと思います。

# 構成
今回はこんな構成での実装を行います。
<img width="726" alt="スクリーンショット 2017-12-23 23.02.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/e92042fb-7ba2-b8fd-72be-960201bafee1.png">


# IBM GitLab の準備
まずは、Flow定義の保存先（バージョン管理先）となるGitLabのリポジトリを準備します。今回は、IBM Cloudが提供してます[GitLab環境](https://git.ng.bluemix.net)を用いたいと思います。

- テキトウな名称で新規リポジトリを作成ください。
- 作成後、リポジトリの「Project ID」をメモください。（あとで、API CALLの設定を行う際、使用します）
 <img width="1058" alt="スクリーンショット 2017-12-23 23.09.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/b556c9ea-6972-1e78-f221-1c6ea42fcb57.png">
- アカウントの「Personal Access Token」を作成し、トークンをメモください。（あとで、API CALLの設定を行う際、使用します）
 <img width="1064" alt="スクリーンショット 2017-12-23 23.17.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/5bf36024-3163-5964-f3e6-e996841d0f88.png">

これでGitLabの準備は完了です。


# IBM Cloud Functions（OpenWhisk）の実装
続いて、Node-REDのFlow定義が保存されているCloudantの更新を検知し動かす、「Functions（機能）」の実装を行っていきたいと思います。

- 「機能」の開始 > 概要より、「作成の開始」をクリックし、まずはシーケンスを作成します。
 <img width="808" alt="スクリーンショット 2017-12-23 23.22.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/f0016a7d-68f4-570e-c527-23a1e4abe757.png">
- 任意のシーケンス名を入力、「Use Public」の「Websocket」を選択し、必要パラメータを入力します。（「uri」は後ほど行うNode-RED実装のWebsocket uriと同じ値にします）
 <img width="957" alt="スクリーンショット 2017-12-23 23.30.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/1ffb3b64-1719-e7a9-b3cb-60df67bbaa56.png">
- シーケンスのアクション一覧にて「Add」を押下し、もう一つアクションを追加します。
 <img width="1102" alt="スクリーンショット 2017-12-23 23.36.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/7b18c2ff-e4f3-4e31-6e76-82cd08a8ccac.png">

 アクションのコードは以下のように定義ください。

  ```javascript
  function main(params) {
     var msg = {};
     msg.payload = params.id;
     return msg;
  }
  ```
 その後、以下のような順番で保存ください。
 <img width="1033" alt="スクリーンショット 2017-12-23 23.47.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/a3a83695-3325-e32f-cb91-052a74691202.png">


- 続いて、作成したシーケンスにトリガーを追加します。追加するトリガーは、「Cloudant」を選択ください。
 <img width="1138" alt="スクリーンショット 2017-12-23 23.51.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/da02fc99-95e9-2eb2-8dcd-e7c6075ada2e.png">

 登録し終わると、以下のようになります。
 <img width="1042" alt="スクリーンショット 2017-12-23 23.54.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/a4155ee4-95f0-a3ba-1b53-37b34847f7a6.png">


これで、CloudantのUpdateを検知し、WebsocketでUpdateされたDocumentのIDをSendする、Functions（機能）の実装が完了しました。




# Node-RED の実装
続いて、Functions（機能）から送られてきたDocumentのIDをもとに、Flow定義をGitにPushするNode-REDのフローを定義します。

- まずは、Node-REDにGitLabに接続するためのNodeを追加します。パレットの管理より、ノードの追加で「gitlab」を検索し、[node-red-contrib-gitlab](https://flows.nodered.org/node/node-red-contrib-gitlab) ノードを追加ください。（私の自作ノードです）
 <img width="614" alt="スクリーンショット 2017-12-24 0.01.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/1e0d900b-a7b4-a6ea-11f8-4fd7a214b78c.png">

- その後、以下のようにフローを定義ください。
 <img width="1045" alt="スクリーンショット 2017-12-24 0.05.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/6d499b0b-7a35-29f6-be91-7dbb802392a4.png">

フロー定義は以下になります。

```json

[
    {
        "id": "f457b9b7.dea6d",
        "type": "websocket in",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "server": "166ec69b.fef401",
        "client": "",
        "x": 110,
        "y": 100,
        "wires": [
            [
                "80dfb138.ecced",
                "1b444420.62923c"
            ]
        ]
    },
    {
        "id": "80dfb138.ecced",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 270,
        "y": 100,
        "wires": []
    },
    {
        "id": "b03e8025.2dc4f8",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "false",
        "x": 650,
        "y": 400,
        "wires": []
    },
    {
        "id": "6972a335.8a49f4",
        "type": "GitLab-Update-RepositoryFile",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "branch_name": "master",
        "encoding": "text",
        "gitlabconfig": "fa916f31.d3a12",
        "x": 430,
        "y": 400,
        "wires": [
            [
                "b03e8025.2dc4f8"
            ]
        ]
    },
    {
        "id": "d8082f39.a124",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 1030,
        "y": 540,
        "wires": []
    },
    {
        "id": "87c6cbda.c168c",
        "type": "catch",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "scope": [
            "6972a335.8a49f4"
        ],
        "x": 360,
        "y": 440,
        "wires": [
            [
                "4d03b085.c2dcc",
                "f4070782.4803a8"
            ]
        ]
    },
    {
        "id": "4d03b085.c2dcc",
        "type": "switch",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "property": "_error.statusCode",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "400",
                "vt": "num"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "outputs": 2,
        "x": 390,
        "y": 560,
        "wires": [
            [
                "5055bcc.3113cc4"
            ],
            [
                "9780af03.3d19f"
            ]
        ]
    },
    {
        "id": "9780af03.3d19f",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 530,
        "y": 580,
        "wires": []
    },
    {
        "id": "7dde70e9.f74138",
        "type": "GitLab-Create-RepositoryFile",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "branch_name": "master",
        "encoding": "text",
        "gitlabconfig": "fa916f31.d3a12",
        "x": 820,
        "y": 540,
        "wires": [
            [
                "d8082f39.a124"
            ]
        ]
    },
    {
        "id": "bee7ea4.93eca18",
        "type": "comment",
        "z": "4ab9fe27.86f9c8",
        "name": "IBM Cloud Functions からMSG受信",
        "info": "",
        "x": 180,
        "y": 60,
        "wires": []
    },
    {
        "id": "9c5b58f8.0a78d",
        "type": "cloudant in",
        "z": "4ab9fe27.86f9c8",
        "name": "Flow定義を取得",
        "cloudant": "",
        "database": "nodered",
        "service": "KS-Test-20171211-cloudantNoSQLDB",
        "search": "_id_",
        "design": "",
        "index": "",
        "x": 440,
        "y": 140,
        "wires": [
            [
                "26d24ca.e909734"
            ]
        ]
    },
    {
        "id": "947eff54.91dc6",
        "type": "function",
        "z": "4ab9fe27.86f9c8",
        "name": "Git PUSH MSG作成",
        "func": "var payloadMessage = {\n    \"file_path\": msg.process._id + \".json\",\n    \"content\": msg.payload,\n    \"commit_message\": \"Update Node-RED Flow\"\n};\n\nmsg.payload = payloadMessage;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 480,
        "y": 260,
        "wires": [
            [
                "7707921c.c4755c",
                "6972a335.8a49f4"
            ]
        ]
    },
    {
        "id": "1b444420.62923c",
        "type": "function",
        "z": "4ab9fe27.86f9c8",
        "name": "IDを退避",
        "func": "msg.process =  {\n    \"_id\" : msg.payload\n};\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 280,
        "y": 140,
        "wires": [
            [
                "9c5b58f8.0a78d"
            ]
        ]
    },
    {
        "id": "7707921c.c4755c",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 690,
        "y": 260,
        "wires": []
    },
    {
        "id": "26d24ca.e909734",
        "type": "json",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "pretty": true,
        "x": 290,
        "y": 260,
        "wires": [
            [
                "efda5601.e13fe8",
                "947eff54.91dc6"
            ]
        ]
    },
    {
        "id": "34bb144a.7dbd0c",
        "type": "comment",
        "z": "4ab9fe27.86f9c8",
        "name": "Flow定義をbase64変換",
        "info": "",
        "x": 340,
        "y": 300,
        "wires": []
    },
    {
        "id": "ce0f378e.d9db9",
        "type": "comment",
        "z": "4ab9fe27.86f9c8",
        "name": "ファイル更新",
        "info": "",
        "x": 490,
        "y": 360,
        "wires": []
    },
    {
        "id": "3d9fa74c.87c2f8",
        "type": "comment",
        "z": "4ab9fe27.86f9c8",
        "name": "ファイル新規登録",
        "info": "",
        "x": 790,
        "y": 500,
        "wires": []
    },
    {
        "id": "dfe5779d.fb3b28",
        "type": "comment",
        "z": "4ab9fe27.86f9c8",
        "name": "まだファイルがなく失敗した場合",
        "info": "",
        "x": 250,
        "y": 480,
        "wires": []
    },
    {
        "id": "efda5601.e13fe8",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "false",
        "x": 450,
        "y": 220,
        "wires": []
    },
    {
        "id": "f4070782.4803a8",
        "type": "debug",
        "z": "4ab9fe27.86f9c8",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "true",
        "x": 510,
        "y": 440,
        "wires": []
    },
    {
        "id": "5055bcc.3113cc4",
        "type": "function",
        "z": "4ab9fe27.86f9c8",
        "name": "Git PUSH MSG作成",
        "func": "msg.payload = msg.inputMessage.payload;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 580,
        "y": 540,
        "wires": [
            [
                "7dde70e9.f74138"
            ]
        ]
    },
    {
        "id": "166ec69b.fef401",
        "type": "websocket-listener",
        "z": "",
        "path": "/ws/push",
        "wholemsg": "false"
    },
    {
        "id": "fa916f31.d3a12",
        "type": "gitlab-config",
        "z": "",
        "url": "https://git.ng.bluemix.net/api/v3",
        "project_id": "11111",
        "name": "TEST"
    }
]
```

これで実装は完了です。

# 使ってみる

試しに動かしてみます。今回は（別の環境作ってないので）今回作成したNode-REDのフローの変更を検知しGitにPushさせる形で試します。

- Node-RED上で「デプロイ」ボタンを押します。そうすると、まずはFanctionsが走ります。Functionsでは以下のようなログが確認できます。
 <img width="646" alt="スクリーンショット 2017-12-24 0.52.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/3d6ddccf-590f-9d95-adfd-88e5071f3d4a.png">

 その後、Node-RED側に処理がわたり、Gitに（初回は）新規ファイルが追加されます。
 <img width="1043" alt="スクリーンショット 2017-12-24 0.38.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/9bfb898c-47ee-60eb-8c96-39dc4ecf7765.png">

- 試しに更新してみます。フローを少し変更し、「デプロイ」を押します。そうするとファイルの更新が行われ、Git上ではdiffも確認することができます。
 <img width="930" alt="スクリーンショット 2017-12-24 0.42.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/142110/27ebd3c7-fe27-8339-93b1-bdddd45cf69d.png">


# まとめ
いかがでしたでしょうか。これでFlow更新時のバージョン管理が自動的に行われるようになりますので、Node-REDで複数人でフロー開発する際も安心して進められるのでは、と思います！