はじめに
案件開発をする際にほぼ必ずSORACOM Air SIMを使うのですが、社内で管理しているSIMの数が多すぎてどのSIMがどの認証情報を使っててどのサービス設定されてて...っていうのを把握しきれなくなり、パッと見てSIMの情報を把握できるアプリを作ろうと思いました。そこで今回AWS lambda, kintoneを使ってSIMの自動管理アプリを作りました。
完成品
今回作ったアプリはこんな感じです。表示内容を「すべて」にしているのでテーブルがはみ出してますが、
表示内容をカスタマイズして必要項目に絞ることもできます
構成
今回のアーキテクチャは以下の図のようになってます。
毎日9:00にCloudWatch Eventからlambdaをinvokeし、SORACOM APIを使ってkintoneにSIMの情報を更新させています。
kintoneアプリのフォーム設定
フォームの一覧としては以下の形で作成しました。
今回一括で知りたかった情報をフォームに入れたため、IPアドレスなどの詳細に関しては扱っていません。
フォーム項目のそれぞれのキー名等は以下のようにしました。
※ レコード番号
等の自動設定のキー名の変更方法ご存知の方いらっしゃいましたら教えてください...
"レコード番号": {
"type": "RECORD_NUMBER",
"value": "32"
},
"air_check": {
"type": "CHECK_BOX",
"value": ["VPG"]
},
"harvest_radio": {
"type": "RADIO_BUTTON",
"value": "off"
},
"group_name": {
"type": "SINGLE_LINE_TEXT",
"value": "グループ名"
},
"更新者": {
"type": "MODIFIER",
"value": {
"code": "Administrator",
"name": "Administrator"
}
},
"作成者": {
"type": "CREATOR",
"value": {
"code": "Administrator",
"name": "Administrator"
}
},
"imsi": {
"type": "SINGLE_LINE_TEXT",
"value": "imsi番号"
},
"$revision": {
"type": "__REVISION__",
"value": "1"
},
"make_date": {
"type": "CREATED_TIME",
"value": "YYYY-MM-DDTHH:mm:ssZ"
},
"beam_radio": {
"type": "RADIO_BUTTON",
"value": "on"
},
"krypton_radio": {
"type": "RADIO_BUTTON",
"value": "off"
},
"update_date": {
"type": "UPDATED_TIME",
"value": "YYYY-MM-DDTHH:mm:ssZ"
},
"shared_key": {
"type": "SINGLE_LINE_TEXT",
"value": "事前共有鍵名"
},
"sim_name": {
"type": "SINGLE_LINE_TEXT",
"value": "SIM名"
},
"auth_name": {
"type": "SINGLE_LINE_TEXT",
"value": "認証情報名"
},
"endorse_radio": {
"type": "RADIO_BUTTON",
"value": "off"
},
"funnel_radio": {
"type": "RADIO_BUTTON",
"value": "off"
},
"status": {
"type": "SINGLE_LINE_TEXT",
"value": "active"
},
"$id": {
"type": "__ID__",
"value": "1"
}
}
lambdaの構成
lambda内の主な処理の構成は
- SORACOM APIを使用するのに必要なトークンを取得する
- kintoneに現在保存されている情報一覧を取得する
- SORACOM APIを使用してSIM情報とグループ情報の一覧を取得する
- 3で取得したデータを前項のjson形式に合うように成形する
- 2の情報と4の情報を比較して
- 追加するデータ
- 更新するデータ
- 削除するデータ
- 変更不要のデータ
に分類し、配列に収める
6. 5で作成したデータをそれぞれに対応する処理にかけてアプリを更新する
です。
使用したSORACOM API
今回使用したSORACOM APIは以下の3つです
私が説明するよりリファレンスの方が100億倍わかりやすいのでリンクだけ貼っておきます...
使用したkintone API
今回使用したkintone APIは以下の4つです。
私が説明するよりリファレンスの方が(以下略)
AWS環境の構築
同期が最近「terraform!terraform!」って言っていたのでそんなに推してるなら使ってみよう。と思い
今回lambdaとCloudWatch eventの設定はterraformを使ってみました。
今までserverless frameworkを使っていたので少し抵抗はあったのですが、
基本から教えてもらうとserverless frameworkより直感的でわかりやすかったです。
moduleさえ用意しておけば汎用的に使えるしファイル内に記載するキー名(?)も省略されてないので読めばterraform初心者でも何をしているのか大体理解することができました。
(個人の意見なので異論はもちろん認めます)
躓いたところ
lambdaからkintone APIたたけない?!
kintone APIを使用するにあたって公式サイトを参考に始めたんですけど、
let basicUserName = 'xxxxx'; // basicAuth user name
let basicPassword = 'xxxxx'; // basicAuth password
このbasicUserName
とbasicPassword
ってなに?!ってなりました...
自分のユーザーとパスワード載せてどうすんの...って思って...
ただAPIToken
だけでローカルで実行できたしいいやーって思ってそのままにしてたんですけど、
lambdaに乗せたらTypeError: Cannot read property 'id' of null
って怒られてしまいました...
同期に言われて気づいたんですけど、普段社内ネットワークでつないでて気づかなかったんですが、
社内ネットワーク以外でログインしようとするとベーシック認証通さないといけなくてbasicUserName
とbasicPassword
はそのときに使うユーザー名とパスワードだったみたいです。
なるほど納得
lambdaなど外部のネットワークからAPIを利用する時はこの部分を設定しないとAPIを使えないみたいです。
1日に1回invokeされるように設定したはずなのに1分に1回invokeされてる?!
CloudWatch eventの設定をtfファイル上でこれを参考にして以下のように設定しました。(以下は一部)
resource "aws_cloudwatch_event_rule" "cloudwatch_event_rule_info" {
name = "soracom_sim_update_event"
description = "SORACOM SIM管理アプリ(kintone)の更新lambdaをキックするイベント"
event_pattern = <<PATTERN
{
"detail-type": [
"Scheduled Event"
]
}
PATTERN
schedule_expression = "cron(0 0 * * ? *)"
}
この状態で実行し、コンソール上で確認してもクーロン式で毎日UTC00:00にinvokeされるな
と思っていたら、なんと1分に1回invokeするようになってました😱
同じような内容のルールと比較してみると
{
"detail-type": [
"Scheduled Event"
]
}
期待通り動作しているルールには detail-type
が存在していない...
試しにこれを削除して実装してみると無事に1日1回のinvokeに変わりました
根本原因はまだわからず...もしご存知の方がいらっしゃいましたら教えてください...
kintoneアプリ内にある全レコードを取得できない?!(2019/02/28追記)
SIMの枚数が約150枚あるアカウントでこのシステムを動かすとlambdaの構成にある追加するデータ
の中に既にアプリに存在するSIMの情報が...そしてkintoneのAPI側でもすでに存在する値なので追加できません
とエラーが...
なぜ??と思ってみてみたら、最初にkintoneアプリからレコードを全件取得していたつもりができていませんでした...取得件数を見てみると100件...
確かにアプリ上での表示件数は最大100件だしAPIでは1回に100件しか取得できないのか...と納得
どうやって残りの50件を取得したかというと、下の感じで全部取りきれてなかったら再帰で取得する...として解決しました。getRecordsのレスポンスの情報から再帰的に全件取得するの、私には難しかった...
だってgetRecordsのレスポンスってレコードの情報が連想配列で入ってるのと指定したqueryで取得できるレコードの数しかないんですよ?
/**
* kintoneアプリ上のレコード数を取得
*/
static getTotalRecords() {
return new Promise((resolve, reject) => {
kintoneRecord
.getRecords(appId, "", [], true)
.then(res => {
return resolve(Number(res.totalCount));
})
.catch(err => {
return reject(err);
});
});
}
/**
* kintoneアプリにスキャンをかける
* 1度に100件までしか取得できないので
* 全件が100件超える場合は再帰で取得する
* @param {object[]} scanRecords
* @param {string} query
* @param {number} recordCounts
*/
static getRecords(scanRecords, query, recordCounts) {
return new Promise((resolve, reject) => {
kintoneRecord
.getRecords(appId, query, [], true)
.then(res => {
console.log("[SCAN KINTONE RECORDS] success!!");
scanRecords = scanRecords.concat(res.records);
if (scanRecords.length === recordCounts) {
console.log("[SCAN KINTONE RECORDS] All done!!");
return resolve(scanRecords);
} else if (scanRecords.length < recordCounts) {
const startRecordNum = Number(
scanRecords[scanRecords.length - 1]["レコード番号"]["value"]
);
const query = `レコード番号 < ${startRecordNum}`;
return resolve(
KintoneController.getRecords(scanRecords, query, recordCounts)
);
}
})
.catch(err => {
if (typeof err === Function) {
return reject(err.getAll());
}
return reject(err);
});
});
}
/**
* kintoneアプリ上にあるレコードを全件スキャン
*/
static scanKintoneRecords() {
return new Promise(async (resolve, reject) => {
try {
const recordCounts = await KintoneController.getTotalRecords();
console.log("[SCAN RECORDS COUNT] ", recordCounts);
let scanRecords = [];
scanRecords = await KintoneController.getRecords(
scanRecords,
"",
recordCounts
);
console.log("[SCAN KINTONE RECORDS] ", JSON.stringify(scanRecords));
console.log("[SCANED KINTONE RECORDS COUNT] ", scanRecords.length);
return resolve(scanRecords);
} catch (err) {
return reject(err);
}
});
}
まとめ
SORACOM APIもkintoneのアプリ作成もterraformも初めてだったんですけどどれも使いやすかったのでわりとすんなり(?)作成できました。SIM管理に困ってる方がいましたらぜひ。
コード内容
あまり可読性に自信がないのですが参考にしていただければと思います...
PRお待ちしてます😓
nodeはv8.10を使用しています。
※今回メインはkintoneアプリ開発なのでlambdaのコードのみでterraformのコードは載せません。また別の機会に記事にできれば良いなぁと思います。