NTTドコモR&Dの技術ブログです。

Slackワークフローで実現するアカウント管理自動化

はじめに

こんにちは、NTTドコモ情報システム部の寺井です。
普段の業務では、社外システムとの情報連携に係るシステム開発を行っており、社内外の方とのコミュニケーションには主にSlackを利用しています。また、担当システムではDatadogを活用した監視運用も行っています。

今回、アカウント管理業務の効率化を目的として、Slackワークフローの新機能「ブランチ」とアカウント管理簿として利用しているGoogleスプレッドシートを活用し、アカウント作成・削除を自動化する仕組みを構築しました。本仕組みを導入することで、全体の作業時間を短縮(約1時間→約5分)できます。
本記事では、Datadogアカウントの作成・削除を例に、その仕組みをご紹介します。
ご興味ある方は、ぜひご覧ください!


記事の目的

アカウント管理業務は、想像以上に時間を要することがありますよね。
私のプロジェクトでは、以下の手順で作業を進めています。
おそらく、皆さんの中にも同様の流れで進めているプロジェクトが多いのではないでしょうか。

登場人物

  • アカウント申請者:新規アカウントの作成・削除を申請する人
  • アカウント管理者:実務レベルでの設定・削除・権限管理をする人
  • アカウント管理責任者:組織内でのアカウント管理全般を統括し、承認権限を持つ人

アカウント管理業務



この方法では、人の手による作業が必要となるため、操作ミスが発生する可能性があります。また、アカウントの作成や削除の過程で、管理簿との突合も必要です。さらに、一連の作業を完了するだけで、約1時間もの時間を要してしまいます。

そこで、私は今年8月頃に追加されたSlackワークフローの新機能「ブランチ」*1とGoogleスプレッドシートを活用し、アカウント管理業務の手順1~2、4、6~7を自動化することで、この課題を解決しました。

以下では、Slackワークフローの「ブランチ」機能と、今回構築したシステムの概要をご紹介します。


Slackワークフロー ブランチ(条件分岐)機能の概要

これまでは、ワークフロー内で条件分岐ができなかったため、用途ごとに個別のワークフローを作成する必要がありました。
(例:アカウント作成用とアカウント削除用のワークフローをそれぞれ作成)
しかし、今回「ブランチ」機能が追加されたことで、一つのワークフローにまとめて管理できるようになりました。
なお、ブランチは最大で15個まで作成可能です。

例えば以下のような質問を作ったとして、


それぞれの回答に対して分岐させることができます。


さらに一つのブランチの中にさらに分岐を入れることもできます。


詳細はSlackのマニュアルを参照してください。
今回はこの機能を使って、一つのワークフローでアカウント作成、削除のワークフローを作ります。


システム概要

使用するツール

  • Slack
  • Googleスプレッドシート
  • Google Apps Script(GAS)
  • アカウント作成・削除する対象のツール(今回はDatadog)

作成したシステムの全体構成は以下です。


全体フロー

  1. Slackワークフロー起動
    Slackワークフローを起動し、アカウントの「作成」または「削除」を選択し、必要情報(ユーザー名・メールアドレスなど)を入力。
    → Googleスプレッドシートに送信し、管理簿を更新。
    ※【アカウント管理業務1.に該当】

  2. アカウント管理者による承認
    アカウント管理者にDMで承認依頼が届き、承認ボタンを押下。
    → Googleスプレッドシートに送信し、管理簿のステータスを更新(申請中 → 管理者承認済)。
    ※【アカウント管理業務2. 3.に該当】

  3. アカウント管理責任者による承認
    アカウント管理責任者にDMで承認依頼が届き、承認ボタンを押下。
    → Googleスプレッドシートに送信し、管理簿のステータスを更新(管理者承認済 → 責任者承認済)。
    ※【アカウント管理業務4. 5.に該当】

  4. Googleスプレッドシート更新をトリガーにGAS起動

  5. GASがDatadogにリクエスト送信
    アカウント作成または削除を実行。
    ※【アカウント管理業務6.に該当】

  6. DatadogからGASへレスポンス返却
    ※【アカウント管理業務6.に該当】

  7. 管理簿のステータスを更新(責任者承認済 → 完了)
    ※【アカウント管理業務7.に該当】

  8. Slackに完了通知送信


