Add initial support to multiple pihole servers

This commit is contained in:
Galorhallen 2021-12-10 04:48:28 +01:00
parent 53aae16d90
commit fdbd1a678f
4 changed files with 112 additions and 27 deletions

View file

@ -20,24 +20,32 @@ type Config struct {
PIHolePort uint16 `config:"pihole_port"` PIHolePort uint16 `config:"pihole_port"`
PIHolePassword string `config:"pihole_password"` PIHolePassword string `config:"pihole_password"`
PIHoleApiToken string `config:"pihole_api_token"` PIHoleApiToken string `config:"pihole_api_token"`
Port string `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"`
Port uint16 `config:"port"`
Interval time.Duration `config:"interval"` Interval time.Duration `config:"interval"`
} }
func getDefaultConfig() *Config { func getDefaultEnvConfig() *EnvConfig {
return &Config{ return &EnvConfig{
PIHoleProtocol: "http", PIHoleProtocol: []string{"http"},
PIHoleHostname: "127.0.0.1", PIHoleHostname: []string{"127.0.0.1"},
PIHolePort: 80, PIHolePort: []uint16{80},
PIHolePassword: "", PIHolePassword: []string{},
PIHoleApiToken: "", PIHoleApiToken: []string{},
Port: "9617", Port: 9617,
Interval: 10 * time.Second, Interval: 10 * time.Second,
} }
} }
// Load method loads the configuration by using both flag or environment variables. // Load method loads the configuration by using both flag or environment variables.
func Load() *Config { func Load() (*EnvConfig, []Config) {
loaders := []backend.Backend{ loaders := []backend.Backend{
env.NewBackend(), env.NewBackend(),
flags.NewBackend(), flags.NewBackend(),
@ -45,7 +53,7 @@ func Load() *Config {
loader := confita.NewLoader(loaders...) loader := confita.NewLoader(loaders...)
cfg := getDefaultConfig() cfg := getDefaultEnvConfig()
err := loader.Load(context.Background(), cfg) err := loader.Load(context.Background(), cfg)
if err != nil { if err != nil {
panic(err) panic(err)
@ -53,7 +61,7 @@ func Load() *Config {
cfg.show() cfg.show()
return cfg return cfg, cfg.Split()
} }
//Validate check if the config is valid //Validate check if the config is valid
@ -64,6 +72,33 @@ func (c Config) Validate() error {
return nil return nil
} }
func (c EnvConfig) Split() []Config {
result := make([]Config, 0, len(c.PIHoleHostname))
for i, hostname := range c.PIHoleHostname {
config := Config{
PIHoleHostname: hostname,
PIHoleProtocol: c.PIHoleProtocol[i],
PIHolePort: c.PIHolePort[i],
}
if c.PIHoleApiToken != nil && len(c.PIHoleApiToken) > 0 {
if c.PIHoleApiToken[i] != "" {
config.PIHoleApiToken = c.PIHoleApiToken[i]
}
}
if c.PIHolePassword != nil && len(c.PIHolePassword) > 0 {
if c.PIHolePassword[i] != "" {
config.PIHolePassword = c.PIHolePassword[i]
}
}
result = append(result, config)
}
return result
}
func (c Config) hostnameURL() string { func (c Config) hostnameURL() string {
s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname) s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname)
if c.PIHolePort != 0 { if c.PIHolePort != 0 {
@ -82,7 +117,7 @@ func (c Config) PIHoleLoginURL() string {
return c.hostnameURL() + "/admin/index.php?login" return c.hostnameURL() + "/admin/index.php?login"
} }
func (c Config) show() { func (c EnvConfig) show() {
val := reflect.ValueOf(&c).Elem() val := reflect.ValueOf(&c).Elem()
log.Println("------------------------------------") log.Println("------------------------------------")
log.Println("- PI-Hole exporter configuration -") log.Println("- PI-Hole exporter configuration -")
@ -95,14 +130,14 @@ func (c Config) show() {
if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" { if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" {
log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface())) log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
} else { } else {
showAuthenticationMethod(typeField.Name, valueField.String()) showAuthenticationMethod(typeField.Name, valueField.Len())
} }
} }
log.Println("------------------------------------") log.Println("------------------------------------")
} }
func showAuthenticationMethod(name, value string) { func showAuthenticationMethod(name string, length int) {
if len(value) > 0 { if length > 0 {
log.Println(fmt.Sprintf("Pi-Hole Authentication Method : %s", name)) log.Println(fmt.Sprintf("Pi-Hole Authentication Method : %s", name))
} }
} }

View file

@ -32,7 +32,9 @@ func NewClient(config *config.Config) *Client {
os.Exit(1) os.Exit(1)
} }
return &Client{ fmt.Printf("Creating client with config %s\n", config)
client := &Client{
config: config, config: config,
httpClient: http.Client{ httpClient: http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
@ -40,6 +42,14 @@ func NewClient(config *config.Config) *Client {
}, },
}, },
} }
fmt.Printf("Client created with config %s\n", client)
return client
}
func (c *Client) String() string {
return c.config.PIHoleHostname
} }
// Metrics scrapes pihole and sets them // Metrics scrapes pihole and sets them
@ -58,6 +68,23 @@ func (c *Client) Metrics() http.HandlerFunc {
} }
} }
func (c *Client) CollectMetrics(writer http.ResponseWriter, request *http.Request) {
stats, err := c.getStatistics()
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte(err.Error()))
return
}
c.setMetrics(stats)
log.Printf("New tick of statistics: %s", stats.ToString())
promhttp.Handler().ServeHTTP(writer, request)
}
func (c *Client) GetHostname() string {
return c.config.PIHoleHostname
}
func (c *Client) setMetrics(stats *Stats) { func (c *Client) setMetrics(stats *Stats) {
metrics.DomainsBlocked.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DomainsBeingBlocked)) metrics.DomainsBlocked.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DomainsBeingBlocked))
metrics.DNSQueriesToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesToday)) metrics.DNSQueriesToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesToday))

