はじめに
チーム内でシステムのアラート当番を回しており、何かしらのエラーを検知したら当番が対応するという運用をしています。
エラーの通知のメンション先はSlackの当番用のユーザーグループなので、当番が変わるタイミングで手動で更新していたのですが、地味に面倒でした。
そこでGASを使ってスプレッドシートで作成した当番表に従ってユーザーグループ定期的に自動更新するようにしました。
概要
Google Spreadsheet
以下のような当番表のシートを作成する。
GAS
- 毎日決まった時間に動くようにトリガーを設定して定期的に実行する。
- 現在日とスプレッドシートの当番開始日が一致するレコードがあったら
その担当者のユーザIDを取得し、ユーザーグループを更新する。 - 更新したらSlack通知でお知らせする。
使用するSlack API
Slack APIのusergroups.users.updateでユーザーグループ所属ユーザーの登録ができます。
このAPIにはユーザーグループのIDとユーザーのIDが必要であるため、
それぞれusergroups.listとusers.lookupByEmailでIDを検索します。
これらのAPIは、SlackのアプリのTokenを使って実行します。
Slackの準備
Slack apiのYour AppsのページからSlackアプリを作成します。
各apiのドキュメントに必要な権限が記載されているので、それぞれ必要な権限を付与します。
今回の場合は以下になります。
- usergroups:write
- usergroups:read
- users:read.email
作成したらWorkspaceにインストールします。
後で使うため、OAuth Access Token をCopyします。
スクリプト
const TOKEN = 'TOKEN';
const SLACK_API_URL = "https://slack.com/api/";
const API_USERGROUPS_LIST = SLACK_API_URL + "usergroups.list";
const API_USERS_LOOKUPBYEMAIL = SLACK_API_URL + "users.lookupByEmail";
const API_USERGROUPS_USERS_UPDATE = SLACK_API_URL + "usergroups.users.update";
const USER_GROUP_NAME = "slackのユーザーグループ名";
const SPREADSHEET = SpreadsheetApp.getActiveSpreadsheet();
const SHEET_USERS = SPREADSHEET.getSheetByName('当番表'); // ここでは「当番表」というシート名から取得する
/** ここはコードでベタ書きしたが、別シートにまとめてそこから取得しても良いかも */
const ADDRESS_LIST = {
"担当者名1":"member1@hogehoge.com",
"担当者名2":"member2@hogehoge.com",
"担当者名3":"member3@hogehoge.com",
"担当者名4":"member4@hogehoge.com"
}
/** UserGroupIDを求める */
function getUserGroupID() {
Logger.log("START getUserGroupID");
let usergroup_id = '';
// GroupIDがわかっている場合はこのまま終了
if (usergroup_id != ""){
return usergroup_id
}
// 全UserGroupを取得して、そこから該当のものを探す
let params = {'method': 'post', 'payload': {'token': TOKEN}};
let resjson = callApi(API_USERGROUPS_LIST, params);
if (resjson == {}) {return "";}
let usergroups = resjson.usergroups;
let hit_usergroup = usergroups.find((v) => v.handle == USER_GROUP_NAME);
if (hit_usergroup === undefined){
throw new Error(":" + USER_GROUP_NAME + "は見つかりませんでした。登録済みか確認してください");
}
Logger.log(hit_usergroup);
// 取得結果
Logger.log('UserGroupID:' + hit_usergroup['id']);
return hit_usergroup['id'];
}
/** カンマ区切りのUserIDリストを返す */
function getUserIDs(targerRow){
Logger.log("START getUserIDs");
let error_flg = false;
let user_id_list = [];
let name_1 = SHEET_USERS.getRange("C"+targerRow).getValues();
let name_2 = SHEET_USERS.getRange("D"+targerRow).getValues();
let email_1 = ADDRESS_LIST[name_1];
let email_2 = ADDRESS_LIST[name_2];
let emailList = [email_1, email_2];
let user_id = '';
let requests = [];
let responses =[];
let response = '';
const options = {
"headers": { 'Authorization': 'Bearer ' + TOKEN }
};
for(let i = 0; i < 2; i++){
requests=API_USERS_LOOKUPBYEMAIL + "?&email=" + emailList[i];
responses = UrlFetchApp.fetch(requests,options);
response = JSON.parse(responses.getContentText("UTF-8"));
user_id = response['user']['id'];
user_id_list.push(user_id);
}
Logger.log('user_id_list:' + user_id_list);
return user_id_list.join(',');
}
/** ユーザーグループの更新 */
function updateUserGroup(usergroup_id, user_id_list) {
Logger.log("START updateUserGroup");
let payload = {
'token': TOKEN,
'usergroup': usergroup_id,
'users': user_id_list,
}
let params = {'method': 'post', 'contentType': 'application/x-www-form-urlencoded', 'payload': payload};
let response = callApi(API_USERGROUPS_USERS_UPDATE, params);
Logger.log(response);
}
/** SlackAPIの呼び出し */
function callApi(url, params){
let response = UrlFetchApp.fetch(url, params);
let resjson = JSON.parse(response);
if (resjson.ok != true) {
Logger.log('エラー:' + resjson['error']);
throw new Error(resjson['error']);
}
return resjson;
}
function findRow(sheet,val,col){
const dat = sheet.getDataRange().getValues(); //受け取ったシートのデータを二次元配列に取得
for(let i=1;i<dat.length;i++){
Logger.log('value:'+dat[i][col-1]);
if(dat[i][col-1] === val){
return i+1;
}
}
return 0;
}
/** メイン処理 */
function main(){
Logger.log("START");
const today = new Date();
todayFormatted = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy-MM-dd');
Logger.log('today:' + todayFormatted);
const targerRow = findRow(SPREADSHEET,todayFormatted,1);
if (targerRow == 0) {
Logger.log('not exists target');
return;
}
Logger.log('targerRow:'+targerRow);
// UserGroupIDを求める
const usergroup_id = getUserGroupID();
// 各行のUserIDを求める
const user_id_list = getUserIDs(targerRow);
// Update実施
updateUserGroup(usergroup_id, user_id_list);
// slack通知
sendNotice();
Logger.log("END");
}
function sendNotice() {
const webhookURL = 'https://hooks.slack.com/services/~';
const data = {
'username' : '当番チームのメンバーを変更してくれるくん',
'channel' : '#チャンネル名',
'icon_emoji': ':smile:',
'attachments': [{
'color': '#008000',
'text' : '当番交代しました!',
}],
};
const payload = JSON.stringify(data);
const options = {
'method' : 'POST',
'contentType' : 'application/json',
'payload' : payload
};
UrlFetchApp.fetch(webhookURL, options);
}
トリガー設定
毎日決まった時間に動くようにGASの画面からトリガーを設定する。