Files
grafana-zabbix/pkg/zabbix/zabbix.go
Jocelyn Collado-Kuri e073382983 Fix always fetch Zabbix version before issuing new requests (#2133)
Previously we were only fetching the version when the version was `0`.
This generally worked, but posed some problems when customers were
updating their Zabbix version, specifically when upgrading from a
version < `7.2.x` to `7.2.x` or above.

Before `7.2.x`, an `auth` parameter was still supported when issuing a
zabbix request, this was deprecated in `6.4.x` and later removed in
`7.2.x`. When a user was on a version < `7.2.x` all the outgoing
requests would add this `auth` parameter. When upgrading to `7.2.x` this
was a problem, because the version was not `0`, hence, not requiring
getting the version again, but also because we were still building the
request considering an older version and adding the `auth` parameter,
when this was no longer supported.

This PR removes the check for `version == 0`, though this now means that
every request that goes out will check the version before hand, I think
this will give us a more accurate representation of the version that
needs to be used.

fixes
https://github.com/orgs/grafana/projects/457/views/40?pane=issue&itemId=3683181283&issue=grafana%7Coss-big-tent-squad%7C135
2025-12-05 17:34:20 -08:00

161 lines
4.6 KiB
Go

package zabbix
import (
"context"
"errors"
"strings"
"time"
"github.com/alexanderzobnin/grafana-zabbix/pkg/metrics"
"github.com/alexanderzobnin/grafana-zabbix/pkg/settings"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbixapi"
"github.com/bitly/go-simplejson"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
// Zabbix is a wrapper for Zabbix API. It wraps Zabbix API queries and performs authentication, adds caching,
// deduplication and other performance optimizations.
type Zabbix struct {
api *zabbixapi.ZabbixAPI
dsInfo *backend.DataSourceInstanceSettings
settings *settings.ZabbixDatasourceSettings
cache *ZabbixCache
version int
logger log.Logger
}
// New returns new instance of Zabbix client.
func New(dsInfo *backend.DataSourceInstanceSettings, zabbixSettings *settings.ZabbixDatasourceSettings, zabbixAPI *zabbixapi.ZabbixAPI) (*Zabbix, error) {
logger := log.New()
zabbixCache := NewZabbixCache(zabbixSettings.CacheTTL, 10*time.Minute)
return &Zabbix{
api: zabbixAPI,
dsInfo: dsInfo,
settings: zabbixSettings,
cache: zabbixCache,
logger: logger,
}, nil
}
func (zabbix *Zabbix) GetAPI() *zabbixapi.ZabbixAPI {
return zabbix.api
}
// Request wraps request with cache
func (ds *Zabbix) Request(ctx context.Context, apiReq *ZabbixAPIRequest) (*simplejson.Json, error) {
var resultJson *simplejson.Json
var err error
version, err := ds.GetVersion(ctx)
if err != nil {
ds.logger.Error("Error querying Zabbix version", "error", err)
ds.version = -1
} else {
ds.version = version
}
cachedResult, queryExistInCache := ds.cache.GetAPIRequest(apiReq)
if !queryExistInCache {
resultJson, err = ds.request(ctx, apiReq.Method, apiReq.Params)
if err != nil {
return nil, err
}
if IsCachedRequest(apiReq.Method) {
ds.logger.Debug("Writing result to cache", "method", apiReq.Method, "version", ds.version)
ds.cache.SetAPIRequest(apiReq, resultJson)
}
} else {
metrics.CacheHitTotal.WithLabelValues(apiReq.Method).Inc()
var ok bool
resultJson, ok = cachedResult.(*simplejson.Json)
if !ok {
resultJson = simplejson.New()
}
}
return resultJson, nil
}
// request checks authentication and makes a request to the Zabbix API.
func (zabbix *Zabbix) request(ctx context.Context, method string, params ZabbixAPIParams) (*simplejson.Json, error) {
zabbix.logger.Debug("Zabbix request", "method", method, "version", zabbix.version)
// Skip auth for methods that are not required it
if method == "apiinfo.version" {
return zabbix.api.RequestUnauthenticated(ctx, method, params, zabbix.version)
}
result, err := zabbix.api.Request(ctx, method, params, zabbix.version)
notAuthorized := isNotAuthorized(err)
isTokenAuth := zabbix.settings.AuthType == settings.AuthTypeToken
if err == backend.DownstreamError(zabbixapi.ErrNotAuthenticated) || (notAuthorized && !isTokenAuth) {
if notAuthorized {
zabbix.logger.Debug("Authentication token expired, performing re-login")
}
err = zabbix.Authenticate(ctx)
if err != nil {
return nil, err
}
return zabbix.request(ctx, method, params)
} else if err != nil {
return nil, err
}
return result, err
}
func (zabbix *Zabbix) Authenticate(ctx context.Context) error {
jsonData, err := simplejson.NewJson(zabbix.dsInfo.JSONData)
if err != nil {
return err
}
authType := zabbix.settings.AuthType
if authType == settings.AuthTypeToken {
token, exists := zabbix.dsInfo.DecryptedSecureJSONData["apiToken"]
if !exists {
return backend.DownstreamError(errors.New("cannot find Zabbix API token"))
}
err = zabbix.api.AuthenticateWithToken(ctx, token)
if err != nil {
zabbix.logger.Error("Zabbix authentication token error", "error", err)
return err
}
zabbix.logger.Debug("Using API token for authentication")
return nil
}
zabbixLogin := jsonData.Get("username").MustString()
var zabbixPassword string
if securePassword, exists := zabbix.dsInfo.DecryptedSecureJSONData["password"]; exists {
zabbixPassword = securePassword
} else {
// Fallback
zabbixPassword = jsonData.Get("password").MustString()
}
err = zabbix.api.Authenticate(ctx, zabbixLogin, zabbixPassword, zabbix.version)
if err != nil {
zabbix.logger.Error("Zabbix authentication error", "error", err)
return err
}
zabbix.logger.Debug("Successfully authenticated", "url", zabbix.api.GetUrl().String(), "user", zabbixLogin)
return nil
}
func isNotAuthorized(err error) bool {
if err == nil {
return false
}
message := err.Error()
return strings.Contains(message, "Session terminated, re-login, please.") ||
strings.Contains(message, "Not authorised.") ||
strings.Contains(message, "Not authorized.")
}