zabbix APIを使ったログイン・ログアウトをGoで実装してみた

お勉強としてZabbix APIを使ったログイン・ログアウトをするだけの実装をGoでやってみたので備忘録メモ程度に書いておきます。

# 環境

## サーバ情報

| 項目   | 内容・バージョン |
|:-------|:-----------------|
| OS     | CentOS 7.4       |
| Zabbix | 3.0.13-2         |

# 開発情報

| 項目 | 内容・バージョン |
|:-----|:-----------------|
| Go   | 1.8.1            |

# Zabbix APIドキュメント

https://www.zabbix.com/documentation/3.0/manual/api/reference/user/login
https://www.zabbix.com/documentation/3.0/manual/api/reference/user/logout

# ソース

`zbxapi` に `login` と `logout` と `postReuquest` のメソッドを実装しています。

```go
package main

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

const (
	// URL Zabbix API URL.
	URL = "http://IP or FQDN/zabbix/api_jsonrpc.php"
	// User Zabbix Login User.
	User = "admin"
	// Passwd Zabbix Login User Password.
	Passwd = "zabbix"
)

// Zabbix API構造体
type zbxapi struct {
	URL    string
	User   string
	Passwd string
	Token  string
}

// login Zabbix API ログインメソッド
func (z *zbxapi) login() error {
	// ログインリクエスト用構造体
	type data struct {
		JSONRPC string `json:"jsonrpc"`
		Method  string `json:"method"`
		Params  struct {
			User   string `json:"user"`
			Passwd string `json:"password"`
		} `json:"params"`
		ID int32 `json:"id"`
	}

	// ログインリクエストのレスポンス用構造体
	type rdata struct {
		JSONRPC string `json:"jsonrpc"`
		Result  string `json:"result"`
		ID      int32  `json:"id"`
	}
	// ログイン情報
	d := new(data)
	d.JSONRPC = "2.0"
	d.Method = "user.login"
	d.Params.User = z.User
	d.Params.Passwd = z.Passwd
	d.ID = 1

	// JSONへ変換してリクエストを実行
	req, err := json.Marshal(d)
	if err != nil {
		return err
	}
	r, err := z.postReuquest(string(req))
	if err != nil {
		return err
	}

	// レスポンスJSONをパースしてTokenを取得
	rbody := new(rdata)
	err = json.Unmarshal([]byte(r), rbody)
	if err != nil {
		return err
	}
	if rbody.Result != "" {
		z.Token = rbody.Result
		return nil
	}
	return errors.New("Error token not set")
}

// logout Zabbix API ログアウトメソッド
func (z *zbxapi) logout() {
	// ログアウト用構造体
	type data struct {
		JSONRPC string   `json:"jsonrpc"`
		Method  string   `json:"method"`
		Params  struct{} `json:"params"`
		ID      int32    `json:"id"`
		Auth    string   `json:"auth"`
	}

	// ログアウト用のレスポンス用構造体
	type rdata struct {
		JSONRPC string `json:"jsonrpc"`
		Result  bool   `json:"result"`
		ID      int32  `json:"id"`
	}

	// ログアウト情報
	d := new(data)
	d.JSONRPC = "2.0"
	d.Method = "user.logout"
	d.ID = 1
	d.Auth = z.Token

	// JSONへ変換してリクエストを実行
	req, _ := json.Marshal(d)
	r, _ := z.postReuquest(string(req))

	// レスポンスJSONをパースして結果を取得
	rbody := new(rdata)
	if err := json.Unmarshal([]byte(r), rbody); err != nil {
		log.Fatal(err)
	}

	// ログアウトが失敗した場合
	if rbody.Result != true {
		log.Fatal("Logout fail")
	} else {
		fmt.Println("Logout success")
	}
}

// postReuquest Zabbixサーバに対してPOSTを実行するメソッド
func (z *zbxapi) postReuquest(b string) (body []byte, err error) {
	// クライアントを作成
	c := createHTTPClient()
	req, err := http.NewRequest("POST", z.URL, strings.NewReader(b))
	if err != nil {
		return nil, err
	}

	// ヘッダを追加
	req.Header.Add("Content-Type", "application/json-rpc")

	// リクエストを実行
	res, err := c.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	// リクエストボディを戻す
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

// createHTTPClient クライアント作成関数
func createHTTPClient() http.Client {
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true},
		},
	}
	return *client
}

func main() {
	// Zabbix にログイン・ログアウトするだけの処理
	zapi := new(zbxapi)
	zapi.URL = URL
	zapi.User = User
	zapi.Passwd = Passwd
	if err := zapi.login(); err != nil {
		log.Fatal(err)
	} else {
		fmt.Printf("Login success\n")
		fmt.Printf("Token: %s\n", zapi.Token)
	}
	defer zapi.logout()
}
```

# 実行してみる

実行すると取得したTokenを表示してログアウトします。

```bash
$ go run main.go
Login success
Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Logout success
```

後は `getReuquest` メソッドを実装すれば値取得も出来そうだけどリクエストするJSONが複雑な場合、どう構造体を実装すればいいんだろ...が、課題かなぁ。
圧倒的Goの経験不足(´･_･`)