Skip to content

Commit cb70292

Browse files
authored
Merge pull request #684 from sysadmind/probe-dsn
Update multi-target handler to use new DSN type
2 parents ac9fa13 + 69a8024 commit cb70292

File tree

5 files changed

+281
-235
lines changed

5 files changed

+281
-235
lines changed

cmd/postgres_exporter/datasource.go

-194
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"os"
2121
"regexp"
2222
"strings"
23-
"unicode"
2423

2524
"github.com/go-kit/log/level"
2625
"github.com/prometheus/client_golang/prometheus"
@@ -173,196 +172,3 @@ func getDataSources() ([]string, error) {
173172

174173
return []string{dsn}, nil
175174
}
176-
177-
// dsn represents a parsed datasource. It contains fields for the individual connection components.
178-
type dsn struct {
179-
scheme string
180-
username string
181-
password string
182-
host string
183-
path string
184-
query string
185-
}
186-
187-
// String makes a dsn safe to print by excluding any passwords. This allows dsn to be used in
188-
// strings and log messages without needing to call a redaction function first.
189-
func (d dsn) String() string {
190-
if d.password != "" {
191-
return fmt.Sprintf("%s://%s:******@%s%s?%s", d.scheme, d.username, d.host, d.path, d.query)
192-
}
193-
194-
if d.username != "" {
195-
return fmt.Sprintf("%s://%s@%s%s?%s", d.scheme, d.username, d.host, d.path, d.query)
196-
}
197-
198-
return fmt.Sprintf("%s://%s%s?%s", d.scheme, d.host, d.path, d.query)
199-
}
200-
201-
// dsnFromString parses a connection string into a dsn. It will attempt to parse the string as
202-
// a URL and as a set of key=value pairs. If both attempts fail, dsnFromString will return an error.
203-
func dsnFromString(in string) (dsn, error) {
204-
if strings.HasPrefix(in, "postgresql://") {
205-
return dsnFromURL(in)
206-
}
207-
208-
// Try to parse as key=value pairs
209-
d, err := dsnFromKeyValue(in)
210-
if err == nil {
211-
return d, nil
212-
}
213-
214-
return dsn{}, fmt.Errorf("could not understand DSN")
215-
}
216-
217-
// dsnFromURL parses the input as a URL and returns the dsn representation.
218-
func dsnFromURL(in string) (dsn, error) {
219-
u, err := url.Parse(in)
220-
if err != nil {
221-
return dsn{}, err
222-
}
223-
pass, _ := u.User.Password()
224-
user := u.User.Username()
225-
226-
query := u.Query()
227-
228-
if queryPass := query.Get("password"); queryPass != "" {
229-
if pass == "" {
230-
pass = queryPass
231-
}
232-
}
233-
query.Del("password")
234-
235-
if queryUser := query.Get("user"); queryUser != "" {
236-
if user == "" {
237-
user = queryUser
238-
}
239-
}
240-
query.Del("user")
241-
242-
d := dsn{
243-
scheme: u.Scheme,
244-
username: user,
245-
password: pass,
246-
host: u.Host,
247-
path: u.Path,
248-
query: query.Encode(),
249-
}
250-
251-
return d, nil
252-
}
253-
254-
// dsnFromKeyValue parses the input as a set of key=value pairs and returns the dsn representation.
255-
func dsnFromKeyValue(in string) (dsn, error) {
256-
// Attempt to confirm at least one key=value pair before starting the rune parser
257-
connstringRe := regexp.MustCompile(`^ *[a-zA-Z0-9]+ *= *[^= ]+`)
258-
if !connstringRe.MatchString(in) {
259-
return dsn{}, fmt.Errorf("input is not a key-value DSN")
260-
}
261-
262-
// Anything other than known fields should be part of the querystring
263-
query := url.Values{}
264-
265-
pairs, err := parseKeyValue(in)
266-
if err != nil {
267-
return dsn{}, fmt.Errorf("failed to parse key-value DSN: %v", err)
268-
}
269-
270-
// Build the dsn from the key=value pairs
271-
d := dsn{
272-
scheme: "postgresql",
273-
}
274-
275-
hostname := ""
276-
port := ""
277-
278-
for k, v := range pairs {
279-
switch k {
280-
case "host":
281-
hostname = v
282-
case "port":
283-
port = v
284-
case "user":
285-
d.username = v
286-
case "password":
287-
d.password = v
288-
default:
289-
query.Set(k, v)
290-
}
291-
}
292-
293-
if hostname == "" {
294-
hostname = "localhost"
295-
}
296-
297-
if port == "" {
298-
d.host = hostname
299-
} else {
300-
d.host = fmt.Sprintf("%s:%s", hostname, port)
301-
}
302-
303-
d.query = query.Encode()
304-
305-
return d, nil
306-
}
307-
308-
// parseKeyValue is a key=value parser. It loops over each rune to split out keys and values
309-
// and attempting to honor quoted values. parseKeyValue will return an error if it is unable
310-
// to properly parse the input.
311-
func parseKeyValue(in string) (map[string]string, error) {
312-
out := map[string]string{}
313-
314-
inPart := false
315-
inQuote := false
316-
part := []rune{}
317-
key := ""
318-
for _, c := range in {
319-
switch {
320-
case unicode.In(c, unicode.Quotation_Mark):
321-
if inQuote {
322-
inQuote = false
323-
} else {
324-
inQuote = true
325-
}
326-
case unicode.In(c, unicode.White_Space):
327-
if inPart {
328-
if inQuote {
329-
part = append(part, c)
330-
} else {
331-
// Are we finishing a key=value?
332-
if key == "" {
333-
return out, fmt.Errorf("invalid input")
334-
}
335-
out[key] = string(part)
336-
inPart = false
337-
part = []rune{}
338-
}
339-
} else {
340-
// Are we finishing a key=value?
341-
if key == "" {
342-
return out, fmt.Errorf("invalid input")
343-
}
344-
out[key] = string(part)
345-
inPart = false
346-
part = []rune{}
347-
// Do something with the value
348-
}
349-
case c == '=':
350-
if inPart {
351-
inPart = false
352-
key = string(part)
353-
part = []rune{}
354-
} else {
355-
return out, fmt.Errorf("invalid input")
356-
}
357-
default:
358-
inPart = true
359-
part = append(part, c)
360-
}
361-
}
362-
363-
if key != "" && len(part) > 0 {
364-
out[key] = string(part)
365-
}
366-
367-
return out, nil
368-
}

