Add initial support to multiple pihole servers
This commit is contained in:
parent
53aae16d90
commit
fdbd1a678f
4 changed files with 112 additions and 27 deletions
|
@ -15,29 +15,37 @@ import (
|
||||||
|
|
||||||
// Config is the exporter CLI configuration.
|
// Config is the exporter CLI configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
PIHoleProtocol string `config:"pihole_protocol"`
|
PIHoleProtocol string `config:"pihole_protocol"`
|
||||||
PIHoleHostname string `config:"pihole_hostname"`
|
PIHoleHostname string `config:"pihole_hostname"`
|
||||||
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
11
main.go
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue