1/*
2** Zabbix
3** Copyright (C) 2001-2021 Zabbix SIA
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18**/
19
20package postgres
21
22import (
23	"context"
24	"net/http"
25	"net/url"
26	"time"
27
28	"zabbix.com/pkg/tlsconfig"
29	"zabbix.com/pkg/uri"
30	"zabbix.com/pkg/zbxerr"
31
32	"github.com/omeid/go-yarn"
33
34	"zabbix.com/pkg/plugin"
35)
36
37const (
38	pluginName = "Postgres"
39	sqlExt     = ".sql"
40	hkInterval = 10
41)
42
43// Plugin inherits plugin.Base and store plugin-specific data.
44type Plugin struct {
45	plugin.Base
46	connMgr *ConnManager
47	options PluginOptions
48}
49
50// impl is the pointer to the plugin implementation.
51var impl Plugin
52
53// Export implements the Exporter interface.
54func (p *Plugin) Export(key string, rawParams []string, _ plugin.ContextProvider) (result interface{}, err error) {
55	var extraParams []string
56
57	params, err := metrics[key].EvalParams(rawParams, p.options.Sessions)
58	if err != nil {
59		return nil, err
60	}
61
62	details, err := tlsconfig.CreateDetails(params["sessionName"], params["TLSConnect"],
63		params["TLSCAFile"], params["TLSCertFile"], params["TLSKeyFile"], params["URI"])
64	if err != nil {
65		return nil, zbxerr.ErrorInvalidConfiguration.Wrap(err)
66	}
67
68	dbname := url.QueryEscape(params["Database"])
69
70	uri, err := uri.NewWithCreds(params["URI"]+"?dbname="+dbname, params["User"], params["Password"], uriDefaults)
71	if err != nil {
72		return nil, err
73	}
74
75	if len(rawParams) > len(params) {
76		extraParams = rawParams[len(params):]
77	}
78
79	handleMetric := getHandlerFunc(key)
80	if handleMetric == nil {
81		return nil, zbxerr.ErrorUnsupportedMetric
82	}
83
84	conn, err := p.connMgr.GetConnection(*uri, details)
85	if err != nil {
86		// Special logic of processing connection errors should be used if pgsql.ping is requested
87		// because it must return pingFailed if any error occurred.
88		if key == keyPing {
89			return pingFailed, nil
90		}
91
92		p.Errf(err.Error())
93
94		return nil, err
95	}
96
97	ctx, cancel := context.WithTimeout(conn.ctx, conn.callTimeout)
98	defer cancel()
99
100	result, err = handleMetric(ctx, conn, key, params, extraParams...)
101
102	if err != nil {
103		p.Errf(err.Error())
104	}
105
106	return result, err
107}
108
109// Start implements the Runner interface and performs initialization when plugin is activated.
110func (p *Plugin) Start() {
111	queryStorage, err := yarn.New(http.Dir(p.options.CustomQueriesPath), "*"+sqlExt)
112	if err != nil {
113		p.Errf(err.Error())
114		// create empty storage if error occurred
115		queryStorage = yarn.NewFromMap(map[string]string{})
116	}
117
118	p.connMgr = NewConnManager(
119		time.Duration(p.options.KeepAlive)*time.Second,
120		time.Duration(p.options.Timeout)*time.Second,
121		time.Duration(p.options.CallTimeout)*time.Second,
122		hkInterval*time.Second,
123		queryStorage,
124	)
125}
126
127// Stop implements the Runner interface and frees resources when plugin is deactivated.
128func (p *Plugin) Stop() {
129	p.connMgr.Destroy()
130	p.connMgr = nil
131}
132