pihole-exporter/config/configuration.go

219 lines
6.4 KiB
Go
Raw Permalink Normal View History

2019-05-08 23:45:04 +02:00
package config
import (
"context"
2021-12-26 23:36:30 +01:00
"errors"
2019-05-08 23:45:04 +02:00
"fmt"
"reflect"
2022-10-25 09:43:34 +02:00
"runtime"
2021-12-10 15:39:22 +01:00
"strings"
2019-05-08 23:45:04 +02:00
"time"
2022-01-05 20:38:33 +01:00
log "github.com/sirupsen/logrus"
2019-05-08 23:45:04 +02:00
"github.com/heetch/confita"
"github.com/heetch/confita/backend"
"github.com/heetch/confita/backend/env"
"github.com/heetch/confita/backend/flags"
)
2019-05-09 21:11:31 +02:00
// Config is the exporter CLI configuration.
2019-05-08 23:45:04 +02:00
type Config struct {
PIHoleProtocol string `config:"pihole_protocol"`
PIHoleHostname string `config:"pihole_hostname"`
PIHolePort uint16 `config:"pihole_port"`
PIHolePassword string `config:"pihole_password"`
PIHoleApiToken string `config:"pihole_api_token"`
BindAddr string `config:"bind_addr"`
Port uint16 `config:"port"`
}
type EnvConfig struct {
PIHoleProtocol []string `config:"pihole_protocol"`
PIHoleHostname []string `config:"pihole_hostname"`
PIHolePort []uint16 `config:"pihole_port"`
PIHolePassword []string `config:"pihole_password"`
PIHoleApiToken []string `config:"pihole_api_token"`
BindAddr string `config:"bind_addr"`
Port uint16 `config:"port"`
Timeout time.Duration `config:"timeout"`
2019-05-08 23:45:04 +02:00
}
func getDefaultEnvConfig() *EnvConfig {
return &EnvConfig{
PIHoleProtocol: []string{"http"},
PIHoleHostname: []string{"127.0.0.1"},
PIHolePort: []uint16{80},
PIHolePassword: []string{},
PIHoleApiToken: []string{},
BindAddr: "0.0.0.0",
Port: 9617,
Timeout: 5 * time.Second,
2019-05-08 23:45:04 +02:00
}
}
2019-05-09 21:11:31 +02:00
// Load method loads the configuration by using both flag or environment variables.
2021-12-26 23:36:30 +01:00
func Load() (*EnvConfig, []Config, error) {
2019-05-08 23:45:04 +02:00
loaders := []backend.Backend{
env.NewBackend(),
flags.NewBackend(),
}
loader := confita.NewLoader(loaders...)
cfg := getDefaultEnvConfig()
2019-05-08 23:45:04 +02:00
err := loader.Load(context.Background(), cfg)
if err != nil {
panic(err)
}
cfg.show()
2021-12-26 23:36:30 +01:00
if clientsConfig, err := cfg.Split(); err != nil {
return cfg, nil, err
} else {
return cfg, clientsConfig, nil
}
2019-05-08 23:45:04 +02:00
}
2021-12-10 15:39:22 +01:00
func (c *Config) String() string {
ref := reflect.ValueOf(c)
fields := ref.Elem()
buffer := make([]string, fields.NumField(), fields.NumField())
for i := 0; i < fields.NumField(); i++ {
valueField := fields.Field(i)
typeField := fields.Type().Field(i)
if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" {
buffer[i] = fmt.Sprintf("%s=%v", typeField.Name, valueField.Interface())
2022-01-07 22:54:02 +01:00
} else if valueField.Len() > 0 {
2022-01-07 22:55:31 +01:00
buffer[i] = fmt.Sprintf("%s=%s", typeField.Name, "*****")
}
2021-12-10 15:39:22 +01:00
}
buffer = removeEmptyString(buffer)
2021-12-10 15:39:22 +01:00
return fmt.Sprintf("<Config@%X %s>", &c, strings.Join(buffer, ", "))
}
2022-10-25 09:43:34 +02:00
// Validate check if the config is valid
func (c Config) Validate() error {
if c.PIHoleProtocol != "http" && c.PIHoleProtocol != "https" {
return fmt.Errorf("protocol %s is invalid. Must be http or https", c.PIHoleProtocol)
}
return nil
}
2021-12-26 23:36:30 +01:00
func (c EnvConfig) Split() ([]Config, error) {
hostsCount := len(c.PIHoleHostname)
result := make([]Config, 0, hostsCount)
for i, hostname := range c.PIHoleHostname {
config := Config{
PIHoleHostname: strings.TrimSpace(hostname),
}
2021-12-26 23:36:30 +01:00
if len(c.PIHolePort) == 1 {
config.PIHolePort = c.PIHolePort[0]
} else if len(c.PIHolePort) == hostsCount {
config.PIHolePort = c.PIHolePort[i]
} else if len(c.PIHolePort) != 0 {
return nil, errors.New("Wrong number of ports. Port can be empty to use default, one value to use for all hosts, or match the number of hosts")
}
2021-12-26 23:36:30 +01:00
if hasData, data, isValid := extractStringConfig(c.PIHoleProtocol, i, hostsCount); hasData {
config.PIHoleProtocol = data
} else if !isValid {
return nil, errors.New("Wrong number of PIHoleProtocol. PIHoleProtocol can be empty to use default, one value to use for all hosts, or match the number of hosts")
}
2021-12-26 23:36:30 +01:00
if hasData, data, isValid := extractStringConfig(c.PIHoleApiToken, i, hostsCount); hasData {
config.PIHoleApiToken = data
2021-12-26 23:36:30 +01:00
} else if !isValid {
return nil, errors.New(fmt.Sprintf("Wrong number of PIHoleApiToken %d (Hosts: %d). PIHoleApiToken can be empty to use default, one value to use for all hosts, or match the number of hosts", len(c.PIHoleApiToken), hostsCount))
}
2021-12-26 23:36:30 +01:00
if hasData, data, isValid := extractStringConfig(c.PIHolePassword, i, hostsCount); hasData {
config.PIHolePassword = data
2021-12-26 23:36:30 +01:00
} else if !isValid {
return nil, errors.New("Wrong number of PIHolePassword. PIHolePassword can be empty to use default, one value to use for all hosts, or match the number of hosts")
}
result = append(result, config)
}
2021-12-26 23:36:30 +01:00
return result, nil
}
2021-12-26 23:36:30 +01:00
func extractStringConfig(data []string, idx int, hostsCount int) (bool, string, bool) {
if len(data) == 1 {
v := strings.TrimSpace(data[0])
if v != "" {
2021-12-26 23:36:30 +01:00
return true, v, true
}
2021-12-26 23:36:30 +01:00
} else if len(data) == hostsCount {
v := strings.TrimSpace(data[idx])
if v != "" {
2021-12-26 23:36:30 +01:00
return true, v, true
}
2021-12-26 23:36:30 +01:00
} else if len(data) != 0 { //Host count missmatch
return false, "", false
}
2021-12-26 23:36:30 +01:00
// Empty
return false, "", true
}
func removeEmptyString(source []string) []string {
var result []string
for _, s := range source {
if s != "" {
result = append(result, s)
}
}
return result
}
func (c Config) hostnameURL() string {
s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname)
if c.PIHolePort != 0 {
s += fmt.Sprintf(":%d", c.PIHolePort)
}
return s
}
2022-10-25 09:43:34 +02:00
// PIHoleStatsURL returns the stats url
func (c Config) PIHoleStatsURL() string {
return c.hostnameURL() + "/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&jsonForceObject"
}
2022-10-25 09:43:34 +02:00
// PIHoleLoginURL returns the login url
func (c Config) PIHoleLoginURL() string {
return c.hostnameURL() + "/admin/index.php?login"
}
func (c EnvConfig) show() {
2019-05-08 23:45:04 +02:00
val := reflect.ValueOf(&c).Elem()
2022-01-05 20:38:33 +01:00
log.Info("------------------------------------")
2023-07-26 09:15:44 +02:00
log.Info("- Pi-hole exporter configuration -")
2022-01-05 20:38:33 +01:00
log.Info("------------------------------------")
2022-10-25 09:53:37 +02:00
log.Info("Go version: ", runtime.Version())
2019-05-08 23:45:04 +02:00
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
// Do not print password or api token but do print the authentication method
if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" {
2022-01-05 20:38:33 +01:00
log.Info(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
} else {
showAuthenticationMethod(typeField.Name, valueField.Len())
}
2019-05-08 23:45:04 +02:00
}
2022-01-05 20:38:33 +01:00
log.Info("------------------------------------")
2019-05-08 23:45:04 +02:00
}
func showAuthenticationMethod(name string, length int) {
if length > 0 {
2023-07-26 09:15:44 +02:00
log.Info(fmt.Sprintf("Pi-hole Authentication Method : %s", name))
}
}