RedmineのAPIをRundeckで実行して定期タスクを自動登録

# 背景
サービス運用の課題管理とタスク管理をRedmineで行っている。
このうち、タスク管理において、継続的に発生するタスクを定期タスクとして登録したいと考えたが、Redmineには該当する機能がなかった。
手動でチケットを登録していると作業漏れの要因にもなるし、なにより面倒くさいので、自動化しようと考えた。

# 前提条件
- 複数メンバで運用するサービスであるため、定期タスクとして登録している事項が容易に共有できる
- 各定期タスクについて、備考などのメモ書きを一元管理できる
＝別の管理台帳を作成するようなことはしない。


# アプローチ
同様の要望は多いようで、プラグインもいくつか見つかったが、上記前提条件に当てはまると思えるものは見つけられなかった。
また、開発が止まっているプラグインも少なくなかった。

https://github.com/nutso/redmine-plugin-recurring-tasks/

https://github.com/jperelli/Redmine-Periodic-Task

そこで、紹介されていたcron+shellで登録する方法を参考に、cronではなくRundeckでshellを実行することにした。
Rundeckでジョブを管理することで、各ジョブの注釈にメモ書きを残すこともできるため、一元管理もできると考える。

https://redmine.tokyo/issues/273#change-3282

# 構築するコンテナの構成
- RedmineとRundeckのそれぞれについて、バックエンドDBをMySQLで構成する
- Docker(Docker-Compose)を用いて、コンテナを作成する際、引数(環境変数)の指定で作成できるデータベースは1つ[^1]なので、コンテナを分けて2つのDBを構築する
- Redmine APIを実行する際、チケットを作成する対象のプロジェクトやトラッカーをIDで指定する必要がある
- APIで参照/取得することもできるが、各情報を取得するためのAPIコマンドを実行する必要があるため、Adminer(データベース管理ツール)のコンテナを立てて、直接DBを参照できるようにする

![configuration.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/8e329d9e-d879-3368-7e2c-01b29fd9439a.png)



# 構築
### フォルダ構成
``` 
redmine 
 ├─ redmine
 │  └─ files      : 各チケットに添付したファイルや画像データ
 ├─ db_redmine
 │  ├─ db         : dbデータを格納
 ├─ rundeck
 │  ├─ data 　　  : rundeckのデータを格納
 │  ├─ logs 　 　 : rundeckで実行したジョブの実行結果を格納
 │　└─ Dockerfile : Rundeckのコンテナをビルドするための定義ファイル
 ├─ db_rundeck
 │  └─ db     : dbデータを格納
 └─ docker-compose.yaml
```
adminerは永続化するデータが存在しないので、フォルダを作成しない。
ただし、表示テーマを変更する際は、フォルダを作成してCSSを格納しておく。

Redmineの構築に関しては以下を参照

https://qiita.com/piityan1126/items/5522a7d0514cb64728a5

Rundeckの構築に関しては以下を参照

https://qiita.com/piityan1126/items/2a7bd2c9830d8436a217

ここでは、作成したdocker-composeを記載する。

### docker-compose.yaml
```
version: '3.8'

services:
  redmine:
    image: redmine:5.1.3
    ports:
      - 8888:3000
    volumes:
      - ./redmine/plugins:/usr/src/redmine/plugins
      - ./redmine/themes:/usr/src/redmine/public/themes
    environment:
      REDMINE_DB_MYSQL: db_redmine
      REDMINE_DB_PASSWORD: example
      REDMINE_SECRET_KEY_BASE: supersecretkey
      
  db_redmine:
    image: mysql:5.7
    volumes:
      - ./db_redmine/db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: redmine
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
      
  rundeck:
    build: rundeck/.
    environment:
      RUNDECK_DATABASE_DRIVER: org.mariadb.jdbc.Driver
      RUNDECK_DATABASE_USERNAME: rundeck
      RUNDECK_DATABASE_PASSWORD: rundeck
      RUNDECK_DATABASE_URL: jdbc:mysql://db_rundeck/rundeck?autoReconnect=true&useSSL=false
      RUNDECK_GRAILS_URL: http://＜ホストサーバのIPアドレス｜ホスト名称＞:4440
    volumes:
      - ./rundeck/data:/home/rundeck/server/data
      - ./rundeck/logs:/home/rundeck/var/logs
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 4440:4440

  db_rundeck:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=rundeck
      - MYSQL_USER=rundeck
      - MYSQL_PASSWORD=rundeck
      - TZ=Asia/Tokyo
    volumes:
      - ./db_rundeck/db:/var/lib/mysql
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci

  adminer:
    image: adminer
    ports:
      - 8080:8080
```

### RundeckのDockerfile
```
FROM rundeck/rundeck:5.9.0

# RedmineのAPIから返されるJSONを解析するためにjqをインストール
USER root
RUN apt-get update && apt-get install -y \
    jq && \
    rm -rf /var/lib/apt/lists/*
```

# 起動
```
user@server:/home/com/rundeck$ docker-compose up -d
```