collector/probe.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ package collector
1616
import (
1717
"context"
1818
"database/sql"
19-
"fmt"
20-
"strings"
2119
"sync"
2220

2321
"github.com/go-kit/log"
22+
"github.com/prometheus-community/postgres_exporter/config"
2423
"github.com/prometheus/client_golang/prometheus"
2524
)
2625

@@ -31,7 +30,7 @@ type ProbeCollector struct {
3130
db *sql.DB
3231
}
3332

34-
func NewProbeCollector(logger log.Logger, registry *prometheus.Registry, dsn string) (*ProbeCollector, error) {
33+
func NewProbeCollector(logger log.Logger, registry *prometheus.Registry, dsn config.DSN) (*ProbeCollector, error) {
3534
collectors := make(map[string]Collector)
3635
initiatedCollectorsMtx.Lock()
3736
defer initiatedCollectorsMtx.Unlock()
@@ -55,11 +54,7 @@ func NewProbeCollector(logger log.Logger, registry *prometheus.Registry, dsn str
5554
}
5655
}
5756

58-
if !strings.HasPrefix(dsn, "postgres://") {
59-
dsn = fmt.Sprintf("postgres://%s", dsn)
60-
}
61-
62-
db, err := sql.Open("postgres", dsn)
57+
db, err := sql.Open("postgres", dsn.GetConnectionString())
6358
if err != nil {
6459
return nil, err
6560
}

config/config.go

+13-15
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ package config
1515

1616
import (
1717
"fmt"
18-
"net/url"
1918
"os"
20-
"strings"
2119
"sync"
2220

2321
"github.com/go-kit/log"
@@ -97,26 +95,26 @@ func (ch *ConfigHandler) ReloadConfig(f string, logger log.Logger) error {
9795
return nil
9896
}
9997

100-
func (m AuthModule) ConfigureTarget(target string) (string, error) {
101-
// ip:port urls do not parse properly and that is the typical way users interact with postgres
102-
t := fmt.Sprintf("exporter://%s", target)
103-
u, err := url.Parse(t)
98+
func (m AuthModule) ConfigureTarget(target string) (DSN, error) {
99+
dsn, err := dsnFromString(target)
104100
if err != nil {
105-
return "", err
101+
return DSN{}, err
106102
}
107103

104+
// Set the credentials from the authentication module
105+
// TODO(@sysadmind): What should the order of precedence be?
108106
if m.Type == "userpass" {
109-
u.User = url.UserPassword(m.UserPass.Username, m.UserPass.Password)
107+
if m.UserPass.Username != "" {
108+
dsn.username = m.UserPass.Username
109+
}
110+
if m.UserPass.Password != "" {
111+
dsn.password = m.UserPass.Password
112+
}
110113
}
111114

112-
query := u.Query()
113115
for k, v := range m.Options {
114-
query.Set(k, v)
116+
dsn.query.Set(k, v)
115117
}
116-
u.RawQuery = query.Encode()
117-
118-
parsed := u.String()
119-
trim := strings.TrimPrefix(parsed, "exporter://")
120118

121-
return trim, nil
119+
return dsn, nil
122120
}

0 commit comments

Comments
 (0)