You've already forked mysqld_exporter
mirror of
https://github.com/prometheus/mysqld_exporter.git
synced 2025-07-09 18:21:43 +03:00
* 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>
237 lines
6.0 KiB
Go
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
|
|
}
|