作り方

Slack

Slackワークフロー

ワークフロービルダーから「+新規」→「ワークフローを構築する」を押下。

以下のように作成します。

ワークフロー全体

ポイント)

  • 本ワークフローは、リンクから開始するように設定しています。
  • アカウント作成・削除は、「1.情報をフォームで収集する」で選択できるようにしています。(設定例は以下)
  • アカウント作成・削除に必要な情報は、「A2, B3.情報をフォームで収集する」で入力できるようにしています。(設定例は以下)

  • 上記で入力した内容は、Slackに投稿されるように設定しています。(設定例は以下。※青字は「{}変数を挿入する」から選択しています)
  • Googleスプレッドシートの更新は以下のように設定しています。

※ステータスのみ変更する場合は、以下のように設定します。

  • DMでメッセージを送信する場合は、以下のように設定します。

注意点)

  1. フォームでアカウント「作成」or「削除」の選択を入れなければ、ブランチを追加できません。
  2. 今回はDatadogアカウントの作成削除のため、「名前」、「メールアドレス」「権限」を入力に設定しますが、他ツールのアカウント作成削除の際には、必要に応じて項目を増やしてください。(項目:A2, B3)
  3. Googleスプレッドシートを更新する際には、「ファイル名」、「シート名」を間違えないように設定してください。
GASから通知を受け取るための準備(Incoming Webhookの設定)

「ツールと設定」→「ワークスペースの設定」→「Menu」→「App管理」
Incoming WebhookをSlackに追加する

通知するSlackチャンネルを選択→「Incoming Webhook インテグレーションの追加」を押下

Webhook URLをメモする(後で使います)→「設定を保存する」を押下

以上でSlackの設定は完了です。

Googleスプレッドシート

以下のようなシートを作成します。 シート名: Datadog A列: 名前 B列; メールアドレス C列: 権限 D列: 作成日 E列: 削除日 F列: ステータス G列: エラー内容

以上でGoogleスプレッドシートの準備は完了です。

Google Apps Script(GAS)

Googleスプレッドシートの上タブ「拡張機能」→「Apps Script」を押下
「コード.gs」に【付録「コード.gs」】をコピペする。

左タブの「プロジェクトの設定」→「appsscript.json」

マニフェスト ファイルをエディタで表示する にチェックを入れる

エディタの画面に戻ると、ファイルに「appsscript.json」が追加されています。 以下のように、「oauthScopes」を追加する。

"oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/spreadsheets.currentonly",
    "https://www.googleapis.com/auth/spreadsheets"
  ]

左タブの「プロジェクトの設定」→「スクリプト プロパティ」に値を追加する。

追加する値は以下。
DATADOG_API_KEY(Datadog APIキー) →Datadog UIのOrganization Settings→API Keysを選択→「New Keys」からAPIキーを作成し、赤枠の値をスクリプトプロパティに設定する。

注意)アカウント作成・削除権限を持っているアカウントで作成してください。

DATADOG_APP_KEY(Datadog APPキー)→Datadog UIのOrganization Settings→Application Keysを選択→「New Keys」からAPPキーを作成し、赤枠の値をスクリプトプロパティに設定する。

最終的にスクリプトプロパティの設定は以下のようになります。

続いて、「コード.gs」を編集していきます。 5行目const SLACK_WEBHOOK_URL=●●●; の●●●に上記で取得した、Webhook URLを入力する。

104行目-110行目のDatadogのRole UUIDを取得します。
Datadog UIのOrganization Settings→Rolesを選択→各権限を押下し、URLのrole_id=以降をメモする。

