LoginSignup
15
0

More than 1 year has passed since last update.

【GAS】GoogleカレンダーからPagerDutyのMaintenance Windowを作成できるようにする

Last updated at Posted at 2022-12-13

作ったもの

Googleカレンダーに予定を作成/変更/削除すると、PagerDutyのメンテナンスウィンドウが作成/変更/削除されます👍

1.png

2.png

既存のAppではScheduleをGoogleカレンダーに出力することしかできないので作成しました。たぶん先行事例はありません。

ref: Schedules in Third-Party Apps

コード(全文)

↓をクリックすると開きます

// スクリプトプロパティ(Key-Valueストア)を使う
const db = PropertiesService.getScriptProperties();
// 定数
const PD_TOKEN = 'Token token=' + db.getProperty('PD_TOKEN');
const PD_HEADERS = {
  "Accept": "application/vnd.pagerduty+json;version=2",
  "Authorization": PD_TOKEN
}
const PD_SERVICE_IDS = [
  { "id": "PVG3BVI", "type": "service_reference" }, // service-A
  { "id": "PHN2FVI", "type": "service_reference" } // service-B
]
const PD_API = 'https://api.pagerduty.com'

// メイン関数
function calendarUpdated(e) {
  let options = {
    'showDeleted': true,
    'maxResults': 100
  };

  // syncTokenを読み込む
  const syncToken = db.getProperty('SYNC_TOKEN');
  if (syncToken) {
    options.syncToken = syncToken;
  } else {
    // syncTokenがない場合(初回)、過去7日間の予定を対象にする
    options.timeMin = getDate7daysAgo();
  }

  // 予定の取得
  const events = Calendar.Events.list(e.calendarId, options);

  for (const event of events.items) {
    // eventIdに紐づくmaintenanceWindowIdを読み込む
    let maintenanceWindowId = db.getProperty(event.id);

    // maintenanceWindowIdがnullならCreate
    // event.statusがcancelledならDelete
    // それ以外はUpdate
    if (maintenanceWindowId == null) {
      maintenanceWindowId = CreateMaintenanceWindow(event.start.dateTime, event.end.dateTime, event.summary);
      db.setProperty(event.id, maintenanceWindowId);
    } else if (event.status == 'cancelled') {
      DeleteMaintenanceWindow(maintenanceWindowId);
      db.deleteProperty(event.id);
    } else {
      UpdateMaintenanceWindow(maintenanceWindowId, event.start.dateTime, event.end.dateTime, event.summary);
    }
  }

  // syncTokenを保存する
  db.setProperty('SYNC_TOKEN', events.nextSyncToken);
}

// Create
function CreateMaintenanceWindow(startTime, endTime, description) {
  const data = {
    "maintenance_window": {
      "type": "maintenance_window",
      "start_time": startTime,
      "end_time": endTime,
      "description": description,
      "services": PD_SERVICE_IDS
    }
  };
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'headers': PD_HEADERS,
    'payload': JSON.stringify(data)
  };
  const url = PD_API + '/maintenance_windows';

  const response = UrlFetchApp.fetch(url, options);
  const responseJSON = JSON.parse(response.getContentText());
  return responseJSON.maintenance_window.id;
}

// Delete
function DeleteMaintenanceWindow(id) {
  const options = {
    'method': 'delete',
    'headers': PD_HEADERS,
  };
  const url = PD_API + '/maintenance_windows/' + id;

  UrlFetchApp.fetch(url, options);
}

// Update
function UpdateMaintenanceWindow(id, startTime, endTime, description) {
  const data = {
    "maintenance_window": {
      "type": "maintenance_window",
      "start_time": startTime,
      "end_time": endTime,
      "description": description,
      "services": PD_SERVICE_IDS
    }
  };
  const options = {
    'method': 'put',
    'contentType': 'application/json',
    'headers': PD_HEADERS,
    'payload': JSON.stringify(data)
  };
  const url = PD_API + '/maintenance_windows/' + id;

  UrlFetchApp.fetch(url, options);
}

// 7日前の日付を返す関数
function getDate7daysAgo() {
  const date = new Date();
  date.setDate(date.getDate() - 7);
  return date.toISOString();
}

コード以外で必要な作業

先にGoogle Cloud Platformのコンソールで次の作業をします。

  1. 適当な名前でGCPプロジェクトを作成する
  2. APIとサービスAPIとサービスの有効化で「Google Calendar API」を有効にする
  3. APIとサービスOAuth 同意画面で同意画面を新規作成する。スコープは「すべてのカレンダーの予定を表示」

次にGoogle Apps Script(以下、GAS)で次の作業をします。

  1. プロジェクトの設定で先ほどのGCPプロジェクトを紐付ける
  2. エディターサービスを追加で「Google Calendar API」を有効にする
  3. トリガートリガーの追加でイベントソースに「カレンダーから」を選ぶ。連携するGoogleカレンダーのメールアドレスを入れ、遷移先でOAuth認証を行う

