[小ネタ] AWS CloudShell 上で Datadog のユーザー一覧を CSV 出力する
1. はじめに
こんにちは。クラスメソッドオペレーションズのあのふじたです。
Datadog に登録されているユーザー一覧を棚卸しする機会があり、Datadog User 一覧を作成する を書きました。
ローカル環境をあまり使わないようなユーザー向けに、記事を参考にして AWS CloudShell 上で完結するスクリプトを作成しました。
備忘録として記事にします。
スクリプトは AI の支援を活用しながら作成しています。
2. やりたいこと
Datadog に登録されているユーザー一覧を取得し、CSV ファイルとして出力します。
出力する項目は以下です。
| 列名 | 内容 |
|---|---|
name |
ユーザー名 |
email |
メールアドレス |
status |
ユーザーステータス |
created_at |
作成日時 |
last_login_time |
最終ログイン日時 |
roles |
付与されている Role |
Datadog API のレスポンスではユーザーと Role が分かれて返ってくるため、DuckDB で結合してから CSV に出力する構成にしています。
3. 事前に準備するもの
作業前に以下を準備します。
- AWS マネジメントコンソールにログインできるアカウント
- AWS CloudShell を利用できる権限
- Datadog API Key
- Datadog Application Key
- Datadog API site
Datadog Application Key には、少なくとも以下の権限が必要です。
user_access_read
4. Datadog API site の確認
Datadog の利用環境に応じて、後続手順で入力する API site が異なります。
| Datadog 環境 | 入力する値 |
|---|---|
| US1 | api.datadoghq.com |
| US3 | api.us3.datadoghq.com |
| US5 | api.us5.datadoghq.com |
| EU | api.datadoghq.eu |
| AP1 | api.ap1.datadoghq.com |
| AP2 | api.ap2.datadoghq.com |
通常の US1 環境であれば、後続の入力では何も入力せず Enter で問題ありません。
5. CloudShell でスクリプトを実行する
AWS マネジメントコンソールにログインし、画面上部の CloudShell アイコン、または検索欄から CloudShell を開きます。
ターミナル画面が表示されたら、以下の内容を 最初から最後までまとめてコピー し、CloudShell に貼り付けて Enter を押します。
スクリプトの流れは以下のとおりです。
curl/jq/DuckDB CLIの存在確認(なければインストール)- Datadog API site・API Key・Application Key の対話入力
/api/v2/usersと/api/v2/rolesをページネーションしながら全件取得jqで DuckDB 用の中間ファイル(JSONL)に変換- DuckDB でユーザーと Role を結合し、CSV と Markdown 表を出力
mkdir -p ~/datadog-user-export
cd ~/datadog-user-export
cat > datadog_user_export.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
WORKDIR="$HOME/datadog-user-export"
PAGES_DIR="$WORKDIR/pages"
PAGE_SIZE=100
mkdir -p "$WORKDIR"
mkdir -p "$PAGES_DIR"
cd "$WORKDIR"
trap 'unset DD_API_KEY DD_APP_KEY DD_SITE' EXIT
echo "========================================"
echo "Datadog ユーザー一覧 CSV 出力"
echo "========================================"
echo ""
echo "=== 必要なコマンドを確認します ==="
# curl の確認
if ! command -v curl >/dev/null 2>&1; then
echo "ERROR: curl が見つかりません。"
echo "AWS CloudShell 環境を確認してください。"
exit 1
fi
# jq の確認・インストール
if ! command -v jq >/dev/null 2>&1; then
echo "jq が見つからないためインストールします..."
if command -v dnf >/dev/null 2>&1; then
sudo dnf install -y jq
else
sudo yum install -y jq
fi
fi
# DuckDB CLI の確認・インストール
find_duckdb() {
# 1. PATH 上の duckdb を確認
if command -v duckdb >/dev/null 2>&1; then
command -v duckdb
return 0
fi
# 2. install.duckdb.org が作る latest を確認
if [ -x "$HOME/.duckdb/cli/latest/duckdb" ]; then
echo "$HOME/.duckdb/cli/latest/duckdb"
return 0
fi
# 3. ~/.duckdb/cli/*/duckdb の中から実行可能なものを探す
if [ -d "$HOME/.duckdb/cli" ]; then
local found
found="$(
find "$HOME/.duckdb/cli" \
-mindepth 2 \
-maxdepth 2 \
-type f \
-name duckdb \
-perm -111 2>/dev/null \
| sort -V \
| tail -n 1
)"
if [ -n "$found" ] && [ -x "$found" ]; then
echo "$found"
return 0
fi
fi
return 1
}
DUCKDB_BIN="$(find_duckdb || true)"
if [ -z "${DUCKDB_BIN}" ]; then
echo "DuckDB CLI が見つからないためインストールします..."
curl -fsSL https://install.duckdb.org | sh
DUCKDB_BIN="$(find_duckdb || true)"
fi
if [ -z "${DUCKDB_BIN}" ] || [ ! -x "$DUCKDB_BIN" ]; then
echo "ERROR: DuckDB CLI が見つかりません。"
echo ""
echo "確認した場所:"
echo "- PATH 上の duckdb"
echo "- $HOME/.duckdb/cli/latest/duckdb"
echo "- $HOME/.duckdb/cli/*/duckdb"
echo ""
echo "確認してください:"
echo "- CloudShell から https://install.duckdb.org にアクセスできるか"
echo "- 組織のネットワーク制限で外部ダウンロードが止められていないか"
exit 1
fi
echo ""
echo "curl:"
curl --version | head -n 1
echo ""
echo "jq:"
jq --version
echo ""
echo "DuckDB:"
echo "$DUCKDB_BIN"
"$DUCKDB_BIN" --version
echo ""
echo "=== Datadog 接続情報を入力します ==="
echo ""
read -r -p "Datadog API site を入力してください [api.datadoghq.com]: " DD_SITE
DD_SITE="${DD_SITE:-api.datadoghq.com}"
DD_SITE="${DD_SITE#https://}"
DD_SITE="${DD_SITE#http://}"
DD_SITE="${DD_SITE%/}"
read -r -s -p "Datadog API Key を貼り付けて Enter を押してください: " DD_API_KEY
echo ""
read -r -s -p "Datadog Application Key を貼り付けて Enter を押してください: " DD_APP_KEY
echo ""
if [ -z "$DD_API_KEY" ]; then
echo "ERROR: Datadog API Key が空です。"
exit 1
fi
if [ -z "$DD_APP_KEY" ]; then
echo "ERROR: Datadog Application Key が空です。"
exit 1
fi
echo ""
echo "Datadog API site: $DD_SITE"
echo ""
echo "=== 古い一時ファイルを削除します ==="
rm -f "$PAGES_DIR"/*.json
rm -f "$WORKDIR"/all_users.json
rm -f "$WORKDIR"/roles_data.json
rm -f "$WORKDIR"/user_roles.jsonl
rm -f "$WORKDIR"/roles.jsonl
rm -f "$WORKDIR"/query.sql
rm -f "$WORKDIR"/export.sql
rm -f "$WORKDIR"/dd-users_*.csv
rm -f "$WORKDIR"/dd-users_*.md
api_get() {
local url="$1"
local output_file="$2"
local http_status
local attempt=1
while true; do
if http_status="$(
curl -sS \
-o "$output_file" \
-w "%{http_code}" \
-H "Accept: application/json" \
-H "DD-API-KEY: ${DD_API_KEY}" \
-H "DD-APPLICATION-KEY: ${DD_APP_KEY}" \
"$url"
)"; then
:
else
http_status="000"
fi
if [[ "$http_status" =~ ^2[0-9][0-9]$ ]]; then
return 0
fi
if [ "$http_status" = "429" ] && [ "$attempt" -lt 5 ]; then
local wait_seconds=$((attempt * attempt))
echo "Rate limit のため ${wait_seconds} 秒待機します..."
sleep "$wait_seconds"
attempt=$((attempt + 1))
continue
fi
echo ""
echo "ERROR: Datadog API の呼び出しに失敗しました。"
echo "HTTP status: $http_status"
echo ""
echo "Datadog からの応答:"
cat "$output_file" || true
echo ""
echo ""
echo "確認してください:"
echo "- Datadog API Key が正しいか"
echo "- Datadog Application Key が正しいか"
echo "- Application Key に user_access_read 権限があるか"
echo "- Datadog API site が正しいか"
echo ""
exit 1
done
}
fetch_paginated_array() {
local label="$1"
local endpoint="$2"
local output_json="$3"
local prefix="$4"
local page=0
local count=0
local total=0
local response_file
local page_array_file
local tmp_file
local url
echo "[]" > "$output_json"
echo "=== ${label} を取得します ==="
while true; do
response_file="$PAGES_DIR/${prefix}_response_$(printf "%04d" "$page").json"
page_array_file="$PAGES_DIR/${prefix}_data_$(printf "%04d" "$page").json"
tmp_file="$PAGES_DIR/${prefix}_tmp_$(printf "%04d" "$page").json"
url="https://${DD_SITE}${endpoint}?page%5Bsize%5D=${PAGE_SIZE}&page%5Bnumber%5D=${page}"
api_get "$url" "$response_file"
if ! jq -e '.data | type == "array"' "$response_file" >/dev/null; then
echo "ERROR: Datadog API のレスポンス形式が想定外です。"
echo "対象ファイル: $response_file"
cat "$response_file"
exit 1
fi
jq '.data' "$response_file" > "$page_array_file"
count="$(jq 'length' "$page_array_file")"
if [ "$count" -eq 0 ]; then
break
fi
jq -s '.[0] + .[1]' "$output_json" "$page_array_file" > "$tmp_file"
mv "$tmp_file" "$output_json"
echo "${label}: page ${page} - ${count} 件取得"
total=$((total + count))
if [ "$count" -lt "$PAGE_SIZE" ]; then
break
fi
page=$((page + 1))
done
echo "${label}: 合計 ${total} 件"
echo ""
}
fetch_paginated_array "Users" "/api/v2/users" "$WORKDIR/all_users.json" "users"
fetch_paginated_array "Roles" "/api/v2/roles" "$WORKDIR/roles_data.json" "roles"
echo "=== jq で DuckDB 用の中間ファイルを作成します ==="
jq -c '
.[] as $u
| if (($u.relationships.roles.data // []) | length) > 0 then
$u.relationships.roles.data[]
| {
user_id: $u.id,
name: $u.attributes.name,
email: $u.attributes.email,
status: $u.attributes.status,
created_at: $u.attributes.created_at,
last_login_time: $u.attributes.last_login_time,
role_id: .id
}
else
{
user_id: $u.id,
name: $u.attributes.name,
email: $u.attributes.email,
status: $u.attributes.status,
created_at: $u.attributes.created_at,
last_login_time: $u.attributes.last_login_time,
role_id: null
}
end
' "$WORKDIR/all_users.json" > "$WORKDIR/user_roles.jsonl"
jq -c '
.[]
| {
role_id: .id,
role_name: .attributes.name
}
' "$WORKDIR/roles_data.json" > "$WORKDIR/roles.jsonl"
USER_ROLE_ROWS="$(wc -l < "$WORKDIR/user_roles.jsonl" | tr -d ' ')"
ROLE_ROWS="$(wc -l < "$WORKDIR/roles.jsonl" | tr -d ' ')"
echo "user_roles.jsonl: ${USER_ROLE_ROWS} 行"
echo "roles.jsonl: ${ROLE_ROWS} 行"
echo ""
OUTPUT_FILE="$WORKDIR/dd-users_$(date -I).csv"
MD_FILE="$WORKDIR/dd-users_$(date -I).md"
cat > "$WORKDIR/query.sql" <<'SQL'
WITH user_roles AS (
SELECT
user_id,
name,
email,
status,
created_at,
last_login_time,
CAST(role_id AS VARCHAR) AS role_id
FROM read_json_auto('user_roles.jsonl', format = 'newline_delimited')
),
roles AS (
SELECT
CAST(role_id AS VARCHAR) AS role_id,
role_name
FROM read_json_auto('roles.jsonl', format = 'newline_delimited')
),
joined AS (
SELECT DISTINCT
ur.user_id,
ur.name,
ur.email,
ur.status,
ur.created_at,
ur.last_login_time,
r.role_name
FROM user_roles ur
LEFT JOIN roles r
ON ur.role_id = r.role_id
)
SELECT
name,
email,
status,
created_at,
last_login_time,
coalesce(
string_agg(role_name, ', ' ORDER BY role_name)
FILTER (WHERE role_name IS NOT NULL),
''
) AS roles
FROM joined
GROUP BY
user_id,
name,
email,
status,
created_at,
last_login_time
ORDER BY
CASE status
WHEN 'Active' THEN 1
WHEN 'Pending' THEN 2
WHEN 'Disabled' THEN 3
ELSE 4
END,
email
SQL
cat > "$WORKDIR/export.sql" <<SQL
COPY (
$(cat "$WORKDIR/query.sql")
) TO '${OUTPUT_FILE}' (HEADER, DELIMITER ',');
SQL
echo "=== DuckDB で CSV を作成します ==="
"$DUCKDB_BIN" < "$WORKDIR/export.sql"
echo "=== DuckDB で Markdown 表を作成します ==="
"$DUCKDB_BIN" -markdown -c "$(cat "$WORKDIR/query.sql")" > "$MD_FILE"
CSV_ROWS="$(tail -n +2 "$OUTPUT_FILE" | wc -l | tr -d ' ')"
echo ""
echo "========================================"
echo "完了"
echo "========================================"
echo "CSV ファイル:"
echo "$OUTPUT_FILE"
echo ""
echo "Markdown ファイル:"
echo "$MD_FILE"
echo ""
echo "CSV 行数:"
echo "$CSV_ROWS"
echo ""
echo "次の手順:"
echo "CloudShell 画面右上の Actions から Download file を選び、"
echo "上記の CSV ファイルパスを入力してダウンロードしてください。"
echo ""
SH
chmod +x datadog_user_export.sh
./datadog_user_export.sh
6. 実行中に入力する内容
スクリプト実行中に、以下のように聞かれます。
Datadog API site を入力してください [api.datadoghq.com]:
US1 環境の場合は何も入力せずに Enter を押します。
次に、Datadog の Key を聞かれます。
Datadog API Key を貼り付けて Enter を押してください:
Datadog Application Key を貼り付けて Enter を押してください:
この入力内容は画面には表示されません。
表示されなくても入力はされているので、そのまま Enter を押して問題ありません。
7. 実行イメージ
正常に完了すると、最後に以下のような表示が出ます。
========================================
完了
========================================
CSV ファイル:
/home/cloudshell-user/datadog-user-export/dd-users_2026-07-09.csv
Markdown ファイル:
/home/cloudshell-user/datadog-user-export/dd-users_2026-07-09.md
CSV 行数:
123
次の手順:
CloudShell 画面右上の Actions から Download file を選び、
上記の CSV ファイルパスを入力してダウンロードしてください。
CSV と同じ内容の Markdown 表も出力しているので、Backlog のチケット等にそのまま貼りたい場合は md ファイルが利用できます。
8. CSV ファイルをダウンロードする
- CloudShell 画面右上の Actions をクリックします。
- Download file をクリックします。
- 完了メッセージに表示された CSV ファイルパスを入力します。
例:
/home/cloudshell-user/datadog-user-export/dd-users_2026-07-09.csv
- Download をクリックすると、PC に CSV ファイルが保存されます。
9. ダウンロード後の後片付け
CSV にはユーザー名、メールアドレス、最終ログイン日時、Role などの情報が含まれます。
ダウンロード後は CloudShell 上の一時ファイルを削除しておきます。
CloudShell に以下を貼り付けて実行します。
cd ~/datadog-user-export
rm -f all_users.json
rm -f roles_data.json
rm -f user_roles.jsonl
rm -f roles.jsonl
rm -f query.sql
rm -f export.sql
rm -f dd-users_*.csv
rm -f dd-users_*.md
rm -f pages/*.json
unset DD_API_KEY
unset DD_APP_KEY
unset DD_SITE
echo "後片付けが完了しました。"
10. この手順で使うもの
| 用途 | 使用するもの |
|---|---|
| Datadog API の呼び出し | curl |
| JSON の加工 | jq |
| Users と Roles の結合 | DuckDB CLI |
| CSV 出力 | DuckDB CLI |
| ファイルダウンロード | AWS CloudShell |
11. よくあるエラー
HTTP status: 401
原因候補:
- Datadog API Key が間違っている
- Datadog Application Key が間違っている
- Key が無効化されている
対応:
- Key を確認して、最初からやり直してください。
HTTP status: 403
原因候補:
- Application Key に
user_access_read権限がない - Datadog 側の権限が不足している
対応:
- Datadog 管理者に Application Key の権限を確認してもらってください。
HTTP status: 404
原因候補:
- Datadog API site が間違っている
対応:
- Datadog 環境に応じて、以下を確認してください。
api.datadoghq.com
api.datadoghq.eu
api.us3.datadoghq.com
api.us5.datadoghq.com
api.ap1.datadoghq.com
api.ap2.datadoghq.com
DuckDB CLI が見つかりません
原因候補:
- DuckDB のインストールに失敗している
- CloudShell から外部ダウンロードが制限されている
対応:
- AWS 管理者に CloudShell の外部通信制限を確認してください。
- このスクリプトでは、以下の場所を順番に探します。
PATH 上の duckdb
/home/cloudshell-user/.duckdb/cli/latest/duckdb
/home/cloudshell-user/.duckdb/cli/*/duckdb
12. 最後に
実行環境を CloudShell に寄せることで、作業者のローカル環境に依存せず「コピーして貼り付けるだけ」で同じ結果が得られるようにしました。
Datadog のユーザー棚卸しなどで、このブログがどなたかのお役に立てば幸いです。
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイト をぜひご覧ください。※2026年1月 アノテーション㈱から社名変更しました。




