[小ネタ] AWS CloudShell 上で Datadog のユーザー一覧を CSV 出力する

[小ネタ] AWS CloudShell 上で Datadog のユーザー一覧を CSV 出力する

Datadog に登録されているユーザー一覧をCSV形式で取得・出力する方法をご紹介します。AWS CloudShell上で動作するワンクリック実行スクリプトにより、ローカル環境の準備なく、ユーザー情報を簡単に棚卸しできます。
2026.07.11

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 を押します。

スクリプトの流れは以下のとおりです。

  1. curl / jq / DuckDB CLI の存在確認(なければインストール)
  2. Datadog API site・API Key・Application Key の対話入力
  3. /api/v2/users/api/v2/roles をページネーションしながら全件取得
  4. jq で DuckDB 用の中間ファイル(JSONL)に変換
  5. 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 ファイルをダウンロードする

  1. CloudShell 画面右上の Actions をクリックします。
  2. Download file をクリックします。
  3. 完了メッセージに表示された CSV ファイルパスを入力します。

例:

/home/cloudshell-user/datadog-user-export/dd-users_2026-07-09.csv
  1. 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月 アノテーション㈱から社名変更しました。

この記事をシェアする

関連記事