[Redmine] チケットをRESTful APIで作成する - PHP版

RedmineのCSVをインポートする機能は便利なんですが、親チケットと子チケットを同時に登録できないのが辛いところ。そこで今回はRedmineのRESTfulAPIを利用しプログラムからチケットを登録する方法をまとめます。

準備

まずはRedmine上でRESTful APIが利用できる状態か確認します。

Redmineの管理者権限のあるユーザーでログインし、「管理」→「設定」→「API」とたどります。「RESTによるWebサービスを有効にする」にチェックが付いていればOK。今回はPHPから利用するのでJSONPのチェックは外れていても構いません。

次にAPIを実行するユーザーの「APIアクセスキー」を取得します。 当該ユーザーでRedmineへログインした後に「個人設定」ページの右側にある「APIアクセスキー」の「表示」をクリックすると英数字が組み合わさった長い文字列が表示されますのでこれをメモします。

APIアクセスキーがわかると何でも出来てしまうので、外部に漏れた場合のことを考慮し、APIを実行するユーザーは権限や参加プロジェクトは必要最小限にした専用のユーザーを作ることをおすすめします。

よくあるのは間違えてGitHubなどの公開リポジトリにあげちゃったというヤツですね。さすがに仕事でやっている場合は中々考えにくいですが、個人的なプロジェクトの場合はご注意を。

チケットを作成する

原理

チケットを新規に作成するAPIは非常にシンプルな作りで、/issues.json?key=(APIアクセスキー)にPOSTメソッドで題名や優先度などの必要な情報を送信するだけです。拡張子の部分はjsonであればJSON、xmlであればXMLでやり取りが出来ます。

ソースコード

実際にやってみた方が早いですね。以下がソースコードです。特にライブラリなどは利用していないので、そのままコピペしCLIなどで実行可能です。定数の部分はご自身の環境に合わせて変更してください。

ここではJSONでRedmineサーバとやり取りを行っています。

<?php
/**
 * Redmineにチケットを作成する
 *
 * @author M.Katsube
 * @version 1.0.0
 */

//-----------------------------------
// 定数
//-----------------------------------
// Redmineが稼働しているURL
define('END_POINT', 'https://redmine.example.com');

// APIを実行するユーザーのAPIアクセスキー
define('TOKEN', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');

//-----------------------------------
// 実行
//-----------------------------------
// URL作成
$url = sprintf('%s/issues.json?key=%s', END_POINT, TOKEN);

// リクエスト実行
$result = sendPostRequest($url, [
  'issue' => [
    'project_id' => 1,          // プロジェクト
    'subject'    => 'APIテスト',  // 題名

    // 以下は任意で指定する
    //'description'      => '',  // 説明文
    //'tracker_id'       => '',  // トラッカー
    //'status_id'        => '',  // ステータス
    //'priority_id'      => '',  // 優先度
    //'category_id'      => '',  // カテゴリ
    //'fixed_version_id' => '',  // 対象バージョン
    //'assigned_to_id'   => '',  // 担当者
    //'parent_issue_id'  => '',  // 親チケット
    //'custom_fields'    => '',  // カスタムフィールド
    //'watcher_user_ids' => '',  // ウォッチャー(ユーザーID) v2.3.0〜
    //'is_private'       => '',  // プライベートチケットにするか true or false
    //'estimated_hours'  => ''   // 予定工数
  ]
]);

// レスポンスを人間が見やすく加工して表示
$json = json_decode($result);
echo json_encode($json, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);


/**
 * cURLでPOST通信を行う
 *
 * @param [string] $url     - 'http://example.com/'
 * @param [array]  $query   - 連想配列でクエリーを指定
 * @return [string|boolean] - 成功: 取得結果,  失敗: false
 */
function sendPostRequest($url, $query){
  // cURL初期化
  $ch = curl_init();

  // cURLオプション設定
  curl_setopt_array($ch, [
    CURLOPT_URL            => $url,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_RETURNTRANSFER => true,     // レスポンス内容をcurl_exec()の戻り値として返却する
    CURLOPT_POSTFIELDS     => json_encode($query)
  ]);

  // リクエストを実行
  $res = curl_exec($ch);
  curl_close($ch);

  return($res);
}

実行する

先ほどのソースコードをコピペして保存したら、以下のようにそのまま実行します。

$ php create_issue.php

正常に処理が完了すると以下のように最終的に登録されたチケットの内容がJSONで返されます。このJSONを利用し、APIの戻り値のIDを取得し子チケットを作成することもできます。

{
    "issue": {
        "id": 123,
        "project": {
            "id": 1,
            "name": "究極のチャーハン作成プロジェクト"
        },
        "tracker": {
            "id": 6,
            "name": "開発"
        },
        "status": {
            "id": 1,
            "name": "新規"
        },
        "priority": {
            "id": 2,
            "name": "中"
        },
        "author": {
            "id": 20,
            "name": "WS 勝部麻季人"
        },
        "fixed_version": {
            "id": 1,
            "name": "バージョン1.0.0"
        },
        "parent": {
            "id": 10
        },
        "subject": "材料を買いに行く",
        "description": "",
        "start_date": "2020-10-20",
        "due_date": null,
        "done_ratio": 0,
        "is_private": false,
        "estimated_hours": null,
        "total_estimated_hours": null,
        "created_on": "2020-10-20T11:34:02Z",
        "updated_on": "2020-10-20T11:34:02Z",
        "closed_on": null
    }
}

おまけ

プロジェクトidを調べる

以下のようなURLにAPIアクセスキーを指定してアクセスするとプロジェクト一覧がJSONで返されます。

https://redmine.example.com/projects.json?key=xxxxxxxxxxxxxxxxxxxx

以下のようなJSONになりますので、idの部分の数字を参考にします。

{
  projects: [
  {
    id: 1,
    name: "究極のチャーハン作成プロジェクト",
    identifier: "ultimatefriedrice",
    description: "チャーハン作るよ!",
    status: 1,
    is_public: false,
    inherit_members: false,
    custom_fields: [
      {
        id: 1,
        name: "Slack Channel",
        value: ""
      }
    ],
    created_on: "2020-09-17T02:27:43Z",
    updated_on: "2020-09-25T07:16:42Z"
  },
  //(ç•¥)

DoS攻撃状態になるのを避ける

サンプルのコードでは1回だけしかリクエストを送っていませんが、実際にはファイルなどに記録されたチケット一覧をまとめて登録するケースで利用されると思います。このように例えば数百件のチケットを登録するような場合は、処理の途中にsleep()を挟んでサーバを適度に休ませてください。これを忘れると中途半端なDoS攻撃状態になりますw

<?php
for( $i=0; $i<count($tickets); $i++ ){
  // チケット登録処理
  $result = sendPostRequest($url, $tickets[$i]);

  // 3回毎に1秒休む
  if( ($i % 3) === 0 ){
    sleep(1);
  }
}

親チケットが指定できない

APIを実行するRedmine上のユーザーの権限を最小限にしたかったのでロールを「Reporter」にしていたのですが、このロールはデフォルトで子チケットの管理ができない設定になっています。もしプログラム上から指定しても無視されます(エラーにはなりません)。

当初これを知らなかったのでめっちゃハマりしましたw ロールを「Developer」に変更したら解決したのでまさかと思ったら。こんな細かく権限設定できるとは思わなんだw

参考ページ