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

@ -15,29 +15,37 @@ import (
// Config is the exporter CLI configuration.
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"`
Port string `config:"port"`
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"`
}
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"`
}
func getDefaultConfig() *Config {
return &Config{
PIHoleProtocol: "http",
PIHoleHostname: "127.0.0.1",
PIHolePort: 80,
PIHolePassword: "",
PIHoleApiToken: "",
Port: "9617",
func getDefaultEnvConfig() *EnvConfig {
return &EnvConfig{
PIHoleProtocol: []string{"http"},
PIHoleHostname: []string{"127.0.0.1"},
PIHolePort: []uint16{80},
PIHolePassword: []string{},
PIHoleApiToken: []string{},
Port: 9617,
Interval: 10 * time.Second,
}
}
// Load method loads the configuration by using both flag or environment variables.
func Load() *Config {
func Load() (*EnvConfig, []Config) {
loaders := []backend.Backend{
env.NewBackend(),
flags.NewBackend(),
@ -45,7 +53,7 @@ func Load() *Config {
loader := confita.NewLoader(loaders...)
cfg := getDefaultConfig()
cfg := getDefaultEnvConfig()
err := loader.Load(context.Background(), cfg)
if err != nil {
panic(err)
@ -53,7 +61,7 @@ func Load() *Config {
cfg.show()
return cfg
return cfg, cfg.Split()
}
//Validate check if the config is valid
@ -64,6 +72,33 @@ func (c Config) Validate() error {
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 {
s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname)
if c.PIHolePort != 0 {
@ -82,7 +117,7 @@ func (c Config) PIHoleLoginURL() string {
return c.hostnameURL() + "/admin/index.php?login"
}
func (c Config) show() {
func (c EnvConfig) show() {
val := reflect.ValueOf(&c).Elem()
log.Println("------------------------------------")
log.Println("- PI-Hole exporter configuration -")
@ -95,14 +130,14 @@ func (c Config) show() {
if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" {
log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
} else {
showAuthenticationMethod(typeField.Name, valueField.String())
showAuthenticationMethod(typeField.Name, valueField.Len())
}
}
log.Println("------------------------------------")
}
func showAuthenticationMethod(name, value string) {
if len(value) > 0 {
func showAuthenticationMethod(name string, length int) {
if length > 0 {
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)
}
return &Client{
fmt.Printf("Creating client with config %s\n", config)
client := &Client{
config: config,
httpClient: http.Client{
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
@ -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) {
metrics.DomainsBlocked.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DomainsBeingBlocked))
metrics.DNSQueriesToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesToday))

View file

@ -1,8 +1,10 @@
package server
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/eko/pihole-exporter/internal/pihole"
@ -16,15 +18,29 @@ type Server struct {
// 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).
func NewServer(port string, client *pihole.Client) *Server {
func NewServer(port uint16, clients []*pihole.Client) *Server {
mux := http.NewServeMux()
httpServer := &http.Server{Addr: ":" + port, Handler: mux}
httpServer := &http.Server{Addr: ":" + strconv.Itoa(int(port)), Handler: mux}
s := &Server{
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("/liveness", s.livenessHandler())

11
main.go
View file

@ -11,12 +11,19 @@ import (
)
func main() {
conf := config.Load()
envConf, clientConfigs := config.Load()
metrics.Init()
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() {
s.ListenAndServe()
close(serverDead)