プログラミングをせずに、コマンドラインツールの組み合わせでRedmineのAPIにアクセスしてみます。
					* **curl**: HTTPリクエストを行う
					* **jq**: JSONデータを整形・加工する
				
				
				
					REST APIとは
					
						Redmineが持つデータに、外部から
						(Web UIを経由せず)アクセスできる仕組み。
					
				
				
					アクセス方法
					HTTPでリクエストすると、XMLまたはJSON形式のレスポンスが帰ってくる
					
				
				
					## REST APIの何がうれしい?
					* 他システムからRedmineを操作できる
					  - 自動化、チャットボットなど
					* Web UIの操作ではできないことが実現できる
				
				
					## APIを利用したツール例
					1. Redmineチケット★一括★
					2. Redmine Notifier
				
				
					**①Redmineチケット★一括★**
					Excelファイルを読み込んでチケットを登録
					
					https://www.vector.co.jp/soft/winnt/util/se503347.html
				
				
					**②Redmine Notifier**
					チケットの更新をデスクトップに通知
					
					https://github.com/emsk/redmine-notifier
				
				
				
					## 前提条件
					* コマンドラインからAPIを利用してみます
					* 扱いやすいJSON形式を利用します
					* APIへのアクセスを行う端末のOSは **Ubuntu** または **macOS** を想定しています
					* **Windows**の人は **Windows Subsystem for Linux** をインストールするなどしてください
				
				
					## REST APIを有効にする
					「管理」→「設定」→「API」タブの「**RESTによるWebサービスを有効にする**」をON
					
				
				
					## jq をPCにインストールする
					jq はJSON形式のテキストを整形・加工するツール。APIで取得したデータの処理に使う。
					### ubuntu:
					``` bash
					sudo apt-get install jq
					```
					### macOS:
					``` bash
					brew install jq
					```
				
				
					## APIにアクセスできるかテスト
					``` bash
					curl --user ログインID:パスワード 'http://redmine.test/issues.json?limit=1' | jq .
					```
				
				
					こんな感じの画面が出ればOK
					
				
				
				
					## ユーザー一覧の取得
					Redmineのユーザー一覧をJSON形式で取得する
					``` bash
					curl --user admin:パスワード 'http://redmine.test/users.json?limit=100'
					```
				
				
					``` json
					{"users":[{"id":1,"login":"admin","firstname":"Redmine","lastname":"Admin","mail":"admin@somenet.foo","created_on":"2006-07-19T17:12:21Z","last_login_on":"2018-05-25T20:24:22Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":3,"login":"dlopper","firstname":"Dave","lastname":"Lopper","mail":"dlopper@somenet.foo","created_on":"2006-07-19T17:33:19Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":22,"login":"example","firstname":"Some","lastname":"One","mail":"someone@example.jp","created_on":"2018-05-24T13:40:12Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":2,"login":"jsmith","firstname":"John","lastname":"Smith","mail":"jsmith@somenet.foo","created_on":"2006-07-19T17:32:09Z","last_login_on":"2006-07-19T20:42:15Z","custom_fields":[{"id":4,"name":"Phone number","value":"01 42 50 00 00"},{"id":5,"name":"Money","value":""}]},{"id":8,"login":"miscuser8","firstname":"User","lastname":"Misc","mail":"miscuser8@foo.bar","created_on":"2006-07-19T17:33:19Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":9,"login":"miscuser9","firstname":"User","lastname":"Misc","mail":"miscuser9@foo.bar","created_on":"2006-07-19T17:33:19Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":4,"login":"rhill","firstname":"Robert","lastname":"Hill","mail":"rhill@somenet.foo","created_on":"2006-07-19T17:34:07Z","custom_fields":[{"id":4,"name":"Phone number","value":"01 23 45 67 89"},{"id":5,"name":"Money","value":""}]},{"id":7,"login":"someone","firstname":"Some","lastname":"One","mail":"someone@foo.bar","created_on":"2006-07-19T17:33:19Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":15,"login":"user0001","firstname":"Redmine","lastname":"Admin","mail":"user0001@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":16,"login":"user0002","firstname":"Dave","lastname":"Lopper","mail":"user0002@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":17,"login":"user0003","firstname":"John","lastname":"Smith","mail":"user0003@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":18,"login":"user0004","firstname":"User","lastname":"Misc","mail":"user0004@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":19,"login":"user0005","firstname":"User","lastname":"Misc","mail":"user0005@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":20,"login":"user0006","firstname":"Robert","lastname":"Hill","mail":"user0006@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]},{"id":21,"login":"user0007","firstname":"Some","lastname":"One","mail":"user0007@example.com","created_on":"2018-05-24T13:30:18Z","custom_fields":[{"id":4,"name":"Phone number","value":""},{"id":5,"name":"Money","value":""}]}],"total_count":15,"offset":0,"limit":100}
					```
				
				
					## jq で見やすく整形
					``` bash
					curl --user admin:パスワード 'http://redmine.test/users.json?limit=100' | jq .
					```
				
				
					``` json
					{
					  "users": [
					    {
					      "id": 1,
					      "login": "admin",
					      "firstname": "Redmine",
					      "lastname": "Admin",
					      "mail": "admin@somenet.foo",
					      "created_on": "2006-07-19T17:12:21Z",
					      "last_login_on": "2018-05-25T20:33:44Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 3,
					      "login": "dlopper",
					      "firstname": "Dave",
					      "lastname": "Lopper",
					      "mail": "dlopper@somenet.foo",
					      "created_on": "2006-07-19T17:33:19Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 22,
					      "login": "example",
					      "firstname": "Some",
					      "lastname": "One",
					      "mail": "someone@example.jp",
					      "created_on": "2018-05-24T13:40:12Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 2,
					      "login": "jsmith",
					      "firstname": "John",
					      "lastname": "Smith",
					      "mail": "jsmith@somenet.foo",
					      "created_on": "2006-07-19T17:32:09Z",
					      "last_login_on": "2006-07-19T20:42:15Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": "01 42 50 00 00"
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 8,
					      "login": "miscuser8",
					      "firstname": "User",
					      "lastname": "Misc",
					      "mail": "miscuser8@foo.bar",
					      "created_on": "2006-07-19T17:33:19Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 9,
					      "login": "miscuser9",
					      "firstname": "User",
					      "lastname": "Misc",
					      "mail": "miscuser9@foo.bar",
					      "created_on": "2006-07-19T17:33:19Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 4,
					      "login": "rhill",
					      "firstname": "Robert",
					      "lastname": "Hill",
					      "mail": "rhill@somenet.foo",
					      "created_on": "2006-07-19T17:34:07Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": "01 23 45 67 89"
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 7,
					      "login": "someone",
					      "firstname": "Some",
					      "lastname": "One",
					      "mail": "someone@foo.bar",
					      "created_on": "2006-07-19T17:33:19Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 15,
					      "login": "user0001",
					      "firstname": "Redmine",
					      "lastname": "Admin",
					      "mail": "user0001@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 16,
					      "login": "user0002",
					      "firstname": "Dave",
					      "lastname": "Lopper",
					      "mail": "user0002@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 17,
					      "login": "user0003",
					      "firstname": "John",
					      "lastname": "Smith",
					      "mail": "user0003@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 18,
					      "login": "user0004",
					      "firstname": "User",
					      "lastname": "Misc",
					      "mail": "user0004@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 19,
					      "login": "user0005",
					      "firstname": "User",
					      "lastname": "Misc",
					      "mail": "user0005@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 20,
					      "login": "user0006",
					      "firstname": "Robert",
					      "lastname": "Hill",
					      "mail": "user0006@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    },
					    {
					      "id": 21,
					      "login": "user0007",
					      "firstname": "Some",
					      "lastname": "One",
					      "mail": "user0007@example.com",
					      "created_on": "2018-05-24T13:30:18Z",
					      "custom_fields": [
					        {
					          "id": 4,
					          "name": "Phone number",
					          "value": ""
					        },
					        {
					          "id": 5,
					          "name": "Money",
					          "value": ""
					        }
					      ]
					    }
					  ],
					  "total_count": 15,
					  "offset": 0,
					  "limit": 100
					}
					```
				
				
					## ユーザー一覧のJSONをCSVに変換
					``` bash
					curl --user admin:パスワード http://redmine.test/users.json | jq -r '.users[] | [.login, .mail, .firstname, .lastname] | @csv'
					```
					1. users[] の値を取り出して、
					2. 各ユーザーのデータから必要な値を取り出して配列に変換して、
					3. CSVに変換
				
				
					``` nohighlight
					"admin","admin@somenet.foo","Redmine","Admin"
					"dlopper","dlopper@somenet.foo","Dave","Lopper"
					"jsmith","jsmith@somenet.foo","John","Smith"
					"miscuser8","miscuser8@foo.bar","User","Misc"
					"miscuser9","miscuser9@foo.bar","User","Misc"
					"rhill","rhill@somenet.foo","Robert","Hill"
					"someone","someone@foo.bar","Some","One"
					```
					フォーマット: ログインID,メールアドレス,名,姓
				
				
					## ユーザーを登録する
					ファイル newuser.json を用意して
					``` nohighlight
					{
					  "user": {
					    "login": "maeda",
					    "mail": "maeda@example.com",
					    "firstname": "剛",
					    "lastname": "前田",
					    "password": "I3JlZG1pbmV0"
					  }
					}
					```
					実行
					``` bash
					cat newuser.json | curl 'http://redmine.test/users.json' --user admin:パスワード --header 'Content-type: application/json' --data @-
					```
				
				
					## ユーザーをCSVから一括登録する
					ファイル user.csv を用意して
					``` nohighlight
					user0001,foo@example.com,Joe,Bloggs,HiaH4JJd
					user0002,bar@example.com,Jane,Public,9iQYyLn5
					user0003,baz@example.com,Chris,Wong,dG9EFggG
					```
					実行
					``` bash
					cat /tmp/users.csv | while read LINE
					do
					  echo $LINE | jq -R 'gsub("\"";"") | split(",") | {"user": {"login": .[0], "mail": .[1], "firstname": .[2], "lastname": .[3], "password": .[4]}}' | curl -v 'http://redmine.test/users.json' --user admin:パスワード --header 'Content-type: application/json' --data @-
					done
					```
				
				
					1. 1行ずつ読み取りコマンド実行: **`cat ... while ...`**
					2. ダブルクォーテーションを削除: **`gsub("\"";"")`**
					3. コンマで分割: **`split(",")`**
					4. JSONに変換: **`{"user": {"login": .[0], ...}}`**
					5. 標準入力からJSONを受け取りAPIを呼び出してユーザー登録: **`curl ...`**
					``` bash
					cat /tmp/users.csv | while read LINE
					do
					  echo $LINE | jq -R 'gsub("\"";"") | split(",") | {"user": {"login": .[0], "mail": .[1], "firstname": .[2], "lastname": .[3], "password": .[4]}}' | curl 'http://redmine.test/users.json' --user admin:パスワード --header 'Content-type: application/json' --data @-
					done
					```
				
				
					## チケット操作
					``` bash
					# チケット一覧取得
					curl http://ホスト名/issues.json --user ログインID:パスワード
					# 個別のチケット取得
					curl http://ホスト名/issues/1.json --user ログインID:パスワード
					# チケット作成
					curl http://ホスト名/issues.json --user ログインID:パスワード --header 'Content-type: application/json' --data '{"issue": {"project_id": 1, "tracker_id": 1, "subject": "件名", "description": "説明"}}'
					# チケット更新
					curl http://ホスト名/issues/5.json --user ログインID:パスワード --request 'PUT' --header 'Content-type: application/json' --data '{"issue": {"subject": "変更後件名", "description": "変更後説明"}}'
					# チケット削除
					curl http://ホスト名/issues/16.json --user ログインID:パスワード --request 'DELETE'
					```
				
				
					## 指定したチケットを取得する
					``` bash
					curl --user ログインID:パスワード 'http://redmine.test/issues/1.json' | jq .
					```
				
				
					``` json
					{
					  "issue": {
					    "id": 1,
					    "project": {
					      "id": 1,
					      "name": "eCookbook"
					    },
					    "tracker": {
					      "id": 1,
					      "name": "Bug"
					    },
					    "status": {
					      "id": 1,
					      "name": "New"
					    },
					    "priority": {
					      "id": 4,
					      "name": "Low"
					    },
					    "author": {
					      "id": 2,
					      "name": "John Smith"
					    },
					    "category": {
					      "id": 1,
					      "name": "Printing"
					    },
					    "subject": "Cannot print recipes",
					    "description": "Unable to print recipes",
					    "start_date": "2018-05-22",
					    "due_date": "2018-06-02",
					    "done_ratio": 0,
					    "spent_hours": 154.25,
					    "total_spent_hours": 154.25,
					    "custom_fields": [
					      {
					        "id": 2,
					        "name": "Searchable field",
					        "value": "125"
					      },
					      {
					        "id": 1,
					        "name": "Database",
					        "value": ""
					      },
					      {
					        "id": 6,
					        "name": "Float field",
					        "value": "2.1"
					      },
					      {
					        "id": 8,
					        "name": "Custom date",
					        "value": "2009-12-01"
					      },
					      {
					        "id": 9,
					        "name": "Project 1 cf",
					        "value": ""
					      }
					    ],
					    "created_on": "2018-05-20T05:54:42Z",
					    "updated_on": "2018-05-22T05:54:42Z"
					  }
					}
					```
				
				
					## チケットを1行のテキストに変換
					コマンド
					``` bash
					curl --user ログインID:パスワード 'http://redmine.test/issues/1.json?limit=100' | jq '.issue | "\(.tracker.name) #\(.id) - \(.subject) (\(.status.name)) http://redmine.test/issues/\(.id)" | @text'
					```
					結果
					``` nohighlight
					Bug #1 - Cannot print recipes (New) http://redmine.test/issues/1
					```
				
				
					### 開始日が今日以前のチケットを抽出
					コマンド
					``` bash
					curl --user ログインID:パスワード "http://redmine.test/issues.json?limit=100&start_date=<=`date +%Y-%m-%d`&sort=start_date" | jq -r '.issues[] | "\(.tracker["name"]) #\(.id) - \(.subject) (\(.start_date))" | @text'
					```
					結果
					``` nohighlight
					Bug #3 - Error 281 when updating a recipe (2018-05-08)
					Bug #7 - Issue due today (2018-05-13)
					Feature request #2 - Add ingredients categories (2018-05-21)
					Bug #1 - Cannot print recipes (2018-05-22)
					Bug #6 - Issue of a private subproject (2018-05-23)
					Bug #9 - Blocked Issue (2018-05-23)
					Bug #10 - Issue Doing the Blocking (2018-05-23)
					```
				
				
					## 未完了で、自分が担当で、更新日が7日以上前のチケットから1件をランダムに表示
					コマンド
					``` bash
					curl --user ログインID:パスワード "http://redmine.test/issues.json?limit=100&assigned_to_id=me&updated_on=<=`date -v-7d +%Y-%m-%d`" | jq -r '.issues[] | "\(.tracker.name) #\(.id) - \(.subject) (\(.updated_on)) http://redmine.test/issues/\(.id)" | @text' | python -c 'import sys, random; print(random.choice(sys.stdin.readlines()));'
					```
					結果
					``` nohighlight
					Bug #3 - Error 281 when updating a recipe (2006-07-19T19:07:27Z) http://redmine.test/issues/3
					```
				
				
					## 題名が正規表現にマッチするチケットを探す
					1万件の未完了チケットの中から、題名が `/(交通|宿泊)/i` にマッチするチケットを探す
					``` bash
					for OFFSET in `seq 0 100 10000`; do curl --user ログインID:ユーザー名 "https://ホスト名/issues.json?limit=100&offset=$OFFSET" | jq -r '.issues[] | select(.subject | test("交通|宿泊"; "i")) | "\(.tracker["name"]) #\(.id) - \(.subject)"'; done
					```
					※すごく遅いです
				
				
				
					## レスポンスはXMLかJSON
					* URLの拡張子でXMLかJSONか決まる
					* コマンドラインで使うなら **JSON** がおすすめ。 **jq** が強力で、また	登録用データを作るのもXMLより楽
					``` bash
					# XML
					curl --user ログインID:パスワード 'http://redmine.test/issues/1.xml'
					# JSON
					curl --user ログインID:パスワード 'http://redmine.test/issues/1.json'
					```
				
				
					## 1回のリクエストで取得できるのは最大100件
					* デフォルトは25件。URLのパラメータ `limit` を指定して増やせるが、上限は100
					* それ以上のデータを取得するには `offset` の値を変えながら複数のリクエストを行う
					```
					# 0..99
					curl --user admin:パスワード 'http://redmine.test/users.json?limit=100'
					# 100..199
					curl --user admin:パスワード 'http://redmine.test/users.json?limit=100&offset=100'
					# 200..299
					curl --user admin:パスワード 'http://redmine.test/users.json?limit=100&offset=200'
					```
				
				
					## 認証はbasic認証またはAPIキーで行う
					### basic認証
					``` bash
					curl --user ログインID:パスワード 'http://redmine.test/issues.json'
					```
					### APIキー
					``` bash
					curl --header 'X-Redmine-API-Key: APIキー' 'http://redmine.test/issues.json'
					```
					APIキーは「個人設定」画面のサイドバー内「APIアクセスキー」で確認する
				
				
					offsetを 0 から 900 まで100ずつ増やしながら10回のリクエストを行う例
					```
					for OFFSET in `seq 0 100 900`; do curl --user admin:パスワード "http://redmine.test/users.json?limit=100&offset=$OFFSET" | jq -r '.users[] | [.login, .mail, .firstname, .lastname] | @csv'; done > support-users.csv
					```
				
				
					## 一部のオブジェクトはシステム管理者権限が必要
					ユーザー、グループ、カスタムフィールドの一覧など
				
				
					## デバッグには curl の `-v` オプションが便利
					ヘッダが見えるのでRedmineサーバとの通信で何が起こっているか把握しやすい
					``` shell
					# システム管理者でないユーザーがユーザーの一覧にアクセス
					$ curl -v --user ログインID:パスワード "http://redmine.test/users.json"
					.
					(中略)
					.
					< HTTP/1.1 403 Forbidden
					< X-Frame-Options: SAMEORIGIN
					< X-XSS-Protection: 1; mode=block
					< X-Content-Type-Options: nosniff
					< Content-Type: application/json
					.
					.
					```
				
				
				
				
					* curl と jq を使えば、プログラミング無しでAPIを活用できる。案外簡単!
					* Web UIでできないこともAPIを使えば実現できる場合あり
					* ちょっとした自動化にも使えそう
					**APIでRedmineをちょっと便利に!**