以下のようにRole UUIDを入力する。

続いて、トリガーを設定します。
左タブから「トリガー」→「トリガーを追加」を選択
以下のように設定します。

以上でGASの設定も完了しました。

(+α) DatadogのAudit Trail(監査証跡)より、アカウント作成・削除を監視する

Datadogアカウントを作成or削除した場合は、Audit Trailからログを確認することができます。

万が一、不正にアカウントが作成されてしまった場合でも、作成・削除ログをSlackへ通知することができるため、すぐに異常を検知できます。
なお、今回の記事では、この設定手順については割愛いたします。


完成したもの

実際に作ったものを動かしてみます。

全体フローの①
まずは、作成したSlackワークフローを起動します。

選択画面が出てきました。アカウント作成を実施していきます。

ワークフローを起動したチャンネルに投稿されました。

「続行する」を押すと以下の画面が出てきます。
今回はテストなので、適当に入力します。

スレッドに依頼内容が投稿されました。

アカウント管理簿にも反映されています。

全体フローの②
アカウント管理者のDMに承認依頼が来ました。

「承認する」ボタンを押すと、ステータスが「管理者作成依頼済」になりました。

全体フローの③
アカウント管理責任者のDMに承認依頼が来ました。

「承認する」ボタンを押すと、ステータスが「責任者作成依頼済」になりました。

全体フローの④-⑤
ほぼ同時にワークフローを起動したスレッドに作成中と投稿されました。 裏でGASがDatadogにアカウント作成のリクエストを送信しています。

全体フローの⑥-⑧
作成完了通知が来ました。

管理簿にも作成日が追記され、ステータスが完了と更新されています。

Datadogを確認すると、アカウントが追加されていました。権限も追加されています。

アカウント削除も全体フロー①にて、「削除」を選択すると、アカウント作成と同じように動作して、削除できます。ここでは割愛します。


苦労・工夫点

コーディングでひと苦労
もともと Python や Java には慣れていましたが、GAS(Google Apps Script)はあまり触れる機会がありませんでした。
そのため、コーディングの過程でいくつか躓く点はありましたが、Copilot を活用して補いました。最近の AI は本当に優秀で、不慣れな言語でも効率的にコードを書けるようになっていると実感しました。

サードパーティサービスの選択について
外部サービス(今回の場合は Datadog)へアカウント作成・削除のリクエストを送信するためには、サーバ環境が必要となります。 しかし、自分でサーバを構築するには相応の時間と手間がかかるため、AWS か GAS(Google Apps Script) のいずれかを検討しました。 今回は、以下の観点から GAS(Google) を選択しました。

  1. アカウント管理簿をGoogleスプレッドシートで作成したい
    → 協力会社の方も参照できるようにするため、クラウド上で共有可能なGoogleスプレッドシートが有効。

  2. アカウント管理業務の効率化が目的であり、管理簿の更新も自動化したい
    → GASを利用することで、ワークフローとスプレッドシートの連携が容易になる。


まとめ

ここまでお読みいただき、ありがとうございます。
本記事では、アカウント管理業務の効率化を目的として、Slackワークフローの新機能「ブランチ」とアカウント管理簿として利用しているGoogleスプレッドシートを活用し、アカウント作成・削除を自動化する仕組みをご紹介しました。

今回は Datadog を例に取り上げましたが、外部APIが用意されているツールであれば、GASのコードを編集するだけで同様の仕組みを実現できます。(例:JIRA、Confluence、Backlog など)

私自身が今回作成したSlackワークフローを使ってみた感想としては、
ボタンを押すだけでアカウント作成・削除が完了するため、手作業によるミスを防止でき、手順書や管理簿の追記(作成日など)も不要になるので、業務が大幅に楽になると感じました。
全体の作業時間も、約1時間 → 約5分程度に短縮可能です。

