GitlabのPipeline通知をRaspberry piで受ける方法の話

先日からWebhook実装方法調べたりMQTTやったりしていました。
今回は実際に組み合わせて１本の通知プログラム？を実装してみたいと思います。

関連記事はこちら

- [GitlabからのWebhook(Pipeline)の調査の話 (2022年5月23日)](https://www.mmpp.org/archives/2891)
- [MQTT(Mosquitto)サーバの構築の話 (2022年5月28日)](https://qiita.com/wataru775/items/2bb2c7adedc5fd93c936)
- [LaravelにてMQTTメッセージを発行する方法の話 (2022年5月29日)](https://qiita.com/wataru775/items/77c29a1e8e29424b0b05)
- [Raspberry pi(paho)でのMQTTメッセージ購読の話 (2022年5月31日)](https://qiita.com/wataru775/items/3090c582f9b897bff7a6)

# 出来上がりプログラム
出来上がりのプログラムの動作をまとめますと

GitlabにおいてCI/CDを実行するPipelineを監視します。 ← これは Gitlabがやってくれます。
GitlabではPipelineの状態が変化するとWebhookで通知されます。
WebhookインタフェイスではGitlabから通知されるとMQTTサーバにメッセージを発行します。
MQTTサーバにメッセージが到着すると購読しているRaspberry piに通知が来る。
Raspberry piでは購読されたMQTTメッセージを解析して標準出力にPipelineの状態を出力する
本来はこれ以降に色々作れるでしょうが、ソフトウェア的にはここまでとしました。

構成図はこちら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/d97ba5d3-2395-daac-2eab-450ac03376d9.png)

Pipelineで通知される状態は以下です

- pending : 実行準備中
- running : 実行中
- success : 成功
- fail : 失敗

# Webhook APIの実装
Gitlabのpipelineの状態が変化するとWebhookで送信されます。

今回このWebhookのAPIをLaravelにて実装を行いました。

APIの動作は以下の様な動作をします。
- Webhook受け口
- MQTTメッセージの発行

この二つを繋いだだけです。

```php
<?php
namespace App\Http\Controllers;
 
use App\PipelineMessage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use PhpMqtt\Client\Facades\MQTT;
use DateTime;
use DateTimeZone;
 
class WebhookController extends Controller
{
    public function test(Request $request ){
        if($request->headers->get('x-gitlab-event') != 'Pipeline Hook'){
            return '';
        }
 
        // Pipelineメッセージを作成します
        $pipelineMessage =  new PipelineMessage();
        $pipelineMessage->project = 'tsundere-book';
        $pipelineMessage->type = $request->headers->get('x-gitlab-event');
        $pipelineMessage->id = $request->object_attributes['id'];
        $pipelineMessage->status = $request->object_attributes['status'];
        $pipelineMessage->created_at = $this->parseDateTime($request->object_attributes['created_at'])->format(DateTime::ATOM);
        $pipelineMessage->finished_at = $request->object_attributes['finished_at'] != null ? $this->parseDateTime($request->object_attributes['finished_at'])->format(DateTime::ATOM) : '';
         
        // メッセージの作成
        $jsonMessage = json_encode($pipelineMessage);
 
        // MQTTメッセージを発行する
        MQTT::publish('pipeline', $jsonMessage);
 
        return "";
    }
 
    public function parseDateTime(string $datetime_value) : ?DateTime {
        $datetime = DateTime::createFromFormat('Y-m-d H:i:s e',$datetime_value);
        $tz = new DateTimeZone('ASIA/Tokyo');
        $datetime->setTimezone($tz);
        return $datetime;
    }
}
```
ポイントは
- 通知メッセージはJSONの書式とする
- MQTTのtopicは”pipeline”
- Webhookから来る情報は”Pipeline Hook”以外は無視する
- 日付は試験しやすい様にTimezoneを日本にしている
- Raspberry piの日付処理によってはUTCでも良いかも…
- GitlabのバグでWebhooksテストの日付が違うので焦らない様に
想定 : 2022-05-30 23:05:31 UTC
バグ : 2022-05-31T22:27:55.545Z

# Raspberry pi 実装
MQTTサーバへ発行されたMQTTメッセージは Raspberry piにて購読されます。

その内容を標準出力するプログラムの実装はこんな感じです。

on_message以外は前回と変わりません。

```python3
#! /usr/bin/python3
import paho.mqtt.client as mqtt
import json
 
host = '192.168.1.201'
port = 1883
topic = 'pipeline'
 
def on_message(client, userdata, msg):
    print(str(msg.payload,'utf-8'))
    message = json.loads(str(msg.payload,'utf-8'))
    print("status : " + message['status'])
 
def run():
    client = mqtt.Client()
    client.connect(host, port=port, keepalive=60)
     
    client.subscribe(topic)
    client.on_message = on_message
 
    client.loop_forever()
 
if __name__ == '__main__':
   run()
```

ポイントは
- MQTTのtopic “pipeline”を購読している
- MQTTメッセージの内容をJSONで解析している
- MQTTメッセージの解釈内容からstatusを抽出して出力する

# 動作検証
実装が完了しました。
次は動作検証を行いたいと思います。

Raspberry piにおいて購読プログラムを実施した状態で
GitlabのCI/CDを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/dee79509-1ca7-b393-184a-9824fd6fb1c7.png)
パイプラインを実行します。(Run pipeline)

ジョブの状態を確認すると実行中となっています…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/8302ec38-1f60-a5bb-5b72-d28a66ad2504.png)
しばらくすると成功状態となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/c1835691-1a74-e870-22f9-1378a96134ec.png)
その時の購読プログラムの標準出力…

```bash
{"type":"Pipeline Hook","project":"tsundere-book","id":512,"status":"pending","massage":"","created_at":"2022-06-01T07:27:55+09:00","finished_at":""}
status : pending
{"type":"Pipeline Hook","project":"tsundere-book","id":512,"status":"running","massage":"","created_at":"2022-06-01T07:27:55+09:00","finished_at":""}
status : running
{"type":"Pipeline Hook","project":"tsundere-book","id":512,"status":"success","massage":"","created_at":"2022-06-01T07:27:55+09:00","finished_at":"2022-06-01T07:28:24+09:00"}
status : success
```

pending(準備中)、実行中(running)、成功(success)の状態は取れました！

# 改善が必要な部分
今回は対策はしませんが製品とする場合には対策が必要な部分をまとめておきたいと思います。

「pendingが早いとrunningが先に出る。」

runningが来てpendingですがidがありますのでそれで状態は把握は可能ですが、
Raspberry piのコードがめんどくさいかな…

「GitlabのWebhooksでのテストパケットで落ちる」

書式誤りに対応させるかはアレですが、これはした方がいいですね。
今は500エラーなので

「再実行の場合にfinished_atに値が入っている」

一度実行したために測定時間があるために終了日時が入っているとは思います。
一貫性がないのでこれを判断に使うのは難しいかな
新規実行 : null
再実行 : 日時あり

# 終いに
今回はGitlabのPipelineの状態を監視してRaspberry piで処理が行えました。
しかし、標準出力までしかしておりませんのでアレです。
今後、Pipeline statusによって機器の出力を変えてやれば出来上がりですね。

次回以降はこの電子工作周りがメインになりそうですね

記事 : https://www.mmpp.org/archives/2934