本記事は連載「ServiceNow標準機能だけで作るPull型自動化連携」の第4回です。
第3回では、Catalog Itemから依頼を出すと Open + autoengine1 のSCTASKが生成されるところまで作りました。
今回は、自動化エンジン側からOOTBのTable APIだけで、実行待ちのSCTASKを取得し、確保する部分を実装します。
この回で作るもの
| ステップ | 使うAPI | 状態の変化 |
|---|---|---|
| 実行待ちの取得 | Table API(GET) | 変化なし |
| 確保 | Table API(PATCH) |
Open + autoengine1 → In Progress + autoengine1
|
この回で、ServiceNow側に新しく作るものはありません。OOTBのTable APIだけを使います。
1. 準備
1.1 必要なもの
- 第3回で作成したCatalog Item、Execution Plan、自動化ユーザー
- APIサービスユーザー
svc_autoengine1_api -
curlとjqが使えるLinux環境
1.2 接続設定
export SN_INSTANCE="https://<instance>.service-now.com"
export SN_USER="svc_autoengine1_api"
export SN_PASSWORD='********'
export TABLE_API_PATH="/api/now/table/sc_task"
export ASSIGNMENT_GROUP="autoengine1group"
export WAITING_ASSIGNEE="autoengine1"
export OPEN_STATE="1"
export IN_PROGRESS_STATE="2"
Stateの内部値(Open=1、In Progress=2)は、インスタンスによって異なる場合があります。第3回の動作確認で調べた値を設定してください。
2. 実行待ちのSCTASKを取得する
2.1 検索条件
実行待ちの条件は、第2回で決めた状態表現そのものです。
| 条件 | Encoded Query |
|---|---|
| StateがOpen | state=1 |
Assignment groupがautoengine1group
|
assignment_group.name=autoengine1group |
Assigned toがautoengine1
|
assigned_to.user_name=autoengine1 |
| 作成日時の古い順 | ORDERBYsys_created_on |
これらを ^(AND)でつなぎます。
state=1^assignment_group.name=autoengine1group^assigned_to.user_name=autoengine1^ORDERBYsys_created_on
グループとユーザーは、sys_idではなく名前(name、user_name)で指定しています。これにより、インスタンスが変わっても同じスクリプトをそのまま使えます。
2.2 curlで取得する
query="state=${OPEN_STATE}^assignment_group.name=${ASSIGNMENT_GROUP}^assigned_to.user_name=${WAITING_ASSIGNEE}^ORDERBYsys_created_on"
curl --fail-with-body --silent --show-error \
--user "${SN_USER}:${SN_PASSWORD}" \
--header "Accept: application/json" \
--get "${SN_INSTANCE}${TABLE_API_PATH}" \
--data-urlencode "sysparm_query=${query}" \
--data-urlencode "sysparm_fields=sys_id,number" \
--data-urlencode "sysparm_limit=10000" | jq .
sysparm_fields で、必要な sys_id と number だけを返すようにしています。変数などの詳細は、次回作るScripted REST APIで、作業の直前に取得します。
2.3 レスポンス例
{
"result": [
{ "sys_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "number": "SCTASK0010006" },
{ "sys_id": "0f9e8d7c6b5a49382716a5b4c3d2e1f0", "number": "SCTASK0010007" }
]
}
実行待ちがない場合は、result が空の配列になります。
3. SCTASKを確保する
3.1 確保とは
取得したSCTASKのStateを In Progress に変えることを、この連載では「確保」と呼びます。
Open + autoengine1 → In Progress + autoengine1
確保すると、2章の検索条件(State=Open)に当てはまらなくなるので、次回のPullで同じSCTASKを再取得しません。Assigned toはこの時点では変えません。
3.2 curlで確保する
task_sys_id="a1b2c3d4e5f60718293a4b5c6d7e8f90"
curl --fail-with-body --silent --show-error \
--user "${SN_USER}:${SN_PASSWORD}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--request PATCH \
--data "{\"state\":\"${IN_PROGRESS_STATE}\"}" \
"${SN_INSTANCE}${TABLE_API_PATH}/${task_sys_id}" | jq '.result | {number, state}'
Table APIの更新はsys_idで対象を指定します。ここで使うsys_idは、直前のGETで受け取った値をそのまま渡すだけです。外部に公開するインターフェースやスクリプトの設定値として、sys_idを持つ必要はありません。
3.3 確保できたことを確認する
レスポンスの state が 2 になっていれば、確保できています。
{
"number": "SCTASK0010006",
"state": "2"
}
ServiceNowの画面では、SCTASKのStateが In Progress、Assigned toが autoengine1 のままになっていることを確認します。
4. 取得と確保をまとめたスクリプト
#!/usr/bin/env bash
set -Eeuo pipefail
: "${SN_INSTANCE:?set SN_INSTANCE}"
: "${SN_USER:?set SN_USER}"
: "${SN_PASSWORD:?set SN_PASSWORD}"
TABLE_API_PATH="${TABLE_API_PATH:-/api/now/table/sc_task}"
ASSIGNMENT_GROUP="${ASSIGNMENT_GROUP:-autoengine1group}"
WAITING_ASSIGNEE="${WAITING_ASSIGNEE:-autoengine1}"
OPEN_STATE="${OPEN_STATE:-1}"
IN_PROGRESS_STATE="${IN_PROGRESS_STATE:-2}"
api() {
curl --fail-with-body --silent --show-error \
--user "${SN_USER}:${SN_PASSWORD}" \
--header "Accept: application/json" "$@"
}
get_open_tasks() {
local query
query="state=${OPEN_STATE}^assignment_group.name=${ASSIGNMENT_GROUP}^assigned_to.user_name=${WAITING_ASSIGNEE}^ORDERBYsys_created_on"
api --get "${SN_INSTANCE}${TABLE_API_PATH}" \
--data-urlencode "sysparm_query=${query}" \
--data-urlencode "sysparm_fields=sys_id,number" \
--data-urlencode "sysparm_limit=10000"
}
claim_task() {
local task_sys_id="$1"
api --header "Content-Type: application/json" \
--request PATCH \
--data "$(jq -n --arg state "$IN_PROGRESS_STATE" '{state:$state}')" \
"${SN_INSTANCE}${TABLE_API_PATH}/${task_sys_id}"
}
main() {
local tasks_json total index task_sys_id task_number claim_response
tasks_json="$(get_open_tasks)"
total="$(jq '.result | length' <<<"$tasks_json")"
if [[ "$total" -eq 0 ]]; then
echo "No Open + ${WAITING_ASSIGNEE} SCTASK found."
exit 0
fi
for ((index=0; index<total; index++)); do
task_sys_id="$(jq -r ".result[${index}].sys_id" <<<"$tasks_json")"
task_number="$(jq -r ".result[${index}].number" <<<"$tasks_json")"
echo "Claiming ${task_number}: state ${OPEN_STATE} -> ${IN_PROGRESS_STATE}"
claim_response="$(claim_task "$task_sys_id")"
if [[ "$(jq -r '.result.state // empty' <<<"$claim_response")" != "$IN_PROGRESS_STATE" ]]; then
echo "ERROR: claim failed for ${task_number}." >&2
jq . <<<"$claim_response" >&2
exit 1
fi
echo "Claimed ${task_number}"
# 次回: Scripted REST APIで詳細を取得し、実行開始(autoengine1 -> autoengine1running)
done
}
main "$@"
実運用では、このスクリプトをcronや自動化エンジンのスケジュール機能で定期実行します。AAPを使う場合は、同じ処理をPlaybookの ansible.builtin.uri モジュールで書くこともできます。
5. なぜTable APIの単純な更新で確保するのか
確保の方法には、Scripted REST APIで「現在のStateがOpenの場合だけ更新する」条件付き更新を作る選択肢もあります。
本連載では、次の理由からTable APIの単純な更新を採用しました。
- ServiceNow側の開発を増やさない: 確保のためだけにAPIを作ると、保守の対象が増えます。
- ポーラーは1つの前提: ディスパッチャーが1つなら、同じSCTASKを2つの処理が同時に確保することはありません。
-
二重実行は次のステップで防ぐ: 次回作る実行開始APIは、Assigned toが
autoengine1の場合だけautoengine1runningへ更新する条件付き更新(Compare-and-set)です。仮に同じSCTASKを2回確保しても、実際に作業を始められるのは1回だけです。
- ポーラー(ディスパッチャー)を複数動かす場合は、確保の段階での排他制御を追加で設計してください。
- 取得してから確保するまでの間に自動化エンジンが停止すると、同じSCTASKが次回も取得されます。自動化処理は、できるだけ冪等にしておくと安全です。
- 確保したまま長時間
In Progress + autoengine1に残ったSCTASKは、ServiceNowの標準機能(SLAやエスカレーション)で検知する想定です。
6. 動作確認
-
実行待ちのSCTASKがある状態でGETすると、そのSCTASKの
sys_idとnumberが返る - 他のグループや他の担当者のSCTASKは返らない
-
実行待ちがない場合、
resultが空の配列になる -
PATCH後、レスポンスの
stateが2になる -
画面上で、SCTASKが
In Progress + autoengine1になっている - 確保後に再度GETすると、確保したSCTASKは返らない
第7回で紹介するBusiness Ruleを設定すると、確保した時点でRITMのActivityに進捗が表示されるようになります。
次回予告
次回は、ServiceNow側にScripted REST APIを作成し、SCTASKの詳細(変数を含む)を取得して、実行開始を宣言する部分を実装します。expected_current_assigned_to を使った条件付き更新と、400・409のエラー設計を解説します。
連載記事
- 第1回 なぜ今Pull型連携なのか
- 第2回 全体アーキテクチャと設計方針
- 第3回 Catalog Item・Variable Set・Execution Planの準備
- 第4回 Table APIで実行待ちタスクを取得・確保する(本記事)
※ 本記事の内容は個人の見解であり、所属する組織の公式な見解ではありません。