ただし、権限の種類によってはセキュリティ上、手動で作成すべきケースもあります。
そのため、どの権限を自動化するかは慎重に見極める必要があります。(例:Admin権限など)


付録

「コード.gs」

const DATADOG_API_KEY = PropertiesService.getScriptProperties().getProperty('DATADOG_API_KEY'); //DatadogのAPIキー
const DATADOG_APP_KEY = PropertiesService.getScriptProperties().getProperty('DATADOG_APP_KEY'); //DatadogのApplicationキー
const DATADOG_URL = 'https://api.datadoghq.com/api/v2/users'; //DatadogのリクエストURL
const SHEET_NAME = 'Datadog'; //アカウント管理簿のシート名
const SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/----'; //Incoming WebhookのURL


// スプレッドシート更新をトリガーに起動
function atEdit() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  const data = sheet.getDataRange().getValues();
  // Logger.log(data);

  for (let i = 1;i < data.length; i++){

    // Datadogアカウント作成
    if (data[i][5] === '責任者作成承認済') {
      var name = data[i][0];
      var email = data[i][1];
      var role = data[i][2];
      
      // Logger.log(data[i][2]);

      try {
        // Datadogアカウント作成関数実行
        var result = createDatadogUser(name, email, role); 

        // スプレッドシート更新
        sheet.getRange(i + 1, 4).setValue(new Date()); // 作成日更新
        sheet.getRange(i + 1, 6).setValue('完了'); // ステータス更新
        sheet.getRange(i + 1, 7).setValue(''); // エラー内容取消

        // Slack通知(成功)
        sendSlackNotification(name, email, '作成');

      } catch (error) {
        // スプレッドシート更新
        sheet.getRange(i + 1, 6).setValue('error'); // ステータス更新
        sheet.getRange(i + 1, 7).setValue(error.message); // エラーメッセージ

        // Slack通知(失敗)
        sendSlackErrorNotification(name, email, '作成', error.message);
      }
    }

    // Datadogアカウント削除
    if (data[i][5] === '責任者削除承認済') {
      var name = data[i][0];
      var email = data[i][1];

      try {
        // DatadogユーザーID取得関数実行
        var userId = getDatadogUserIdByEmail(email); 
        Logger.log(userId);

      } catch (error) {
        // スプレッドシート更新
        sheet.getRange(i + 1, 6).setValue('error'); // ステータス更新
        sheet.getRange(i + 1, 7).setValue(error.message);// エラーメッセージ

        //Slack通知(失敗)
        sendSlackErrorNotification(name, email, '削除', error.message);
      }

      try {
        //Datadogアカウント削除関数実行
        var result = deleteDatadogUser(userId);

        // スプレッドシート更新
        sheet.getRange(i + 1, 5).setValue(new Date()); // 削除日更新
        sheet.getRange(i + 1, 6).setValue('完了'); // ステータス更新
        sheet.getRange(i + 1, 7).setValue(''); // エラー内容取消

        // Slack通知(成功)
        sendSlackNotification(name, email, '削除');

      } catch (error) {
        // スプレッドシート更新
        sheet.getRange(i + 1, 6).setValue('error'); // ステータス更新
        sheet.getRange(i + 1, 7).setValue(error.message);// エラーメッセージ

        // Slack通知(失敗)
        sendSlackErrorNotification(name, email, '削除', error.message);
      }
    }

  }
}



