-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Maikel Poot <[email protected]>
- Loading branch information
1 parent
cc53157
commit c4cbed1
Showing
18 changed files
with
966 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2020 The Prometheus Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"gopkg.in/yaml.v3" | ||
"os" | ||
) | ||
|
||
var ( | ||
ErrorNoConfigFileGiven = errors.New("File path cannot be an empty string") | ||
) | ||
|
||
func index2human(index int) string { | ||
|
||
// Reduce value to last digit, with the exception for 11th,12th and 13th. | ||
// 22 => 2 => 22nd | ||
selector := index | ||
if index >= 14 { | ||
selector = index % 10 | ||
} | ||
|
||
switch selector { | ||
case 1: | ||
return fmt.Sprintf("%dst", index) | ||
case 2: | ||
return fmt.Sprintf("%dnd", index) | ||
case 3: | ||
return fmt.Sprintf("%drd", index) | ||
default: | ||
return fmt.Sprintf("%dth", index) | ||
} | ||
} | ||
|
||
type DuplicateCredentialsKeyError struct { | ||
message string | ||
index int | ||
first int | ||
} | ||
|
||
func (e DuplicateCredentialsKeyError) Error() string { | ||
return fmt.Sprintf("%s credential has duplicate key '%s' (already defined by %s credential)", index2human(e.index), e.message, index2human(e.first)) | ||
} | ||
|
||
func NewDefaultConfig() *Config { | ||
return &Config{ | ||
MetricsPath: "/metrics", | ||
ProbePath: "/probe", | ||
Credentials: make([]Credentials, 0), | ||
LegacyMode: true, | ||
MustConnectOnStartup: true, | ||
} | ||
} | ||
|
||
func (c *Config) ReadFromFile(path string) error { | ||
var err error | ||
var data []byte | ||
if path == "" { | ||
return ErrorNoConfigFileGiven | ||
} | ||
// Turn off legacyMode | ||
c.LegacyMode = false | ||
|
||
data, err = os.ReadFile(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = yaml.Unmarshal(data, c) | ||
if err != nil { | ||
return err | ||
} | ||
var credErr CredentialsErrorInterface | ||
keyCount := map[string]int{} | ||
for i, credential := range c.Credentials { | ||
if credErr = credential.Validate(); credErr != nil { | ||
credErr.SetIndex(i + 1) | ||
return credErr | ||
} | ||
if first, ok := keyCount[credential.GetKey()]; !ok { | ||
keyCount[credential.GetKey()] = i | ||
} else { | ||
return &DuplicateCredentialsKeyError{credential.GetKey(), i + 1, first + 1} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type Config struct { | ||
MetricsPath string `yaml:"metrics_path"` | ||
ProbePath string `yaml:"probe_path"` | ||
Credentials []Credentials `yaml:"credentials"` | ||
LegacyMode bool `yaml:"legacy_mode"` | ||
DSN string `yaml:"dsn"` | ||
PidFile string `yaml:"pid_file"` | ||
MustConnectOnStartup bool `yaml:"must_connect_on_startup"` | ||
} | ||
|
||
func (c *Config) GetCredentials(key string) (Credentials, error) { | ||
for _, cred := range c.Credentials { | ||
if cred.GetKey() == key { | ||
return cred, nil | ||
} | ||
} | ||
|
||
return Credentials{}, fmt.Errorf("credential %s not found", key) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
## metrics_path: /path/for/metrics; Default: /metrics | ||
metrics_path: /metrics | ||
## probe_path: /path/for/probe; Default: /probe | ||
probe_path: /probe | ||
|
||
## Turn on legacy mode, where the exporter scrapes a configured endpoint and exposes the metrics on the metrics_path | ||
## Defaults to false when using config file | ||
legacy_mode: false | ||
## dsn connection uri when using legacy_mode | ||
dsn: postgres://postgres:@localhost:6543/pgbouncer?sslmode=disable | ||
## pgbouncer pid file when using legacy_mode | ||
pid_file: | ||
## must_connect_on_startup: true|false; Default: true | ||
## If true the exporter will fail to start if any connection fails to connect within the startup fase. | ||
## If false the exporter will start even if some connections fail. | ||
must_connect_on_startup: false | ||
|
||
## Credentials for multi-target usage | ||
credentials: | ||
- key: monitoring # Optional, if key is not the username is used as credential key | ||
username: username | ||
password: password | ||
|
||
|
||
- username: sslstats # Optional, if key is not the username is used as credential key | ||
## SSL Connection parameters | ||
## for more info see the corresponding ssl* parameters in | ||
## https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE | ||
ssl: | ||
mode: "verify-full" # optional, Defaults to `prefer` | ||
cert: testdata/client.crt # path to the certificate file | ||
key: testdata/client.key # path to the private key file | ||
password: "" # required if private key is password protected | ||
compression: 1 # optional: 1 = enable, 0 = disable, default to `0` | ||
negotiation: "direct" # optional, defaults to `postgres` | ||
cert_mode: "require" # optional, defaults to `allow` | ||
root_cert: "system" # optional, path to allowed server CA's, or `system` for system's ca store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright 2024 The Prometheus Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific langu | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/google/go-cmp/cmp" | ||
"io/fs" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestDefaultConfig(t *testing.T) { | ||
|
||
config := NewDefaultConfig() | ||
|
||
MetricsPathWant := "/metrics" | ||
if config.MetricsPath != MetricsPathWant { | ||
t.Errorf("MetricsPath does not match. Want: %v, Got: %v", MetricsPathWant, config.MetricsPath) | ||
} | ||
|
||
ProbePathWant := "/probe" | ||
if config.ProbePath != ProbePathWant { | ||
t.Errorf("ProbePath does not match. Want: %v, Got: %v", ProbePathWant, config.ProbePath) | ||
} | ||
|
||
} | ||
|
||
func TestUnHappyFileConfig(t *testing.T) { | ||
|
||
config := NewDefaultConfig() | ||
var err error | ||
|
||
err = config.ReadFromFile("") | ||
if errors.Is(err, ErrorNoConfigFileGiven) == false { | ||
t.Errorf("config.ReadFromFile should return ErrorNoConfigFileGiven error. Got: %v", err) | ||
} | ||
|
||
err = config.ReadFromFile("./testdata/i-do-not-exist.yaml") | ||
if errors.Is(err, fs.ErrNotExist) == false { | ||
t.Errorf("config.ReadFromFile should return fs.ErrNotExist error. Got: %v", err) | ||
} | ||
|
||
err = config.ReadFromFile("./testdata/parse_error.yaml") | ||
if err != nil && strings.Contains(err.Error(), "yaml: line") == false { | ||
t.Errorf("config.ReadFromFile should return yaml parse error. Got: %v", err) | ||
} | ||
|
||
err = config.ReadFromFile("./testdata/duplicate_creds.yaml") | ||
var dcke *DuplicateCredentialsKeyError | ||
if errors.As(err, &dcke) == false { | ||
t.Errorf("config.ReadFromFile should return DuplicateCredentialsKeyError error. Got: %v", err) | ||
} | ||
|
||
err = config.ReadFromFile("./testdata/invalid_creds.yaml") | ||
var ce *CredentialsError | ||
if errors.As(err, &ce) == false { | ||
t.Errorf("config.ReadFromFile should return CredentialsError error. Got: %v", err) | ||
} else if err.(*CredentialsError).field != "ssl.key" { | ||
t.Errorf("config.ReadFromFile should return CredentialsError for field key. Got: %v", err) | ||
} | ||
|
||
for i, v := range map[int]string{2: "2nd", 3: "3rd", 4: "4th", 15: "15th", 20: "20th", 30: "30th"} { | ||
err = DuplicateCredentialsKeyError{message: "test", index: i, first: 1} | ||
want := fmt.Sprintf("%s credential has duplicate key 'test' (already defined by 1st credential)", v) | ||
if err.Error() != want { | ||
t.Errorf("DuplicateCredentialsKeyError did not return expected string. Want %v, Got: %v", want, err.Error()) | ||
} | ||
} | ||
|
||
} | ||
|
||
func TestFileConfig(t *testing.T) { | ||
|
||
config := NewDefaultConfig() | ||
var err error | ||
|
||
err = config.ReadFromFile("./testdata/config.yaml") | ||
if err != nil { | ||
t.Errorf("config.ReadFromFile() should not throw an error: %v", err) | ||
} | ||
|
||
MetricsPathWant := "/prom" | ||
if config.MetricsPath != MetricsPathWant { | ||
t.Errorf("MetricsPath does not match. Want: %v, Got: %v", MetricsPathWant, config.MetricsPath) | ||
} | ||
|
||
ProbePathWant := "/data" | ||
if config.ProbePath != ProbePathWant { | ||
t.Errorf("ProbePath does not match. Want: %v, Got: %v", ProbePathWant, config.ProbePath) | ||
} | ||
|
||
CredKeyWant := "cred_c" | ||
cred, err := config.GetCredentials(CredKeyWant) | ||
if err != nil { | ||
t.Errorf("config.GetCredentials() should not throw an error: %v", err) | ||
} | ||
if cred.GetKey() != "cred_c" { | ||
t.Errorf("Key of retreived credential does not match. Want: %v, Got: %v", CredKeyWant, cred.GetKey()) | ||
} | ||
|
||
_, err = config.GetCredentials("cred_d") | ||
if err == nil { | ||
t.Errorf("config.GetCredentials should return error. Got: %v", err) | ||
} | ||
|
||
credWants := []Credentials{ | ||
{ | ||
Key: "cred_a", | ||
Username: "user", | ||
Password: "pass", | ||
}, | ||
{ | ||
Key: "", | ||
Username: "cred_b", | ||
Password: "pass", | ||
}, | ||
} | ||
|
||
for i := range credWants { | ||
if cmp.Equal(config.Credentials[i], credWants[i]) == false { | ||
t.Errorf("Credentials config %d does not match. Want: %v, Got: %v", i, credWants[i], config.Credentials[i]) | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.