|
| 1 | +// Copyright 2021 The Prometheus Authors |
| 2 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 3 | +// you may not use this file except in compliance with the License. |
| 4 | +// You may obtain a copy of the License at |
| 5 | +// |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +// |
| 8 | +// Unless required by applicable law or agreed to in writing, software |
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 11 | +// See the License for the specific language governing permissions and |
| 12 | +// limitations under the License. |
| 13 | + |
| 14 | +package main |
| 15 | + |
| 16 | +import ( |
| 17 | + "fmt" |
| 18 | + "io/ioutil" |
| 19 | + "net/url" |
| 20 | + "os" |
| 21 | + "regexp" |
| 22 | + "strings" |
| 23 | + |
| 24 | + "github.com/go-kit/kit/log/level" |
| 25 | + "github.com/prometheus/client_golang/prometheus" |
| 26 | +) |
| 27 | + |
| 28 | +func (e *Exporter) discoverDatabaseDSNs() []string { |
| 29 | + // connstring syntax is complex (and not sure if even regular). |
| 30 | + // we don't need to parse it, so just superficially validate that it starts |
| 31 | + // with a valid-ish keyword pair |
| 32 | + connstringRe := regexp.MustCompile(`^ *[a-zA-Z0-9]+ *= *[^= ]+`) |
| 33 | + |
| 34 | + dsns := make(map[string]struct{}) |
| 35 | + for _, dsn := range e.dsn { |
| 36 | + var dsnURI *url.URL |
| 37 | + var dsnConnstring string |
| 38 | + |
| 39 | + if strings.HasPrefix(dsn, "postgresql://") { |
| 40 | + var err error |
| 41 | + dsnURI, err = url.Parse(dsn) |
| 42 | + if err != nil { |
| 43 | + level.Error(logger).Log("msg", "Unable to parse DSN as URI", "dsn", loggableDSN(dsn), "err", err) |
| 44 | + continue |
| 45 | + } |
| 46 | + } else if connstringRe.MatchString(dsn) { |
| 47 | + dsnConnstring = dsn |
| 48 | + } else { |
| 49 | + level.Error(logger).Log("msg", "Unable to parse DSN as either URI or connstring", "dsn", loggableDSN(dsn)) |
| 50 | + continue |
| 51 | + } |
| 52 | + |
| 53 | + server, err := e.servers.GetServer(dsn) |
| 54 | + if err != nil { |
| 55 | + level.Error(logger).Log("msg", "Error opening connection to database", "dsn", loggableDSN(dsn), "err", err) |
| 56 | + continue |
| 57 | + } |
| 58 | + dsns[dsn] = struct{}{} |
| 59 | + |
| 60 | + // If autoDiscoverDatabases is true, set first dsn as master database (Default: false) |
| 61 | + server.master = true |
| 62 | + |
| 63 | + databaseNames, err := queryDatabases(server) |
| 64 | + if err != nil { |
| 65 | + level.Error(logger).Log("msg", "Error querying databases", "dsn", loggableDSN(dsn), "err", err) |
| 66 | + continue |
| 67 | + } |
| 68 | + for _, databaseName := range databaseNames { |
| 69 | + if contains(e.excludeDatabases, databaseName) { |
| 70 | + continue |
| 71 | + } |
| 72 | + |
| 73 | + if len(e.includeDatabases) != 0 && !contains(e.includeDatabases, databaseName) { |
| 74 | + continue |
| 75 | + } |
| 76 | + |
| 77 | + if dsnURI != nil { |
| 78 | + dsnURI.Path = databaseName |
| 79 | + dsn = dsnURI.String() |
| 80 | + } else { |
| 81 | + // replacing one dbname with another is complicated. |
| 82 | + // just append new dbname to override. |
| 83 | + dsn = fmt.Sprintf("%s dbname=%s", dsnConnstring, databaseName) |
| 84 | + } |
| 85 | + dsns[dsn] = struct{}{} |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + result := make([]string, len(dsns)) |
| 90 | + index := 0 |
| 91 | + for dsn := range dsns { |
| 92 | + result[index] = dsn |
| 93 | + index++ |
| 94 | + } |
| 95 | + |
| 96 | + return result |
| 97 | +} |
| 98 | + |
| 99 | +func (e *Exporter) scrapeDSN(ch chan<- prometheus.Metric, dsn string) error { |
| 100 | + server, err := e.servers.GetServer(dsn) |
| 101 | + |
| 102 | + if err != nil { |
| 103 | + return &ErrorConnectToServer{fmt.Sprintf("Error opening connection to database (%s): %s", loggableDSN(dsn), err.Error())} |
| 104 | + } |
| 105 | + |
| 106 | + // Check if autoDiscoverDatabases is false, set dsn as master database (Default: false) |
| 107 | + if !e.autoDiscoverDatabases { |
| 108 | + server.master = true |
| 109 | + } |
| 110 | + |
| 111 | + // Check if map versions need to be updated |
| 112 | + if err := e.checkMapVersions(ch, server); err != nil { |
| 113 | + level.Warn(logger).Log("msg", "Proceeding with outdated query maps, as the Postgres version could not be determined", "err", err) |
| 114 | + } |
| 115 | + |
| 116 | + return server.Scrape(ch, e.disableSettingsMetrics) |
| 117 | +} |
| 118 | + |
| 119 | +// try to get the DataSource |
| 120 | +// DATA_SOURCE_NAME always wins so we do not break older versions |
| 121 | +// reading secrets from files wins over secrets in environment variables |
| 122 | +// DATA_SOURCE_NAME > DATA_SOURCE_{USER|PASS}_FILE > DATA_SOURCE_{USER|PASS} |
| 123 | +func getDataSources() ([]string, error) { |
| 124 | + var dsn = os.Getenv("DATA_SOURCE_NAME") |
| 125 | + if len(dsn) != 0 { |
| 126 | + return strings.Split(dsn, ","), nil |
| 127 | + } |
| 128 | + |
| 129 | + var user, pass, uri string |
| 130 | + |
| 131 | + dataSourceUserFile := os.Getenv("DATA_SOURCE_USER_FILE") |
| 132 | + if len(dataSourceUserFile) != 0 { |
| 133 | + fileContents, err := ioutil.ReadFile(dataSourceUserFile) |
| 134 | + if err != nil { |
| 135 | + return nil, fmt.Errorf("failed loading data source user file %s: %s", dataSourceUserFile, err.Error()) |
| 136 | + } |
| 137 | + user = strings.TrimSpace(string(fileContents)) |
| 138 | + } else { |
| 139 | + user = os.Getenv("DATA_SOURCE_USER") |
| 140 | + } |
| 141 | + |
| 142 | + dataSourcePassFile := os.Getenv("DATA_SOURCE_PASS_FILE") |
| 143 | + if len(dataSourcePassFile) != 0 { |
| 144 | + fileContents, err := ioutil.ReadFile(dataSourcePassFile) |
| 145 | + if err != nil { |
| 146 | + return nil, fmt.Errorf("failed loading data source pass file %s: %s", dataSourcePassFile, err.Error()) |
| 147 | + } |
| 148 | + pass = strings.TrimSpace(string(fileContents)) |
| 149 | + } else { |
| 150 | + pass = os.Getenv("DATA_SOURCE_PASS") |
| 151 | + } |
| 152 | + |
| 153 | + ui := url.UserPassword(user, pass).String() |
| 154 | + dataSrouceURIFile := os.Getenv("DATA_SOURCE_URI_FILE") |
| 155 | + if len(dataSrouceURIFile) != 0 { |
| 156 | + fileContents, err := ioutil.ReadFile(dataSrouceURIFile) |
| 157 | + if err != nil { |
| 158 | + return nil, fmt.Errorf("failed loading data source URI file %s: %s", dataSrouceURIFile, err.Error()) |
| 159 | + } |
| 160 | + uri = strings.TrimSpace(string(fileContents)) |
| 161 | + } else { |
| 162 | + uri = os.Getenv("DATA_SOURCE_URI") |
| 163 | + } |
| 164 | + |
| 165 | + dsn = "postgresql://" + ui + "@" + uri |
| 166 | + |
| 167 | + return []string{dsn}, nil |
| 168 | +} |
0 commit comments