// Datadogアカウント作成関数
function createDatadogUser(name, email, role) {
  // var name = 'ドコモ 太郎';
  // var email = 'test_01@gmail.com';
  // var role = 'Datadog Read Only Role';
  var DATADOG_ROLE_IDs = []; //Datadog Role UUIDの配列を準備

  var roles = (role.includes(",")) ? role.split(",") : [role]; //2つ以上権限を付与する場合は、カンマ区切りで配列に格納
  // Logger.log(roles);

    for (let i = 0;i < roles.length; i++){
      Logger.log(roles)[i];
      if (roles[i]=='Datadog Read Only Role'){
        DATADOG_ROLE_IDs[i] = '---' // Datadog Role UUID(Datadog Read Only Role)事前に取得してくる
      }
      else if (roles[i]=='CaseManagement Edit Role'){
        DATADOG_ROLE_IDs[i] = '---' // Datadog Role UUID(CaseManagement Edit Role)事前に取得してくる
      }
      else if (roles[i]=='Datadog Standard Role'){
        DATADOG_ROLE_IDs[i] = '---' // Datadog Role UUID(Datadog Standard Role)事前に取得してくる
      }
    }
    
    // APIを実行できる形に成形
    var rolesArray = DATADOG_ROLE_IDs.map(function(id) {
        return { "id": id, "type": "roles" };
      })
    // Logger.log(rolesArray);
    
    // リクエスト内容
    var options = {
      method: 'post',
      muteHttpExceptions: true,
      contentType: 'application/json',
      headers: {
        'DD-API-KEY': DATADOG_API_KEY,
        'DD-APPLICATION-KEY': DATADOG_APP_KEY
      },
      payload: JSON.stringify({
        data: {
          type: 'users',
          attributes: {
            email: email,
            name: name
            },
          relationships: {
            roles: {
              data: 
                  rolesArray
            }
          }
        }
      })
    }

    try {
        // API実行
        Logger.log(options);
        var response = UrlFetchApp.fetch(DATADOG_URL, options);

        // 結果を取得
        var result = JSON.parse(response.getContentText());
        // Logger.log(result);
        
        // 失敗した場合
        if (result.errors && result.errors.length > 0) {
          throw new Error("入力値が不適切かすでにユーザーが存在します。確認してください。");
        }

        return result;

      } catch (e) {
        throw e;
      }
  }



// Datadogアカウント削除関数
function deleteDatadogUser(userId) {
  // Logger.log(userId);

  // リクエスト内容
  var optionsDel = {
    method: 'delete',
    headers: {
      'DD-API-KEY': DATADOG_API_KEY,
      'DD-APPLICATION-KEY': DATADOG_APP_KEY
    }
  };
  // API実行
  UrlFetchApp.fetch(`https://api.datadoghq.com/api/v2/users/${userId}`, optionsDel);
}


// DatadogユーザーID取得関数(削除用)
function getDatadogUserIdByEmail(email) {
  // var email = '---@gmail.com';
 
  // APIを実行できる形に成形
  var url = 'https://api.datadoghq.com/api/v2/users?filter=' + encodeURIComponent(email);
  // Logger.log(url);

  // リクエスト内容
  var options = {
    method: 'get',
    muteHttpExceptions: true,
    headers: {
      'DD-API-KEY': DATADOG_API_KEY,
      'DD-APPLICATION-KEY': DATADOG_APP_KEY
    }
  };

  try{
    // API実行
    var response = UrlFetchApp.fetch(url, options);

    //結果を取得
    var result = JSON.parse(response.getContentText());
    if (result.data && result.data.length > 0) {
      // Logger.log(result.data);
      return result.data[0].id;
    } else {
      // 失敗した場合
      throw new Error('ユーザーが見つかりません: ' + email);
    }
    
  } catch(e){
    throw e;
  }
  
}


// Slack通知関数(成功)
function sendSlackNotification(name, email, action) {
  // メッセージ内容
  const message = `Datadogアカウント${action}完了: ${name} (${email})`;

  // リクエスト内容
  const payload = {
    text: message
  };
  // 
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload)
  };
 
  // Slackに成功通知を送信
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}


// Slack通知関数(失敗)
function sendSlackErrorNotification(name, email, action, error) {
  // メッセージ内容
  const message = `Datadogアカウント${action}失敗: ${name} (${email})`+ error;
  
  // リクエスト内容
  var payload = {
    text: message
  };

  var options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload)
  };

  // Slackに失敗通知を送信
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}