# RundeckでRedmine APIを実行
### 1. Redmineの初期設定
Redmineにログインしてプロジェクトを作成し、チケットの作成に必要な設定を行う。
* ＜共通設定＞
    - プロジェクト情報
    プロジェクト名：プロジェクト1、プロジェクト識別子：project1
    - チケットのステータス
    名称：新規、対応中、完了
    - トラッカー情報
    名称：トラッカー1、デフォルトのステータス：新規
    - ロール
    名称：管理者
    - 選択肢の値
    チケットの優先度: 高、中、低
* ＜プロジェクト設定＞
    - メンバ
    Redmine Admin: 管理者
    - チケットトラッキング
    トラッカー1

### 2. Redmine APIの有効化
「RESTによるWebサービスを有効にする」をチェックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/0a0edaae-d85d-5cf1-27a2-6bd565309f3e.png)

### 3. Redmine API実行の必要情報を取得
3-1. アクセスキーの取得
「個人設定」を開き、表示されているAPIアクセスキーを記録する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/a0e7b11e-cf85-1791-400e-6b12ccbdb91f.png)

3-2. プロジェクトID、トラッカーIDの取得
3-2-1. Adminerにアクセスして、RedmineのDBにログインする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/4f89c5ac-4e76-745b-0493-3071f7d39c3f.png)
※パスワードは、docker-composeで設定した"example"

3-2-2. "project"テーブルを参照してプロジェクトIDを取得する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/f27da183-de8c-8147-b39b-b54b66b901a4.png)
id列の値がプロジェクトID

3-2-3. "tracker"テーブルを参照してトラッカーIDを取得する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/bdc6ff78-80ae-9553-70c9-acfd7a938608.png)
id列の値がトラッカーID

### 4. RundeckでRedmine APIコマンド実行
Rundeckでのプロジェクトの作成やコマンドの実行設定に関しては以下を参照

https://qiita.com/piityan1126/items/2a7bd2c9830d8436a217

ジョブを作成して、Workflowのステップ1にScriptを選択し、Redmine APIコマンドを呼び出すコマンドを入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/190b843f-8465-40d9-82e8-8a21a1fd9c59.png)


Scriptに記載したコマンドを以下にも記載
```
#!/bin/sh

# Redmine APIのエンドポイントとAPIキー
REDMINE_API_URL="http://192.168.2.10:8888/issues.json"
API_KEY="<3-1で取得したAPIアクセスキー>"

# POSTデータ
POST_DATA='{
  "issue": {
    "project_id": 1,
    "tracker_id": 1,
    "subject": "テストチケット1",
    "description": "これはテストチケット1です",
    "assigned_to_id": 1
  }
}'

# RedmineにPOSTリクエストを送信
response=$(curl -s -X POST "$REDMINE_API_URL" \
  -H "Content-Type: application/json" \
  -H "X-Redmine-API-Key: $API_KEY" \
  -d "$POST_DATA")

# jqでerrors配列の長さを取得
errors_count=$(echo "$response" | jq '.errors | length')

# エラーがある場合に、エラーコード99で終了
if [ $errors_count -gt 0 ]; then
    echo "Errors count: $errors_count"
    echo $response
    exit 99
fi

echo "$response" | jq '.'
exit 0

```

### 5 動作確認
5-1. 手動でシェルを実行
「今すぐジョブを実行」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/e3342dbc-d69b-14df-a3ce-57c2cfc4c791.png)

ジョブの実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/06fc90b5-e2d2-439b-8513-a81983dd0a71.png)
ジョブは正常に完了していて、作成されたチケットの情報がログに出力されている。

5-2. 作成されたチケットをRedmineで確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/e8e2458d-88d1-d760-81fb-0319ef42a6da.png)
無事にチケットを登録することができました！！
あとはRundeckのジョブの設定で任意のタイミングで繰り返し実行することで、
自動的に定期タスクのチケットが作成されます。

5-3. 不正なチケットを作成しようとした場合にエラーとなることを確認
チケットの担当者IDに実在しない値を指定して、ジョブを実行してみる。

``` APIへのポストデータ抜粋
# POSTデータ
POST_DATA='{
  "issue": {
    "project_id": 1,
    "tracker_id": 1,
    "subject": "テストチケット1",
    "description": "これはテストチケット1です",
    "assigned_to_id": 2
  }
}'
```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404547/cd0ba27c-8ca4-4342-aa73-75ec25aff5e4.png)
ジョブは失敗と判定されていて、ログにエラーメッセージも表示されている。

[^1]:【Docker】MySQLで複数database環境を構築する
https://qiita.com/piggydev/items/2096258dc45a6bbe2a19

### 更新
- 2025/02/24
  - シェルを作成せずにジョブのScriptにコマンドを記載しても実行できたので記載を変更
  - RedmineのAPIからエラーが返された場合に、検知できるようにスクリプトを修正
    スクリプトが長くなったので、リファクタリングを実施
  - Redmineから返されるJSONテキストを解析するためにjqをインストールする手順(dockerfileを作成)を追加
  - Redmine APIからエラーが返される場合に、ジョブが失敗することを確認する手順を追加