View file

@ -1,8 +1,10 @@
package server package server
import ( import (
"fmt"
"log" "log"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/eko/pihole-exporter/internal/pihole" "github.com/eko/pihole-exporter/internal/pihole"
@ -16,15 +18,29 @@ type Server struct {
// NewServer method initializes a new HTTP server instance and associates // NewServer method initializes a new HTTP server instance and associates
// the different routes that will be used by Prometheus (metrics) or for monitoring (readiness, liveness). // the different routes that will be used by Prometheus (metrics) or for monitoring (readiness, liveness).
func NewServer(port string, client *pihole.Client) *Server { func NewServer(port uint16, clients []*pihole.Client) *Server {
mux := http.NewServeMux() mux := http.NewServeMux()
httpServer := &http.Server{Addr: ":" + port, Handler: mux} httpServer := &http.Server{Addr: ":" + strconv.Itoa(int(port)), Handler: mux}
s := &Server{ s := &Server{
httpServer: httpServer, httpServer: httpServer,
} }
mux.Handle("/metrics", client.Metrics()) /*fmt.Printf("Server received clients -> %s\n", clients)
for i, client := range clients {
fmt.Printf("Server received clients -> idx: %d, Hostname: %s\n", i, &client)
}*/
mux.HandleFunc("/metrics",
func(writer http.ResponseWriter, request *http.Request) {
for i, client := range clients {
fmt.Printf("Idx: %d, Hostname: %s\n", i, client)
client.CollectMetrics(writer, request)
}
},
)
//mux.Handle("/metrics", client.Metrics())
mux.Handle("/readiness", s.readinessHandler()) mux.Handle("/readiness", s.readinessHandler())
mux.Handle("/liveness", s.livenessHandler()) mux.Handle("/liveness", s.livenessHandler())

11
main.go
View file

@ -11,12 +11,19 @@ import (
) )
func main() { func main() {
conf := config.Load() envConf, clientConfigs := config.Load()
metrics.Init() metrics.Init()
serverDead := make(chan struct{}) serverDead := make(chan struct{})
s := server.NewServer(conf.Port, pihole.NewClient(conf)) clients := make([]*pihole.Client, 0, len(clientConfigs))
for i, _ := range clientConfigs {
client := pihole.NewClient(&clientConfigs[i])
clients = append(clients, client)
fmt.Printf("Append client %s\n", clients)
}
s := server.NewServer(envConf.Port, clients)
go func() { go func() {
s.ListenAndServe() s.ListenAndServe()
close(serverDead) close(serverDead)