■はじめに
今回は、Hinemos ver.7.2 の REST API を使って、コマンドジョブの登録・更新をコードだけで行う方法についてご紹介いたします。
Hinemos では GUI からジョブを設定するのが一般的ですが、同じ構成のジョブを大量に登録したり、コマンドパスを一括で変更したりする場合は、GUI 操作では手間がかかります。
REST API を活用することで、こういった作業をスクリプトで自動化できます。
参考ドキュメント:Hinemos ver.7.2 REST API 開発ガイド(ja_RestAPI_7.2_rev1.html)
第18章「ジョブ管理」
※ REST API 開発ガイドはHinemosのサブスクリプション契約が必要なドキュメントです。
それでは、実際に試してみましょう。
1. 環境情報
今回は以下の環境で検証を行いました。
・利用環境:
Linux版 Hinemosマネージャ、Hinemos Webクライアント(ver.7.2)
Hinemosエージェントサーバ(ファシリティID: agent)
・言語:Python(/usr/libexec/platform-python、RHEL 8)
・対象ジョブユニット:JOBUNIT
・登録するコマンドジョブ:
| ジョブID | ジョブ名 | 起動コマンド |
|---|---|---|
| CMD_JOB_01 | コマンドジョブ01 | /opt/scripts/job_01.sh |
| CMD_JOB_02 | コマンドジョブ02 | /opt/scripts/job_02.sh |
| CMD_JOB_03 | コマンドジョブ03 | /opt/scripts/job_03.sh |
| CMD_JOB_04 | コマンドジョブ04 | /opt/scripts/job_04.sh |
| CMD_JOB_05 | コマンドジョブ05 | /opt/scripts/job_05.sh |
2. 事前準備:GUI でジョブユニットを作成する
REST API でコマンドジョブを登録するには、ジョブユニットがあらかじめ作成済みであることが前提となります。
今回は「ジョブユニットだけ GUI で作成し、その配下のコマンドジョブは API で登録する」という方針で進めます。
Hinemos Webクライアントを起動し、ジョブ設定パースペクティブからジョブユニット JOBUNIT を新規作成・登録してください。
この時点ではコマンドジョブは何も登録しなくて構いません。

