drive-health-metrics/config.go
James Coleman ddafa90a02
Some checks failed
Go package / build (push) Has been cancelled
first commit
2026-06-22 17:16:34 -05:00

131 lines
4 KiB
Go

package main
import (
"log"
"os"
"os/user"
"path"
"path/filepath"
"time"
"github.com/kkyr/fig"
)
// Config is the service-mode configuration, loaded from YAML (via fig) and
// overridable by flags. It only governs the output exporters; drive discovery
// and SMART collection auto-detect their tools and need no configuration.
type Config struct {
// Hostname is used as the host tag/label on every metric. When empty it is
// resolved from the system hostname.
Hostname string `fig:"hostname"`
// Metric outputs.
HTTP HTTPOutputConfig `fig:"http_output"`
Influx InfluxOutputConfig `fig:"influx_output"`
}
// HTTPOutputConfig configures the Prometheus HTTP exporter.
type HTTPOutputConfig struct {
Enabled bool `fig:"enabled"`
BindAddr string `fig:"bind_addr"`
Port uint `fig:"port"`
MetricsPath string `fig:"metrics_path"`
}
// InfluxOutputConfig configures the scheduled InfluxDB output. Metrics are
// pushed every Frequency to InfluxDB's v2 API and/or to Kafka. A zero Frequency
// (or no destination configured) disables the output.
type InfluxOutputConfig struct {
Frequency time.Duration `fig:"frequency"`
KafkaBrokers []string `fig:"kafka_brokers"`
KafkaTopic string `fig:"kafka_topic"`
KafkaUsername string `fig:"kafka_username"`
KafkaPassword string `fig:"kafka_password"`
KafkaInsecureSkipVerify bool `fig:"kafka_insecure_skip_verify"`
KafkaOutputFormat string `fig:"kafka_output_format"` // lineprotocol (default) or json.
InfluxServer string `fig:"influx_server"`
Token string `fig:"token"`
Org string `fig:"org"`
Bucket string `fig:"bucket"`
}
// defaultConfig returns the configuration with all defaults applied, used as the
// base before a file (if any) is loaded over it.
func defaultConfig() *Config {
return &Config{
HTTP: HTTPOutputConfig{
Enabled: true,
Port: 9101,
MetricsPath: "/metrics",
},
Influx: InfluxOutputConfig{
KafkaOutputFormat: "lineprotocol",
},
}
}
// findConfigFile returns the first configuration file that exists, preferring
// the -config flag (configPath), then a local file, the user config dir, and
// finally /etc. It returns "" when none is found — configuration is optional.
func findConfigFile(configPath string) string {
if configPath != "" {
if _, err := os.Stat(configPath); err == nil {
return configPath
}
log.Printf("Configured config path %q not found, falling back to defaults", configPath)
}
candidates := []string{}
if local, err := filepath.Abs("./config.yaml"); err == nil {
candidates = append(candidates, local)
}
if usr, err := user.Current(); err == nil {
candidates = append(candidates, usr.HomeDir+"/.config/drive-health-metrics/config.yaml")
}
candidates = append(candidates, "/etc/drive-health-metrics.yaml")
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
return c
}
}
return ""
}
// ReadConfig loads the configuration into app.config. It always succeeds with a
// usable config: a file is loaded over the defaults when present, flag overrides
// are applied, and the host tag is resolved when unset.
func (a *App) ReadConfig() {
config := defaultConfig()
// Load a configuration file over the defaults when one is available.
if configFile := findConfigFile(a.flags.ConfigPath); configFile != "" {
dir, name := path.Split(configFile)
if dir == "" {
dir = "."
}
if err := fig.Load(config, fig.File(name), fig.Dirs(dir)); err != nil {
log.Printf("Error parsing configuration %q: %s", configFile, err)
}
}
// Resolve the host tag from the system when not configured.
if config.Hostname == "" {
config.Hostname = hostname()
}
// Flag overrides for the HTTP output.
if a.flags.HTTPBind != "" {
config.HTTP.BindAddr = a.flags.HTTPBind
}
if a.flags.HTTPPort != 0 {
config.HTTP.Port = a.flags.HTTPPort
}
if a.flags.HTTPMetricsPath != "" {
config.HTTP.MetricsPath = a.flags.HTTPMetricsPath
}
a.config = config
}