vHoge

VMwareのアレコレ備忘録。CLIでがんばるネタ多め。

vROps のレポートをチャット(Slack)に POST するのは結構大変

こちらの投稿は vExperts Advent Calendar 2022 の 19 日目になります。
今年はセンセーショナルなネタ仕入れられなかったので日常 TIPS 系…
と思ったら結構ハマった話(`・ω・´)
adventar.org

TL;DR

持ってけドロボー github.com

vROps のレポート出力先

vROps って使えるようになれば頗る便利なのですが、如何せん見られる情報が多く、使い慣れるまではなかなかハードルが高い…

そのような場合、レポート機能で定期スケジュールでどこかにファイル出力しておけば vROps の GUI にログイン不要でサクッと見られ、断面を蓄積し見比べたりできるのでとりあえず使い始めたいという場合においてはオススメしています。

さて、そんなレポートの出力先ですが

  • メール添付
  • ファイルサーバ(CIFS)に保存

と、2種類。

まぁでも、最近はよくあるんですよね。
「社内チャット(Slack, Teams etcetc) に投稿できない?」
実際、vROps のアラートの方は Slack とか Webhook のプラグインがあるぐらいだし…

残念ながら vROps だけでは POST できないが、
vROps API を駆使して、外部からレポートを取得し、それを Slack に POST させてみる。

vROps API

一応 API リファレンスは ↓ にありますが… developer.vmware.com

vROps は内部に Swagger が立っていて、API 仕様が載っていたり、Web GUI ベースで API を叩いたりができるので、こちらで試しながら開発してみるのが楽。
URL は https://【vROps IP or FQDN】/suite-api/

サンプル

苦労話をする前に先にサンプルを書いておく。
vROps 環境は 8.6.4 で、スクリプトUbuntu 20.04 LTS (on WSL2) + jqで確認。

#!/bin/sh
# 各種パラメータ
VROPS_FQDN=【vROps IP or FQDN】
VROPS_USER=【vROps のユーザ】
VROPS_PASSWORD=【vROps のパスワード】
VROPS_REPORT_NAME=【対象のレポート名を URL エンコード】

SLACK_CHANNEL=【Slack のチャンネルID】
SLACK_TOKEN=【Slack API の TOKEN】
SLACK_TITLE="【Slack に投稿するタイトル】"
SLACK_COMMENT="【Slack に投稿するメッセージ】"

TMPPATH="/tmp/vrops_report.pdf"

# # vROps の TOKEN を取得し、対象のレポートリストを取得
TOKEN=`curl -X POST "https://$VROPS_FQDN/suite-api/api/auth/token/acquire" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"username\" : \"$VROPS_USER\", \"password\" : \"$VROPS_PASSWORD\"}" -k | jq -r .token`
REPORTS=`curl -X GET "https://$VROPS_FQDN/suite-api/api/reports?name=$VROPS_REPORT_NAME&status=COMPLETED&_no_links=true" -H "accept: application/json" -H "Authorization: vRealizeOpsToken $TOKEN" -k`
LENGTH=`echo $REPORTS | jq -r "[.reports[]] | length"`

# vROps の対象のレポートから最新のファイルをダウンロード
if [ "$LENGTH" != "" ] ;
then
  i=0
  SDATE="19700101000000"
  while [ $i -lt $LENGTH ];
  do
    TMP=`echo $REPORTS | jq ".reports[$i].completionTime"`
    TDATE=`python -c "import datetime;print(datetime.datetime.strptime($TMP, \"%a %b %d %H:%M:%S JST %Y\").strftime(\"%Y%m%d%H%M%S\"))"`
    if [ $SDATE -lt $TDATE ] ;
    then
      SDATE=$TDATE
      ID=`echo $REPORTS | jq -r ".reports[$i].id"`
    fi
    i=`expr $i + 1`
  done
else
  exit 1
fi
curl -X GET "https://$VROPS_FQDN/suite-api/api/reports/$ID/download?_no_links=true" -H "accept: application/pdf" -H "Authorization: vRealizeOpsToken $TOKEN" -o $TMPPATH -k

# Slack へのポスト
curl -X POST "https://slack.com/api/files.upload" -F channels=$SLACK_CHANNEL -F token=$SLACK_TOKEN -F file=@$TMPPATH -F filename=`date +"%Y%m%d"`.pdf -F title=$SLACK_TITLE -F initial_comment=$SLACK_COMMENT

# 不要ファイルは削除
rm $TMPPATH

以下愚痴苦労話

1.レポートリスト取得の検索条件

vROps のレポートリスト、人に優しく使えそうな項目としてはレポート名ぐらいかな(reportDefinitionId 条件に無いし…)ということで、仕様的に vROps のレポート名を URL エンコードして投げるわけですが…

VROPS_REPORT_NAME=【対象のレポート名を URL エンコード】
【中略】
REPORTS=`curl -X GET "https://$VROPS_FQDN/suite-api/api/reports?name=$VROPS_REPORT_NAME&status=COMPLETED&_no_links=true" -H "accept: application/json" -H "Authorization: vRealizeOpsToken $TOKEN" -k`

どうも vROps にプリセットで入っているレポート、日本語名とは別に実体は英語名らしく(?)、日本語名を URL エンコードして検索投げてもひっかからない…
英語名はブラウザ設定の言語で英語を最優先すれば取れますが、なんなのコレ…

例. "[VOA] サマリ レポート" → "%5BVOA%5D+Summary+Report"

ちなみに Swagger 上だと日本語をエンコードした値で正しく動くのでタチが悪い…
あと、クローンしたり新規で作成したものは日本語(しかないから?)ひっかかるっぽい。
(書いていて気付いたが、HTTP ヘッダの言語設定を見ているのかも)

2.最新のレポートが分からない

レポートの API を叩くと対象のレポート一覧が json で帰ってくる。

{
  "pageInfo": {
    "totalCount": 10,
    "page": 0,
    "pageSize": 1000
  },
  "reports": [
    {
      "id": "2344e840-db9f-4182-a747-851ae8dfe4b8",
      "description": "[VOA] サマリ レポート",
      "resourceId": "a7644013-3204-449e-b0e1-b9316a2c292d",
      "reportDefinitionId": "7e795e6f-e3e9-43e3-b772-6bceb6cbd533",
      "subject": [],
      "owner": "report",
      "completionTime": "Mon Dec 12 00:02:10 JST 2022",
      "status": "COMPLETED"
    },
    {
      "id": "645c400b-c2bc-40e6-ba75-e36309c920db",
      "description": "[VOA] サマリ レポート",
      "resourceId": "a7644013-3204-449e-b0e1-b9316a2c292d",
      "reportDefinitionId": "7e795e6f-e3e9-43e3-b772-6bceb6cbd533",
      "subject": [],
      "owner": "report",
      "completionTime": "Wed Dec 14 00:01:13 JST 2022",
      "status": "COMPLETED"
    },
  【以下略】

この一覧(.reports の配列)が順序保証がなく、世代を判定する項目を持っていないので、
レポートの中でどれが最新かを判断するのは全てのレポートから項目を取り出し比較する必要があり、かなり面倒…
サンプルだと while ブン回している辺りがその辺。

まぁ、これは百歩譲れるが、問題は比較に使える項目よ

3.謎の日付書式

項目中に完了日時があるので、それを比較すればどれが最新化は分かりそうですが…

     "completionTime": "Mon Dec 12 00:02:10 JST 2022",

何なのこの書式!!!

今回最大の謎、別に RFC とかでの規定フォーマットでも無いし…
date コマンドでの parse はできないし、文字列(月略称)混ざっているおかげで順序並べ替えだけでも対応できないし…

というわけで致し方なく、インライン python で整形。

TDATE=`python -c "import datetime;print(datetime.datetime.strptime($TMP, \"%a %b %d %H:%M:%S JST %Y\").strftime(\"%Y%m%d%H%M%S\"))"`

それを大小比較してなんとか最新を探すことに。

    if [ $SDATE -lt $TDATE ] ;
    then
      SDATE=$TDATE
      ID=`echo $REPORTS | jq -r ".reports[$i].id"`
    fi

4.ファイルのダウンロード

レポートのダウンロードは ID 指定なので、最新のレポートさえ分かってしまえば大したことなく…

curl -X GET "https://$VROPS_FQDN/suite-api/api/reports/$ID/download?_no_links=true" -H "accept: application/pdf" -H "Authorization: vRealizeOpsToken $TOKEN" -o $TMPPATH -k

5.Slack 投稿以降

ファイル添付は Webhook からは出来ず、https://api.slack.com/apps からアプリを作成、Channel に招待し Token を発行しておき POST。この辺りはチャットツールによりけりですが、REST API からの POST が一般的じゃないですかね…

なんとか動いた…

むかーし(6.x 時代)も似たようなスクリプト書いたことあったけど、ここまで大変じゃなかったような…というより、日付書式がまともだった気がするけど…

あのー、Windows 環境なんですけど…

そんなあなたに PowerShell 版もございます。
PowerShell 7.3.0 で確認。
(※ Invoke-WebRequest の -Form を使っているので PowerShell 6.1 以降は必須のはず)

# 各種パラメータ
$VROPS_FQDN="【vROps IP or FQDN】"
$VROPS_USER="【vROps のユーザ】"
$VROPS_PASSWORD="【vROps のパスワード】"
$VROPS_REPORT_NAME="【対象のレポート名を URL エンコード】"

$SLACK_CHANNEL="【Slack のチャンネルID】"
$SLACK_TOKEN="【Slack API の TOKEN】"
$SLACK_TITLE="【Slack に投稿するタイトル】"
$SLACK_COMMENT="【Slack に投稿するメッセージ】"

$TMPPATH="$env:TEMP/vrops_report.pdf"
$DATE=Get-Date
$DATE=$DATE.ToString("yyyyMMdd")

# vROps の TOKEN を取得し、対象のレポートリストを取得
$AUTH_JSON=Invoke-WebRequest -Headers @{"Content-type"="application/json";"accept"="application/json"} -Method POST -Body "{`"username`":`"$VROPS_USER`",`"password`":`"$VROPS_PASSWORD`"}" -Uri "https://$VROPS_FQDN/suite-api/api/auth/token/acquire" -SkipCertificateCheck | ConvertFrom-Json
$TOKEN=$AUTH_JSON.token
$REPORTS_JSON=Invoke-WebRequest -Headers @{"Content-type"="application/json";"accept"="application/json";"Authorization"="vRealizeOpsToken $TOKEN"} -Method GET -Uri "https://$VROPS_FQDN/suite-api/api/reports?name=$VROPS_REPORT_NAME&status=COMPLETED&_no_links=true" -SkipCertificateCheck | ConvertFrom-Json