3. 認証トークンの取得
Hinemos の REST API は Bearer トークン認証を使用します。
(参照:REST API 開発ガイド「2.1 ログイン」)
まず、以下のスクリプトを実行してトークンを取得します。引数には「ログインユーザ名 / ログインパスワード」を指定します。
|
1 |
python getToken.py hinemos hinemos |
実行すると ./token_hinemos.txt にトークンが保存されます。以降のスクリプトはこのファイルを読み込んで認証に使用します。
getToken.py のコード(クリックで展開)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/usr/libexec/platform-python from urllib.request import * import sys import json args = sys.argv user = args[1] pas = args[2] req = Request( url='http://[HinemosマネージャのIPアドレス]:8080/HinemosWeb/api/AccessRestEndpoints/access/login', data=json.dumps({'userId': user, 'password': pas}).encode(), headers={'Content-Type': 'application/json'}, method='POST') with urlopen(req) as res: body = json.loads(res.read()) print(body) try: file = open("./token_" + user + ".txt", 'w') file.write(body['token']['tokenId']) except Exception as e: print(e) finally: file.close() |
4. コマンドジョブの一括登録
REST API 開発ガイドの「18.2 コマンドジョブを追加する」に記載されている
POST /jobunit/{jobunitId}/commandJob エンドポイントを使用します。
このエンドポイントを使うにあたり、Hinemos ではジョブ設定の変更操作の前にロック(編集セッション)の取得が必要です。
処理の流れは以下の通りです。
- ジョブツリー取得 API でロック取得に必要な情報を確認する
- ロックを取得する
- コマンドジョブを 1 件ずつ登録する(今回は 5 件)
- ロックを解放する
以下のスクリプトを実行してコマンドジョブを一括登録します。引数にはログインユーザ名を指定します。
|
1 |
python registerJobUnit.py hinemos |
registerJobUnit.py のコード(クリックで展開)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
#!/usr/libexec/platform-python # 前提: GUIで JOBUNIT(空のジョブユニット)を作成・登録済みであること from urllib.request import * from urllib.error import HTTPError import sys import json args = sys.argv user = args[1] JOBUNIT_ID = "JOBUNIT" PARENT_ID = "JOBUNIT" FACILITY_ID = "agent" COMMANDS = [ ("CMD_JOB_01", "コマンドジョブ01", "/opt/scripts/job_01.sh"), ("CMD_JOB_02", "コマンドジョブ02", "/opt/scripts/job_02.sh"), ("CMD_JOB_03", "コマンドジョブ03", "/opt/scripts/job_03.sh"), ("CMD_JOB_04", "コマンドジョブ04", "/opt/scripts/job_04.sh"), ("CMD_JOB_05", "コマンドジョブ05", "/opt/scripts/job_05.sh"), ] managerUrl = "http://[HinemosマネージャのIPアドレス]:8080/HinemosWeb/api/" try: f = open("./token_" + user + ".txt") token = f.read() finally: f.close() def api(method, path, body=None): headers = {"Authorization": "Bearer " + token} data = None if body is not None: headers["Content-Type"] = "application/json" data = json.dumps(body).encode() req = Request(url=managerUrl + path, headers=headers, data=data, method=method) with urlopen(req) as res: raw = res.read() return json.loads(raw) if raw else None def wait_rule(): return { "suspend": False, "skip": False, "skipEndStatus": "NORMAL", "skipEndValue": 0, "condition": "AND", "objectGroup": [], "endCondition": False, "endStatus": "NORMAL", "endValue": 0, "exclusiveBranch": False, "exclusiveBranchEndStatus": "NORMAL", "exclusiveBranchEndValue": 0, "exclusiveBranchNextJobOrderList": [], "calendar": False, "calendarEndStatus": "NORMAL", "calendarEndValue": 0, "jobRetryFlg": False, "jobRetryEndStatus": "NORMAL", "jobRetry": 1, "jobRetryInterval": 0, "startDelay": False, "startDelaySession": False, "startDelaySessionValue": 1, "startDelayTime": False, "startDelayConditionType": "AND", "startDelayNotify": False, "startDelayNotifyPriority": "CRITICAL", "startDelayOperation": False, "startDelayOperationType": "STOP_AT_ONCE", "startDelayOperationEndStatus": "NORMAL", "startDelayOperationEndValue": 0, "endDelay": False, "endDelaySession": False, "endDelaySessionValue": 1, "endDelayJob": False, "endDelayJobValue": 1, "endDelayTime": False, "endDelayConditionType": "AND", "endDelayNotify": False, "endDelayNotifyPriority": "CRITICAL", "endDelayOperation": False, "endDelayOperationType": "STOP_AT_ONCE", "endDelayOperationEndStatus": "NORMAL", "endDelayOperationEndValue": 0, "endDelayChangeMount": False, "endDelayChangeMountValue": 0.0, "multiplicityNotify": False, "multiplicityNotifyPriority": "CRITICAL", "multiplicityOperation": "WAIT", "multiplicityEndValue": 0, "queueFlg": False } def end_status(): return [ {"type": "NORMAL", "value": 0, "startRangeValue": 0, "endRangeValue": 0}, {"type": "WARNING", "value": 1, "startRangeValue": 1, "endRangeValue": 1}, {"type": "ABNORMAL", "value": -1, "startRangeValue": -1, "endRangeValue": -1} ] def cmd_job_body(job_id, name, start_cmd): return { "id": job_id, "parentId": PARENT_ID, "name": name, "waitRule": wait_rule(), "endStatus": end_status(), "beginPriority": "NONE", "normalPriority": "NONE", "warnPriority": "NONE", "abnormalPriority": "NONE", "notifyRelationInfos": [], "command": { "facilityID": FACILITY_ID, "processingMethod": "ALL_NODE", "managerDistribution": False, "startCommand": start_cmd, "stopType": "DESTROY_PROCESS", "stopCommand": "", "specifyUser": False, "messageRetry": 3, "messageRetryEndFlg": False, "messageRetryEndValue": 1, "commandRetryFlg": False, "commandRetry": 1, "commandRetryEndStatus": "NORMAL", "normalJobOutputInfo": { "sameNormalFlg": True, "directory": "/", "fileName": "/", "appendFlg": False, "failureOperationFlg": False, "failureOperationType": "STOP_SUSPEND", "failureOperationEndStatus": "NORMAL", "failureOperationEndValue": 0, "failureNotifyFlg": False, "failureNotifyPriority": "CRITICAL", "valid": False }, "errorJobOutputInfo": { "sameNormalFlg": True, "directory": "/", "fileName": "/", "appendFlg": False, "failureOperationFlg": False, "failureOperationType": "STOP_SUSPEND", "failureOperationEndStatus": "NORMAL", "failureOperationEndValue": 0, "failureNotifyFlg": False, "failureNotifyPriority": "CRITICAL", "valid": False }, "jobCommandParamList": [], "envVariable": [] } } # ── Step 1: updateTime 取得 ────────────────────────────────────────────── print("==[Step 1] GET job tree → updateTime ==") tree = api("GET", "JobRestEndpoints/job/setting/job_treeFull") updateTime = None def find_update_time(items): global updateTime for item in items: d = item.get("data", {}) if d.get("type") == "JOBUNIT" and d.get("id") == JOBUNIT_ID: updateTime = d.get("updateTime") if item.get("children"): find_update_time(item["children"]) find_update_time(tree if isinstance(tree, list) else [tree]) if updateTime is None: print("FAILED: " + JOBUNIT_ID + " not found in job tree") sys.exit(1) print("updateTime=" + str(updateTime)) # ── Step 2: ロック取得 ──────────────────────────────────────────────────── print("==[Step 2] Lock ==") lock = api("POST", "JobRestEndpoints/job/setting/jobunit/" + JOBUNIT_ID + "/lock", {"updateTime": updateTime, "forceFlag": True}) session = lock["editSession"] print("session=" + str(session)) # ── Step 3: POST /commandJob でコマンドジョブを1件ずつ登録 ──────────────── print("==[Step 3] POST: register command jobs ==") for cmd in COMMANDS: job_id, name, start_cmd = cmd try: api("POST", "JobRestEndpoints/job/setting/jobunit/" + JOBUNIT_ID + "/commandJob", cmd_job_body(job_id, name, start_cmd)) print(" OK: " + job_id) except HTTPError as e: msg = json.loads(e.read().decode()).get("message", "ERROR") print(" FAILED [" + job_id + "]: " + msg) # ── Step 4: ロック解放 ──────────────────────────────────────────────────── print("==[Step 4] Unlock ==") try: req = Request( url=managerUrl + "JobRestEndpoints/job/setting/jobunit/" + JOBUNIT_ID + "/lock/" + str(session), headers={"Authorization": "Bearer " + token}, method="DELETE" ) with urlopen(req) as res: res.read() print("OK") except HTTPError as e: print("FAILED: " + json.loads(e.read().decode()).get("message", "ERROR")) |
スクリプトを実行すると、以下のように出力されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
==[Step 1] GET job tree → updateTime == updateTime=1746xxxxxxxxx ==[Step 2] Lock == session=12345 ==[Step 3] POST: register command jobs == OK: CMD_JOB_01 OK: CMD_JOB_02 OK: CMD_JOB_03 OK: CMD_JOB_04 OK: CMD_JOB_05 ==[Step 4] Unlock == OK |
● GUI での登録確認
スクリプト実行後、Hinemos Webクライアントのジョブ設定パースペクティブを確認します。
JOBUNIT 配下に 5 件のコマンドジョブが登録されていることが確認できます。

