Zabbix API で Rust をお試し

公式ドキュメントなどでなんとなく見てたけど、 実際に作ったほうが使い方わかるので、Zabbix API を叩くツールを作りながら勉強

やること

Zabbix API ドキュメント見ると "user.login"メソッドで認証しトークンを取得してからじゃないと
各種操作ができないようなので、とりあえず以下までを目標とします。

  1. ZABBIXサーバーにログインする
  2. ホスト一覧を取得

Zabbix APIJSON RPCでやり取りするため、JSONとHTTPリクエストが扱えるcrateを使います。

JSONserde、HTTPリクエストはreqwestが代表的だったのでそれを使用。

Cargo.tomlはこんな感じ

[dependencies]
reqwest = { version = "0.10", features = ["json"] }
tokio = { version = "0.2", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

実装

ユーザーログイン

とりあえずログインするまでだったらこんな感じですね。

use serde_json::json;

// reqwestが非同期のためasyncにしておく
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ZABBIXサーバーのAPIエンドポイントの場所
    let url = "http://my.zabbix.example.com/api_jsonrpc.php";
    // ZABBIX側で作成したユーザー名
    let user = "api";
    // ZABBIX側で作成したユーザーのパスワード
    let password = "password";

    // JSON-RPCリクエストオブジェクト(idは任意、authはなし)
    let request = json!({
        jsonrpc: "2.0",
        method: "user.login",
        params: {
            "user": user,
            "password": password
        },
        id: 1,
        auth: null
    });

    // reqwest クライアントオブジェクトを生成(postでリクエストを送る必要があるため)
    let client = reqwest::Client::new();

    // ZABBIXサーバーにJSON RPCリクエストをPOSTする。
    // 正常に疎通できればレスポンスを取得できる(reqwestが標準だと非同期なためawaitで待つ)
    let response = client.post(url)
                         .json(&request)
                         .send()
                         .await?;

    // レスポンスボディをJSON形式に直した状態で抽出(serdeでDeserializeできる型ならOK)
    let content: serde_json::Value = response.json().await?;

    // レスポンスの値を表示
    // sample:
    // {
    //     "jsonrpc": "2.0",
    //     "result": "0424bd59b807674191e7d77572075f33",
    //     "id": 1
    // }
    println!("{:#?}", content);

    // --- 本来はレスポンスのidが一致するか、エラーが無かったか確認する ---

    Ok(())
}

実際にはこのあと content の中のトークンをリクエストのauthにセットして送ることで、各種情報の取得やホストなど監視設定の作成を行うことになる。

次にZABBIXに登録されているホストの一覧を出しましょう。

ホスト一覧の取得

ZABBIXのホスト一覧メソッドは"host.get"となっている。

host.get API

リクエストの形式はこんな形。

{
    "jsonrpc": "2.0",
    "method": "host.get",
    "params": {
        "output": [
            "hostid",
            "host"
        ],
        "selectGroups": "extend",
        "selectInterfaces": "extend",
        "search": {
            "host": "server"
        }
    },
    "auth": "038e1d7b1735c6a5436ee9eae095879e",
    "id": 2
}

paramsは抽出条件や出力内容をを指定するパラメータ。結構細かく指定できるので詳細は公式ドキュメントを見てもらったほうが良い。

上の場合だと、名前に"server"を含むホストのHostIdとホスト名すべてを取得し、
割り当てられているホストグループとインターフェースを合わせて出力するという内容になる。

さて、コードにすると以下の感じになる。(始めのトークン取得まではそのままなので以降を記載する)

use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "http://my.zabbix.example.com/api_jsonrpc.php";
    let client = reqwest::Client::new();

    // ・・・先程の、"user.login"処理を行い、トークンを取得する。・・・

    // トークンを取得
    let token = content["result"].as_str().expect("Can not parse token");

    // "host.get"のリクエストを作成
    let request = json!({
        "jsonrpc": "2.0",
        "method": "host.get",
        "params": {
            "output": [
                "hostid",
                "host"
            ],
            "selectGroups": "extend",
            "selectInterfaces": "extend",
            "search": {
                "host": "server"
            }
        },
        "auth": token,
        "id": 2
    });

    // POSTの送信からレスポンス取得、JSON型への変換を一律で行う方法。
    let content: serde_json::Value = client.post(url)
                                           .json(&request)
                                           .send()
                                           .await?
                                           .json()
                                           .await?;

    // 抽出結果を取得と表示
    let hosts = content.as_array().expect("Can not parse array");
    println!("{:#}", hosts);

    Ok(())
}

一応これでcargo runとかで確認可能

このあとはもう少しシンプルに作るために処理をまとめたり型作ったりする予定