Skip to content

Commit 3b26962

Browse files
authored
Merge branch 'master' into master
2 parents ce5826a + 6e3d130 commit 3b26962

File tree

33 files changed

+1710
-1146
lines changed

33 files changed

+1710
-1146
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
- docker
44
language: go
55
go:
6-
- '1.8'
6+
- '1.9'
77
# Make sure we have p2 and the postgres client.
88
before_install:
99
- go get -v github.com/mattn/goveralls

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ lint: tools
4242
fmt: tools
4343
gofmt -s -w $(GO_SRC)
4444

45-
test: tools
46-
@mkdir -p $(COVERDIR)
47-
@rm -f $(COVERDIR)/*
45+
run-tests: tools
46+
mkdir -p $(COVERDIR)
47+
rm -f $(COVERDIR)/*
4848
for pkg in $(GO_PKGS) ; do \
4949
go test -v -covermode count -coverprofile=$(COVERDIR)/$$(echo $$pkg | tr '/' '-').out $$pkg ; \
5050
done
51+
52+
test: run-tests
5153
gocovmerge $(shell find $(COVERDIR) -name '*.out') > cover.test.out
5254

5355
test-integration: postgres_exporter postgres_exporter_integration_test
@@ -74,6 +76,6 @@ tools:
7476
$(MAKE) -C $(TOOLDIR)
7577

7678
clean:
77-
rm -f postgres_exporter postgres_exporter_integration_test
79+
rm -rf postgres_exporter postgres_exporter_integration_test $(COVERDIR)
7880

7981
.PHONY: tools docker-build docker lint fmt test vet push cross clean

pg_setting.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (s *pgSetting) normaliseUnit() (val float64, unit string, err error) {
9494
return
9595
case "ms", "s", "min", "h", "d":
9696
unit = "seconds"
97-
case "kB", "MB", "GB", "TB", "8kB", "16MB":
97+
case "kB", "MB", "GB", "TB", "8kB", "16kB", "16MB":
9898
unit = "bytes"
9999
default:
100100
err = fmt.Errorf("Unknown unit for runtime variable: %q", s.unit)
@@ -125,6 +125,8 @@ func (s *pgSetting) normaliseUnit() (val float64, unit string, err error) {
125125
val *= math.Pow(2, 40)
126126
case "8kB":
127127
val *= math.Pow(2, 13)
128+
case "16kB":
129+
val *= math.Pow(2, 14)
128130
case "16MB":
129131
val *= math.Pow(2, 24)
130132
}

pg_setting_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ var fixtures = []fixture{
6060
d: "Desc{fqName: \"pg_settings_eight_kb_fixture_metric_bytes\", help: \"Foo foo foo [Units converted to bytes.]\", constLabels: {}, variableLabels: []}",
6161
v: 139264,
6262
},
63+
{
64+
p: pgSetting{
65+
name: "16_kb_real_fixture_metric",
66+
setting: "3.0",
67+
unit: "16kB",
68+
shortDesc: "Foo foo foo",
69+
vartype: "real",
70+
},
71+
n: normalised{
72+
val: 49152,
73+
unit: "bytes",
74+
err: "",
75+
},
76+
d: "Desc{fqName: \"pg_settings_16_kb_real_fixture_metric_bytes\", help: \"Foo foo foo [Units converted to bytes.]\", constLabels: {}, variableLabels: []}",
77+
v: 49152,
78+
},
6379
{
6480
p: pgSetting{
6581
name: "16_mb_real_fixture_metric",

postgres_exporter.go

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"regexp"
1414
"runtime"
1515
"strconv"
16+
"strings"
1617
"sync"
1718
"time"
1819

@@ -28,8 +29,6 @@ import (
2829
// (semantic version)-(commitish) form.
2930
var Version = "0.0.1"
3031

31-
var sharedDBConn *sql.DB
32-
3332
var (
3433
listenAddress = flag.String(
3534
"web.listen-address", ":9187",
@@ -673,6 +672,11 @@ type Exporter struct {
673672
duration, error prometheus.Gauge
674673
totalScrapes prometheus.Counter
675674

675+
// dbDsn is the connection string used to establish the dbConnection
676+
dbDsn string
677+
// dbConnection is used to allow re-using the DB connection between scrapes
678+
dbConnection *sql.DB
679+
676680
// Last version used to calculate metric map. If mismatch on scrape,
677681
// then maps are recalculated.
678682
lastMapVersion semver.Version
@@ -923,8 +927,16 @@ func (e *Exporter) checkMapVersions(ch chan<- prometheus.Metric, db *sql.DB) err
923927
return nil
924928
}
925929

926-
func getDB(conn string) (*sql.DB, error) {
927-
if sharedDBConn == nil {
930+
func (e *Exporter) getDB(conn string) (*sql.DB, error) {
931+
// Has dsn changed?
932+
if (e.dbConnection != nil) && (e.dsn != e.dbDsn) {
933+
err := e.dbConnection.Close()
934+
log.Warnln("Error while closing obsolete DB connection:", err)
935+
e.dbConnection = nil
936+
e.dbDsn = ""
937+
}
938+
939+
if e.dbConnection == nil {
928940
d, err := sql.Open("postgres", conn)
929941
if err != nil {
930942
return nil, err
@@ -935,10 +947,12 @@ func getDB(conn string) (*sql.DB, error) {
935947
}
936948
d.SetMaxOpenConns(1)
937949
d.SetMaxIdleConns(1)
938-
sharedDBConn = d
950+
e.dbConnection = d
951+
e.dbDsn = e.dsn
952+
log.Infoln("Established new database connection.")
939953
}
940954

941-
return sharedDBConn, nil
955+
return e.dbConnection, nil
942956
}
943957

944958
func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
@@ -949,11 +963,16 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
949963
e.error.Set(0)
950964
e.totalScrapes.Inc()
951965

952-
db, err := getDB(e.dsn)
966+
db, err := e.getDB(e.dsn)
953967
if err != nil {
954968
loggableDsn := "could not parse DATA_SOURCE_NAME"
955-
if pDsn, pErr := url.Parse(e.dsn); pErr != nil {
956-
pDsn.User = url.UserPassword(pDsn.User.Username(), "xxx")
969+
// If the DSN is parseable, log it with a blanked out password
970+
pDsn, pErr := url.Parse(e.dsn)
971+
if pErr == nil {
972+
// Blank user info if not nil
973+
if pDsn.User != nil {
974+
pDsn.User = url.UserPassword(pDsn.User.Username(), "PASSWORD_REMOVED")
975+
}
957976
loggableDsn = pDsn.String()
958977
}
959978
log.Infof("Error opening connection to database (%s): %s", loggableDsn, err)
@@ -981,6 +1000,43 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
9811000
}
9821001
}
9831002

1003+
// try to get the DataSource
1004+
// DATA_SOURCE_NAME always wins so we do not break older versions
1005+
// reading secrets from files wins over secrets in environment variables
1006+
// DATA_SOURCE_NAME > DATA_SOURCE_{USER|FILE}_FILE > DATA_SOURCE_{USER|FILE}
1007+
func getDataSource() string {
1008+
var dsn = os.Getenv("DATA_SOURCE_NAME")
1009+
if len(dsn) == 0 {
1010+
var user string
1011+
var pass string
1012+
1013+
if len(os.Getenv("DATA_SOURCE_USER_FILE")) != 0 {
1014+
fileContents, err := ioutil.ReadFile(os.Getenv("DATA_SOURCE_USER_FILE"))
1015+
if err != nil {
1016+
panic(err)
1017+
}
1018+
user = strings.TrimSpace(string(fileContents))
1019+
} else {
1020+
user = os.Getenv("DATA_SOURCE_USER")
1021+
}
1022+
1023+
if len(os.Getenv("DATA_SOURCE_PASS_FILE")) != 0 {
1024+
fileContents, err := ioutil.ReadFile(os.Getenv("DATA_SOURCE_PASS_FILE"))
1025+
if err != nil {
1026+
panic(err)
1027+
}
1028+
pass = strings.TrimSpace(string(fileContents))
1029+
} else {
1030+
pass = os.Getenv("DATA_SOURCE_PASS")
1031+
}
1032+
1033+
uri := os.Getenv("DATA_SOURCE_URI")
1034+
dsn = "postgresql://" + user + ":" + pass + "@" + uri
1035+
}
1036+
1037+
return dsn
1038+
}
1039+
9841040
func main() {
9851041
flag.Parse()
9861042

@@ -997,12 +1053,18 @@ func main() {
9971053
return
9981054
}
9991055

1000-
dsn := os.Getenv("DATA_SOURCE_NAME")
1056+
dsn := getDataSource()
10011057
if len(dsn) == 0 {
1002-
log.Fatal("couldn't find environment variable DATA_SOURCE_NAME")
1058+
log.Fatal("couldn't find environment variables describing the datasource to use")
10031059
}
10041060

10051061
exporter := NewExporter(dsn, *queriesPath)
1062+
defer func() {
1063+
if exporter.dbConnection != nil {
1064+
exporter.dbConnection.Close() // nolint: errcheck
1065+
}
1066+
}()
1067+
10061068
prometheus.MustRegister(exporter)
10071069

10081070
http.Handle(*metricPath, prometheus.Handler())
@@ -1012,7 +1074,4 @@ func main() {
10121074

10131075
log.Infof("Starting Server: %s", *listenAddress)
10141076
log.Fatal(http.ListenAndServe(*listenAddress, nil))
1015-
if sharedDBConn != nil {
1016-
defer sharedDBConn.Close() // nolint: errcheck
1017-
}
10181077
}

postgres_exporter_integration_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,25 @@ func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) {
7373
}
7474
}
7575
}
76+
77+
// TestInvalidDsnDoesntCrash tests that specifying an invalid DSN doesn't crash
78+
// the exporter. Related to https://github.com/wrouesnel/postgres_exporter/issues/93
79+
// although not a replication of the scenario.
80+
func (s *IntegrationSuite) TestInvalidDsnDoesntCrash(c *C) {
81+
// Setup a dummy channel to consume metrics
82+
ch := make(chan prometheus.Metric, 100)
83+
go func() {
84+
for range ch {
85+
}
86+
}()
87+
88+
// Send a bad DSN
89+
exporter := NewExporter("invalid dsn", *queriesPath)
90+
c.Assert(exporter, NotNil)
91+
exporter.scrape(ch)
92+
93+
// Send a DSN to a non-listening port.
94+
exporter = NewExporter("postgresql://nothing:[email protected]:1/nothing", *queriesPath)
95+
c.Assert(exporter, NotNil)
96+
exporter.scrape(ch)
97+
}

postgres_exporter_test.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
"testing"
88

99
"github.com/blang/semver"
10+
"os"
1011
)
1112

1213
// Hook up gocheck into the "go test" runner.
1314
func Test(t *testing.T) { TestingT(t) }
1415

1516
type FunctionalSuite struct {
16-
e *Exporter
1717
}
1818

1919
var _ = Suite(&FunctionalSuite{})
@@ -85,3 +85,67 @@ func (s *FunctionalSuite) TestSemanticVersionColumnDiscard(c *C) {
8585
)
8686
}
8787
}
88+
89+
// test read username and password from file
90+
func (s *FunctionalSuite) TestEnvironmentSettingWithSecretsFiles(c *C) {
91+
92+
err := os.Setenv("DATA_SOURCE_USER_FILE", "./tests/username_file")
93+
c.Assert(err, IsNil)
94+
defer UnsetEnvironment(c, "DATA_SOURCE_USER_FILE")
95+
96+
err = os.Setenv("DATA_SOURCE_PASS_FILE", "./tests/userpass_file")
97+
c.Assert(err, IsNil)
98+
defer UnsetEnvironment(c, "DATA_SOURCE_PASS_FILE")
99+
100+
err = os.Setenv("DATA_SOURCE_URI", "localhost:5432/?sslmode=disable")
101+
c.Assert(err, IsNil)
102+
defer UnsetEnvironment(c, "DATA_SOURCE_URI")
103+
104+
var expected = "postgresql://custom_username:custom_password@localhost:5432/?sslmode=disable"
105+
106+
dsn := getDataSource()
107+
if dsn != expected {
108+
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, expected)
109+
}
110+
}
111+
112+
// test read DATA_SOURCE_NAME from environment
113+
func (s *FunctionalSuite) TestEnvironmentSettingWithDns(c *C) {
114+
115+
envDsn := "postgresql://user:password@localhost:5432/?sslmode=enabled"
116+
err := os.Setenv("DATA_SOURCE_NAME", envDsn)
117+
c.Assert(err, IsNil)
118+
defer UnsetEnvironment(c, "DATA_SOURCE_NAME")
119+
120+
dsn := getDataSource()
121+
if dsn != envDsn {
122+
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, envDsn)
123+
}
124+
}
125+
126+
// test DATA_SOURCE_NAME is used even if username and password environment variables are set
127+
func (s *FunctionalSuite) TestEnvironmentSettingWithDnsAndSecrets(c *C) {
128+
129+
envDsn := "postgresql://userDsn:passwordDsn@localhost:55432/?sslmode=disabled"
130+
err := os.Setenv("DATA_SOURCE_NAME", envDsn)
131+
c.Assert(err, IsNil)
132+
defer UnsetEnvironment(c, "DATA_SOURCE_NAME")
133+
134+
err = os.Setenv("DATA_SOURCE_USER_FILE", "./tests/username_file")
135+
c.Assert(err, IsNil)
136+
defer UnsetEnvironment(c, "DATA_SOURCE_USER_FILE")
137+
138+
err = os.Setenv("DATA_SOURCE_PASS", "envUserPass")
139+
c.Assert(err, IsNil)
140+
defer UnsetEnvironment(c, "DATA_SOURCE_PASS")
141+
142+
dsn := getDataSource()
143+
if dsn != envDsn {
144+
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, envDsn)
145+
}
146+
}
147+
148+
func UnsetEnvironment(c *C, d string) {
149+
err := os.Unsetenv(d)
150+
c.Assert(err, IsNil)
151+
}

queries.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,4 @@ pg_stat_statements:
142142
- blk_write_time:
143143
usage: "COUNTER"
144144
description: "Total time the statement spent writing blocks, in milliseconds (if track_io_timing is enabled, otherwise zero)"
145+

tests/username_file

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
custom_username

tests/userpass_file

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
custom_password

tools/.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/pkg
2-
bin
3-
tools.deps
4-
metatools.deps
2+
/bin
3+
/tools.deps
4+
/metatools.deps

0 commit comments

Comments
 (0)