登録されたジョブをダブルクリックして開き、起動コマンドが正しく設定されていることも確認します。

5. 起動コマンドの一括更新
登録済みのコマンドジョブの起動コマンドを変更するには、REST API 開発ガイドの
「18.4 コマンドジョブを変更する」に記載の PUT /jobunit/{jobunitId}/commandJob/{jobId} を使用します。
以下のスクリプトは引数で指定したジョブの起動コマンドを変更します。
引数は「ログインユーザ名 / ジョブユニットID / ジョブID / 新しい起動コマンド」の順です。
|
1 |
python updateCommandJob.py hinemos JOBUNIT CMD_JOB_01 /opt/new/scripts/job_01.sh |
updateCommandJob.py のコード(クリックで展開)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
#!/usr/libexec/platform-python from urllib.request import * from urllib.error import HTTPError import sys import json args = sys.argv user = args[1] jobunitId = args[2] jobId = args[3] newStartCmd = args[4] managerUrl = "http://[HinemosマネージャのIPアドレス]:8080/HinemosWeb/api/" try: file = open("./token_" + user + ".txt") token = file.read() finally: file.close() def api(method, path, body=None): headers = {"Authorization": "Bearer " + token} data = None if body is not None: headers["Content-Type"] = "application/json" data = json.dumps(body).encode() req = Request(url=managerUrl + path, headers=headers, data=data, method=method) with urlopen(req) as res: raw = res.read() return json.loads(raw) if raw else None # ジョブツリーから対象ジョブのデータを取得 tree = api("GET", "JobRestEndpoints/job/setting/job_treeFull") updateTime = None jobData = None def find_in_tree(items): global updateTime, jobData for item in items: d = item.get("data", {}) if d.get("type") == "JOBUNIT" and d.get("id") == jobunitId: updateTime = d.get("updateTime") if d.get("id") == jobId and d.get("jobunitId") == jobunitId: jobData = d if item.get("children"): find_in_tree(item["children"]) find_in_tree(tree if isinstance(tree, list) else [tree]) if updateTime is None: print("ERROR: jobunit not found: " + jobunitId) sys.exit(1) if jobData is None: print("ERROR: job not found: " + jobId) sys.exit(1) # ロック取得 lock = api("POST", "JobRestEndpoints/job/setting/jobunit/" + jobunitId + "/lock", {"updateTime": updateTime, "forceFlag": True}) session = lock["editSession"] print("Locked. session=" + str(session)) # startCommand を変更して PUT jobData["command"]["startCommand"] = newStartCmd try: api("PUT", "JobRestEndpoints/job/setting/jobunit/" + jobunitId + "/commandJob/" + jobId + "?isClient=true", jobData) print("Updated: " + jobId + " startCommand=" + newStartCmd) except HTTPError as e: print("PUT failed: " + json.loads(e.read().decode()).get("message", "ERROR")) # ロック解放 try: req = Request( url=managerUrl + "JobRestEndpoints/job/setting/jobunit/" + jobunitId + "/lock/" + str(session), headers={"Authorization": "Bearer " + token}, method="DELETE" ) with urlopen(req) as res: res.read() print("Unlocked.") except HTTPError as e: print("Unlock failed: " + json.loads(e.read().decode()).get("message", "ERROR")) |
5 件まとめてパスを変更する場合は、シェルのループで実行できます。
|
1 2 3 4 |
for i in 01 02 03 04 05; do python updateCommandJob.py hinemos JOBUNIT CMD_JOB_$i \ /opt/new/scripts/job_$i.sh done |
実行結果:
|
1 2 3 4 5 6 7 |
Locked. session=12346 Updated: CMD_JOB_01 startCommand=/opt/new/scripts/job_01.sh Unlocked. Locked. session=12347 Updated: CMD_JOB_02 startCommand=/opt/new/scripts/job_02.sh Unlocked. ... |
● GUI での更新確認
スクリプト実行後、GUI でジョブを開き、起動コマンドが更新されていることを確認します。