解説

1. なぜGASなのか?

まずはノーコードで実現できないか調べました。IFTTTには「予定の追加」というトリガーがありますが、変更と削除はなし。
IFTTT.png

Zapierには変更と削除もありました。
Zapier.png

ただしZapierは無料プランだと毎月100タスクしか実行できません。開発で試行錯誤していたら100回はあっという間に到達しそうなのでやめました。が、この記事を書くために再度調べたら「最初の2週間は1000タスクを実行できる」だと!? Oh...

ref: Get started with your free Zapier trial

コードを書くなら、簡単にGoogleカレンダーと連携できるGASはとても便利です。学習コストや運用コストも低いです。

2. GASでGoogleカレンダーをトリガーにするときの概要

Advanced Calendar ServiceはGASでGoogle Calendar APIを使うためのサービスです。前述の通りGASのエディターサービスを追加から有効にできます。

予定をハンドリングする方法は下記の記事がとても参考になりました。

追加/変更/削除された予定を取得するにはsyncTokenをリクエストに含めてAPIを叩きます。

このsyncTokenはAPIを叩くとnextSyncTokenという名前でレスポンスに入ってくるものです。

初回のみsyncTokenを使わずにAPIを叩いてnextSyncTokenをスクリプトプロパティに保存し、2回目以降はそれを使ってAPIを叩き、また返ってきたnextSyncTokenを保存する、という流れにしています。

3. スクリプトプロパティ

スクリプトプロパティはGASで使えるKey-Valueストアの1つです。簡単かつ便利なので、ついつい雑に使ってしまいます。

コードの抜粋
const db = PropertiesService.getScriptProperties();
// READ
const syncToken = db.getProperty('SYNC_TOKEN');
// WRITE
db.setProperty('SYNC_TOKEN', events.nextSyncToken);

4. 予定とメンテナンスウィンドウの紐付けを保存する

APIについては後述しますが、変更と削除はメンテナンスウィンドウのIDを指定してAPIを叩く必要があります。

そのため、メンテナンスウィンドウの作成後にGoogleカレンダーの予定のIDevent.id)とメンテナンスウィンドウのIDmaintenanceWindowId)の紐付けを保存しておく必要があります。

そこでevent.idをキー、maintenanceWindowIdを値にしてスクリプトプロパティに保存しました。

メインのロジックは次のとおりです。

コードの抜粋(一部改変済み)
// 予定に紐づくメンテナンスウィンドウのIDを取得する
// 新しい予定の場合、まだ存在しないのでnullが返る
let maintenanceWindowId = db.getProperty(event.id);

if (maintenanceWindowId == null) {
  maintenanceWindowId = CreateMaintenanceWindow(引数省略); // 新規作成
  db.setProperty(event.id, maintenanceWindowId); // 作成されたメンテナンスウィンドウのIDを保存する
} else if (event.status == 'cancelled') {
  DeleteMaintenanceWindow(maintenanceWindowId); // 削除
  db.deleteProperty(event.id); // 保存されていたIDも削除する
} else {
  UpdateMaintenanceWindow(maintenanceWindowId, 以下引数省略); // 変更
}

5. PagerDuty APIを叩く

API Referenceが使いやすくて神です。

メンテナンスウィンドウのCreate(POST)、Update(PUT)、Delete(DELETE)をそれぞれの関数で叩いています。

GASで外部APIを叩く際はUrlFetchApp.fetch(url, options)を使うと便利です。

例えばUpdateの関数は次のようになります。

コードの抜粋
function UpdateMaintenanceWindow(id, startTime, endTime, description) {
  const data = {
    "maintenance_window": {
      "type": "maintenance_window",
      "start_time": startTime,
      "end_time": endTime,
      "description": description,
      "services": PD_SERVICE_IDS
    }
  };
  const options = {
    'method': 'put',
    'contentType': 'application/json',
    'headers': PD_HEADERS,
    'payload': JSON.stringify(data)
  };
  const url = PD_API + '/maintenance_windows/' + id;

  UrlFetchApp.fetch(url, options);
}

課題と今後の展望

  • エラーハンドリング
  • スクリプトプロパティに古いデータが溜まっていく問題
  • PD_SERVICE_IDSに対象にするサービスを書いたが、スプレッドシートから読み込んでも良さそう
  • 複数のチームで使えるようにしたい(複数のカレンダーへの対応・予定のタイトルや本文に応じた処理の分岐)
  • ScheduleのOverrideも作れるようにしたい!!(オンコール担当者の上書き)

関連記事: PagerDuty CLIでscheduleをoverrideする方法

15
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
0