# vROps の対象のレポートから最新のファイルをダウンロード
$SDATE=Get-Date -Year 1970 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0
$ID=""
for($i = 0; $i -lt $REPORTS_JSON.reports.Length; $i++)
{
    $TDATE=[DateTime]::ParseExact($REPORTS_JSON.reports.completionTime[$i], "ddd MMM dd HH:mm:ss JST yyyy", [System.Globalization.CultureInfo]::CreateSpecificCulture("en-US")).ToString()
    if($SDATE -lt $TDATE)
    {
        $SDATE=$TDATE
        $ID=$REPORTS_JSON.reports.id[$i]
    }
}
Invoke-WebRequest -Headers @{"Content-type"="application/json";"accept"="application/json";"Authorization"="vRealizeOpsToken $TOKEN"} -Method GET -Uri "https://$VROPS_FQDN/suite-api/api/reports/$ID/download?_no_links=true" -OutFile $TMPPATH -SkipCertificateCheck

# Slack へのポスト
Invoke-WebRequest -Form @{"token"=$SLACK_TOKEN;"channels"=$SLACK_CHANNEL;"title"=$SLACK_TITLE;"initial_comment"=$SLACK_COMMENT;"filename"="$DATE.pdf";"file"=Get-Item -Path $TMPPATH}  -Method POST -Uri "https://slack.com/api/files.upload"

# 不要ファイルは削除
Remove-Item $TMPPATH

PowerShell .NET オブジェクトを扱えるので、あの日付書式も扱えて楽だわね…