6. リクエストボディ作成時のポイント
REST API でコマンドジョブを登録・更新する際、リクエストボディを組み立てる上で確認が必要だったポイントをご紹介します。
● その1:stopType と stopCommand の組み合わせ
REST API 開発ガイド「18.2 コマンドジョブを追加する」の stopType パラメータについて、
EXECUTE_COMMAND(停止コマンド実行)を指定する場合は stopCommand も合わせて設定する必要があります。
停止コマンドを使用しない場合は DESTROY_PROCESS(プロセス終了)を指定してください。
|
1 2 3 |
# 停止コマンドを使用しない場合 "stopType": "DESTROY_PROCESS", "stopCommand": "", |
● その2:通知優先度の 4 フィールドは必須
REST API 開発ガイド「18.2 コマンドジョブを追加する」のリクエストボディには、通知優先度として以下の 4 フィールドの指定が必要です。
通知を使用しない場合は "NONE" を設定してください。
|
1 2 3 4 |
"beginPriority": "NONE", "normalPriority": "NONE", "warnPriority": "NONE", "abnormalPriority": "NONE", |
● その3:リトライ系フィールドの下限値
リトライ機能を使用しない場合でも、以下のフィールドには 0 ではなく 1 以上の値を設定してください。
| フィールド | 下限値 |
|---|---|
commandRetry(繰り返し実行回数) |
1 |
jobRetry(繰り返し実行回数) |
1 |
messageRetryEndValue(エラー時終了値) |
1 |
● その4:出力情報の directory フィールド
ファイル出力を使用しない場合(valid: false)でも、normalJobOutputInfo / errorJobOutputInfo の directory および fileName フィールドには空文字以外の値を設定してください。
|
1 |
"normalJobOutputInfo": {"directory": "/", "fileName": "/", "valid": False, ...} |
● その5:遅延設定フィールドの下限値
開始遅延・終了遅延の機能を使用しない場合でも、以下のフィールドには 0 ではなく 1 以上の値を設定してください。
0 を設定すると、GUI でジョブを開いた際に入力エラーが表示されます。
| フィールド | 下限値 |
|---|---|
startDelaySessionValue |
1 |
endDelaySessionValue |
1 |
endDelayJobValue |
1 |
■おわりに
今回は、Hinemos ver.7.2 の REST API を使ってコマンドジョブの登録・更新をコードで行う方法をご紹介しました。
REST API 開発ガイドに記載の各エンドポイントを活用することで、大量のジョブ登録やコマンドパスの一括変更といった作業をスクリプトで自動化できます。
リクエストボディを組み立てる際は、第6章でご紹介したポイントを参考にしていただければ幸いです。
大量のジョブをコードで管理したい場合や、設定を一括変更したい場合にぜひご活用ください!
Xをフォローする
メルマガに登録する