diff --git a/README.md b/README.md index a1b23c5..bcd59ad 100644 --- a/README.md +++ b/README.md @@ -692,6 +692,9 @@ databases: username: ${DB_USERNAME} ## Database password password: ${DB_PASSWORD} + ## Database password file + ## If specified, will load the database password from a file. + # passwordFile: ${DB_PASSWORD_FILE} ## Database connection url url: localhost:1521/freepdb1 @@ -729,6 +732,11 @@ databases: # label_name1: label_value1 # label_name2: label_value2 +# Optionally configure prometheus webserver +#web: +# listenAddresses: [':9161'] +# systemdSocket: true|false +# configFile: /path/to/webconfigfile metrics: ## How often to scrape metrics. If not provided, metrics will be scraped on request. diff --git a/changelog.md b/changelog.md index fdb3b09..a9d8f16 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,8 @@ Our current priorities are support for Exadata metrics. We expect to address these in an upcoming release. This release includes the following changes: +- Enable configuration of the prometheus webserver from the config file using the `web` prefix. +- Allow loading of database password(s) from a file. - Fixed a bug where database type (CDB, PDB, etc.) was not reported in certain situations. - Fixed a bug where literal passwords containing the '$' character (in the config file) would be evaluated as environment variables. To use literal passwords with the '$' character, escape the '$' character with a second '$': `$test$pwd` becomes `$$test$$pwd`. diff --git a/collector/config.go b/collector/config.go index 523d6f4..363b302 100644 --- a/collector/config.go +++ b/collector/config.go @@ -4,9 +4,11 @@ package collector import ( + "fmt" "github.com/godror/godror/dsn" "github.com/oracle/oracle-db-appdev-monitoring/azvault" "github.com/oracle/oracle-db-appdev-monitoring/ocivault" + "github.com/prometheus/exporter-toolkit/web" "gopkg.in/yaml.v2" "log/slog" "os" @@ -15,15 +17,24 @@ import ( ) type MetricsConfiguration struct { - MetricsPath string `yaml:"metricsPath"` - Databases map[string]DatabaseConfig `yaml:"databases"` - Metrics MetricsFilesConfig `yaml:"metrics"` - Logging LoggingConfig `yaml:"log"` + ListenAddress string `yaml:"listenAddress"` + MetricsPath string `yaml:"metricsPath"` + Databases map[string]DatabaseConfig `yaml:"databases"` + Metrics MetricsFilesConfig `yaml:"metrics"` + Logging LoggingConfig `yaml:"log"` + Web WebConfig `yaml:"web"` +} + +type WebConfig struct { + ListenAddresses *[]string `yaml:"listenAddresses"` + SystemdSocket *bool `yaml:"systemdSocket"` + ConfigFile *string `yaml:"configFile"` } type DatabaseConfig struct { Username string Password string + PasswordFile string `yaml:"passwordFile"` URL string `yaml:"url"` ConnectConfig `yaml:",inline"` Vault *VaultConfig `yaml:"vault,omitempty"` @@ -146,6 +157,14 @@ func (d DatabaseConfig) GetUsername() string { } func (d DatabaseConfig) GetPassword() string { + if d.PasswordFile != "" { + bytes, err := os.ReadFile(d.PasswordFile) + if err != nil { + // If there is an invalid file, exporter cannot continue processing. + panic(fmt.Errorf("failed to read password file: %v", err)) + } + return string(bytes) + } if d.isOCIVault() && d.Vault.OCI.PasswordSecret != "" { return ocivault.GetVaultSecret(d.Vault.OCI.ID, d.Vault.OCI.PasswordSecret) } @@ -163,7 +182,7 @@ func (d DatabaseConfig) isAzureVault() bool { return d.Vault != nil && d.Vault.Azure != nil } -func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*MetricsConfiguration, error) { +func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string, flags *web.FlagConfig) (*MetricsConfiguration, error) { m := &MetricsConfiguration{} if len(cfg.ConfigFile) > 0 { content, err := os.ReadFile(cfg.ConfigFile) @@ -186,14 +205,23 @@ func LoadMetricsConfiguration(logger *slog.Logger, cfg *Config, path string) (*M m.Databases["default"] = m.defaultDatabase(cfg) } - m.merge(cfg, path) + m.merge(cfg, path, flags) return m, nil } -func (m *MetricsConfiguration) merge(cfg *Config, path string) { +func (wc WebConfig) Flags() *web.FlagConfig { + return &web.FlagConfig{ + WebListenAddresses: wc.ListenAddresses, + WebSystemdSocket: wc.SystemdSocket, + WebConfigFile: wc.ConfigFile, + } +} + +func (m *MetricsConfiguration) merge(cfg *Config, path string, flags *web.FlagConfig) { if len(m.MetricsPath) == 0 { m.MetricsPath = path } + m.mergeWebConfig(flags) m.mergeLoggingConfig(cfg) m.mergeMetricsConfig(cfg) if m.Metrics.ScrapeInterval == nil { @@ -201,6 +229,18 @@ func (m *MetricsConfiguration) merge(cfg *Config, path string) { } } +func (m *MetricsConfiguration) mergeWebConfig(flags *web.FlagConfig) { + if m.Web.ListenAddresses == nil { + m.Web.ListenAddresses = flags.WebListenAddresses + } + if m.Web.SystemdSocket == nil { + m.Web.SystemdSocket = flags.WebSystemdSocket + } + if m.Web.ConfigFile == nil { + m.Web.ConfigFile = flags.WebConfigFile + } +} + func (m *MetricsConfiguration) mergeLoggingConfig(cfg *Config) { if m.Logging.LogDisable == nil { m.Logging.LogDisable = cfg.LoggingConfig.LogDisable diff --git a/collector/database.go b/collector/database.go index 403722f..c49db53 100644 --- a/collector/database.go +++ b/collector/database.go @@ -78,11 +78,11 @@ func (d *Database) constLabels(labels map[string]string) map[string]string { func NewDatabase(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *Database { db, dbtype := connect(logger, dbname, dbconfig) return &Database{ - Name: dbname, - Up: 0, - Session: db, - Type: dbtype, - Config: dbconfig, + Name: dbname, + Up: 0, + Session: db, + Type: dbtype, + Config: dbconfig, } } diff --git a/main.go b/main.go index 61f1751..3c0e9b7 100644 --- a/main.go +++ b/main.go @@ -105,12 +105,12 @@ func main() { LogDestination: *logDestination, }, } - m, err := collector.LoadMetricsConfiguration(logger, config, *metricPath) + m, err := collector.LoadMetricsConfiguration(logger, config, *metricPath, toolkitFlags) if err != nil { logger.Error("unable to load metrics configuration", "error", err) return } - + exporter := collector.NewExporter(logger, m) if exporter.ScrapeInterval() != 0 { ctx, cancel := context.WithCancel(context.Background()) @@ -194,7 +194,7 @@ func main() { // start the main server thread server := &http.Server{} - if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil { + if err := web.ListenAndServe(server, m.Web.Flags(), logger); err != nil { logger.Error("Listening error", "error", err) os.Exit(1) }