1
0
mirror of https://github.com/prometheus/mysqld_exporter.git synced 2025-07-09 18:21:43 +03:00
Files
mysqld_exporter/config/config.go
TJ Hoplock be5dc65671 chore!: adopt log/slog, drop go-kit/log (#875)
* chore!: adopt log/slog, drop go-kit/log

Requires: prometheus/common#697

This PR includes:
- linter updates to enable `sloglint` linter
- Go dep updates for prometheus/{client_golang,common,exporter-toolkit}
  libs
- refactorings to adopt log/slog in favor of go-kit/log

The bulk of this PR was automated by the following script which is being
used to aid in converting the various exporters/projects to use slog:

https://gist.github.com/tjhop/49f96fb7ebbe55b12deee0b0312d8434

Builds and passes tests locally with go workspaces and up-to-date main
branch of prometheus/common.

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>

* build(deps): bump prometheus/common to v0.60.0

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>

---------

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
2024-10-10 18:04:21 +02:00

237 lines
6.0 KiB
Go

// Copyright 2022 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 config
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log/slog"
"net"
"os"
"strconv"
"strings"
"sync"
"github.com/go-sql-driver/mysql"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/ini.v1"
)
var (
configReloadSuccess = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "mysqld_exporter",
Name: "config_last_reload_successful",
Help: "Mysqld exporter config loaded successfully.",
})
configReloadSeconds = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "mysqld_exporter",
Name: "config_last_reload_success_timestamp_seconds",
Help: "Timestamp of the last successful configuration reload.",
})
opts = ini.LoadOptions{
// Do not error on nonexistent file to allow empty string as filename input
Loose: true,
// MySQL ini file can have boolean keys.
AllowBooleanKeys: true,
}
err error
)
type Config struct {
Sections map[string]MySqlConfig
}
type MySqlConfig struct {
User string `ini:"user"`
Password string `ini:"password"`
Host string `ini:"host"`
Port int `ini:"port"`
Socket string `ini:"socket"`
SslCa string `ini:"ssl-ca"`
SslCert string `ini:"ssl-cert"`
SslKey string `ini:"ssl-key"`
TlsInsecureSkipVerify bool `ini:"ssl-skip-verfication"` //nolint:misspell
Tls string `ini:"tls"`
}
type MySqlConfigHandler struct {
sync.RWMutex
TlsInsecureSkipVerify bool
Config *Config
}
func (ch *MySqlConfigHandler) GetConfig() *Config {
ch.RLock()
defer ch.RUnlock()
return ch.Config
}
func (ch *MySqlConfigHandler) ReloadConfig(filename string, mysqldAddress string, mysqldUser string, tlsInsecureSkipVerify bool, logger *slog.Logger) error {
var host, port string
defer func() {
if err != nil {
configReloadSuccess.Set(0)
} else {
configReloadSuccess.Set(1)
configReloadSeconds.SetToCurrentTime()
}
}()
cfg, err := ini.LoadSources(
opts,
[]byte("[client]\npassword = ${MYSQLD_EXPORTER_PASSWORD}\n"),
filename,
)
if err != nil {
return fmt.Errorf("failed to load config from %s: %w", filename, err)
}
if host, port, err = net.SplitHostPort(mysqldAddress); err != nil {
return fmt.Errorf("failed to parse address: %w", err)
}
if clientSection := cfg.Section("client"); clientSection != nil {
if cfgHost := clientSection.Key("host"); cfgHost.String() == "" {
cfgHost.SetValue(host)
}
if cfgPort := clientSection.Key("port"); cfgPort.String() == "" {
cfgPort.SetValue(port)
}
if cfgUser := clientSection.Key("user"); cfgUser.String() == "" {
cfgUser.SetValue(mysqldUser)
}
}
cfg.ValueMapper = os.ExpandEnv
config := &Config{}
m := make(map[string]MySqlConfig)
for _, sec := range cfg.Sections() {
sectionName := sec.Name()
if sectionName == "DEFAULT" {
continue
}
mysqlcfg := &MySqlConfig{
TlsInsecureSkipVerify: tlsInsecureSkipVerify,
}
err = sec.StrictMapTo(mysqlcfg)
if err != nil {
logger.Error("failed to parse config", "section", sectionName, "err", err)
continue
}
if err := mysqlcfg.validateConfig(); err != nil {
logger.Error("failed to validate config", "section", sectionName, "err", err)
continue
}
m[sectionName] = *mysqlcfg
}
config.Sections = m
if len(config.Sections) == 0 {
return fmt.Errorf("no configuration found")
}
ch.Lock()
ch.Config = config
ch.Unlock()
return nil
}
func (m MySqlConfig) validateConfig() error {
if m.User == "" {
return fmt.Errorf("no user specified in section or parent")
}
return nil
}
func (m MySqlConfig) FormDSN(target string) (string, error) {
config := mysql.NewConfig()
config.User = m.User
config.Passwd = m.Password
config.Net = "tcp"
if target == "" {
if m.Socket == "" {
host := "127.0.0.1"
if m.Host != "" {
host = m.Host
}
port := "3306"
if m.Port != 0 {
port = strconv.Itoa(m.Port)
}
config.Addr = net.JoinHostPort(host, port)
} else {
config.Net = "unix"
config.Addr = m.Socket
}
} else if prefix := "unix://"; strings.HasPrefix(target, prefix) {
config.Net = "unix"
config.Addr = target[len(prefix):]
} else {
if _, _, err = net.SplitHostPort(target); err != nil {
return "", fmt.Errorf("failed to parse target: %s", err)
}
config.Addr = target
}
if m.TlsInsecureSkipVerify {
config.TLSConfig = "skip-verify"
} else {
config.TLSConfig = m.Tls
if m.SslCa != "" {
if err := m.CustomizeTLS(); err != nil {
err = fmt.Errorf("failed to register a custom TLS configuration for mysql dsn: %w", err)
return "", err
}
config.TLSConfig = "custom"
}
}
return config.FormatDSN(), nil
}
func (m MySqlConfig) CustomizeTLS() error {
var tlsCfg tls.Config
caBundle := x509.NewCertPool()
pemCA, err := os.ReadFile(m.SslCa)
if err != nil {
return err
}
if ok := caBundle.AppendCertsFromPEM(pemCA); ok {
tlsCfg.RootCAs = caBundle
} else {
return fmt.Errorf("failed parse pem-encoded CA certificates from %s", m.SslCa)
}
if m.SslCert != "" && m.SslKey != "" {
certPairs := make([]tls.Certificate, 0, 1)
keypair, err := tls.LoadX509KeyPair(m.SslCert, m.SslKey)
if err != nil {
return fmt.Errorf("failed to parse pem-encoded SSL cert %s or SSL key %s: %w",
m.SslCert, m.SslKey, err)
}
certPairs = append(certPairs, keypair)
tlsCfg.Certificates = certPairs
}
tlsCfg.InsecureSkipVerify = m.TlsInsecureSkipVerify
mysql.RegisterTLSConfig("custom", &tlsCfg)
return nil
}