Skip to content

Commit

Permalink
Merge pull request #110 from Galorhallen/async
Browse files Browse the repository at this point in the history
Connection to Pi-Hole(s) handled in goroutine
  • Loading branch information
eko authored Feb 20, 2022
2 parents 5242c87 + c2437c8 commit 5ca2852
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 64 deletions.
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ You can run it using the following example and pass configuration environment va
$ docker run \
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
-e 'PIHOLE_PASSWORD=mypassword' \
-e 'INTERVAL=30s' \
-e 'PORT=9617' \
-p 9617:9617 \
ekofr/pihole-exporter:latest
Expand All @@ -56,7 +55,6 @@ $ API_TOKEN=$(awk -F= -v key="WEBPASSWORD" '$1==key {print $2}' /etc/pihole/setu
$ docker run \
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
-e "PIHOLE_API_TOKEN=$API_TOKEN" \
-e 'INTERVAL=30s' \
-e 'PORT=9617' \
ekofr/pihole-exporter:latest
```
Expand All @@ -69,7 +67,6 @@ $ docker run \
-e 'PIHOLE_PROTOCOL=https' \
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
-e 'PIHOLE_PASSWORD=mypassword' \
-e 'INTERVAL=30s' \
-e 'PORT=9617' \
-v '/etc/ssl/certs:/etc/ssl/certs:ro' \
-p 9617:9617 \
Expand All @@ -85,7 +82,6 @@ $ docker run \
-e 'PIHOLE_HOSTNAME="192.168.1.2,192.168.1.3,192.168.1.4"' \
-e "PIHOLE_API_TOKEN="$API_TOKEN1,$API_TOKEN2,$API_TOKEN3" \
-e "PIHOLE_PORT="8080,8081,8080" \
-e 'INTERVAL=30s' \
-e 'PORT=9617' \
ekofr/pihole-exporter:latest
```
Expand All @@ -98,7 +94,6 @@ $ docker run \
-e 'PIHOLE_HOSTNAME="192.168.1.2,192.168.1.3,192.168.1.4"' \
-e "PIHOLE_API_TOKEN="$API_TOKEN" \
-e "PIHOLE_PORT="8080" \
-e 'INTERVAL=30s' \
-e 'PORT=9617' \
ekofr/pihole-exporter:latest
```
Expand Down Expand Up @@ -147,7 +142,7 @@ $ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_api_token $API_TOKEN
2019/05/09 20:19:52 PIHoleHostname : 192.168.1.10
2019/05/09 20:19:52 PIHolePassword : azerty
2019/05/09 20:19:52 Port : 9617
2019/05/09 20:19:52 Interval : 10s
2019/05/09 20:19:52 Timeout : 5s
2019/05/09 20:19:52 ------------------------------------
2019/05/09 20:19:52 New Prometheus metric registered: domains_blocked
2019/05/09 20:19:52 New Prometheus metric registered: dns_queries_today
Expand Down Expand Up @@ -182,15 +177,14 @@ scrape_configs:
## Available CLI options
```bash
# Interval of time the exporter will fetch data from PI-Hole
-interval duration (optional) (default 10s)

# Hostname of the Raspberry PI where PI-Hole is installed
# Hostname of the host(s) where PI-Hole is installed
-pihole_hostname string (optional) (default "127.0.0.1")

# Password defined on the PI-Hole interface
-pihole_password string (optional)

# Timeout to connect and retrieve data from a Pi-Hole instance
-timeout duration (optional) (default 5s)

# WEBPASSWORD / api token defined on the PI-Hole interface at `/etc/pihole/setupVars.conf`
-pihole_api_token string (optional)
Expand Down
30 changes: 21 additions & 9 deletions config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"context"
"errors"
"fmt"
"log"
"reflect"
"strings"
"time"

log "github.com/sirupsen/logrus"

"github.com/heetch/confita"
"github.com/heetch/confita/backend"
"github.com/heetch/confita/backend/env"
Expand All @@ -31,7 +32,7 @@ type EnvConfig struct {
PIHolePassword []string `config:"pihole_password"`
PIHoleApiToken []string `config:"pihole_api_token"`
Port uint16 `config:"port"`
Interval time.Duration `config:"interval"`
Timeout time.Duration `config:"timeout"`
}

func getDefaultEnvConfig() *EnvConfig {
Expand All @@ -42,7 +43,7 @@ func getDefaultEnvConfig() *EnvConfig {
PIHolePassword: []string{},
PIHoleApiToken: []string{},
Port: 9617,
Interval: 10 * time.Second,
Timeout: 5 * time.Second,
}
}

Expand Down Expand Up @@ -85,6 +86,7 @@ func (c *Config) String() string {
}
}

buffer = removeEmptyString(buffer)
return fmt.Sprintf("<Config@%X %s>", &c, strings.Join(buffer, ", "))
}

Expand Down Expand Up @@ -156,6 +158,16 @@ func extractStringConfig(data []string, idx int, hostsCount int) (bool, string,
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 {
Expand All @@ -176,25 +188,25 @@ func (c Config) PIHoleLoginURL() string {

func (c EnvConfig) show() {
val := reflect.ValueOf(&c).Elem()
log.Println("------------------------------------")
log.Println("- PI-Hole exporter configuration -")
log.Println("------------------------------------")
log.Info("------------------------------------")
log.Info("- PI-Hole exporter configuration -")
log.Info("------------------------------------")
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" {
log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
log.Info(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
} else {
showAuthenticationMethod(typeField.Name, valueField.Len())
}
}
log.Println("------------------------------------")
log.Info("------------------------------------")
}

func showAuthenticationMethod(name string, length int) {
if length > 0 {
log.Println(fmt.Sprintf("Pi-Hole Authentication Method : %s", name))
log.Info(fmt.Sprintf("Pi-Hole Authentication Method : %s", name))
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.17
require (
github.com/heetch/confita v0.10.0
github.com/prometheus/client_golang v1.12.1
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.7.0
github.com/xonvanetta/shutdown v0.0.3
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
Expand All @@ -15,6 +16,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
Expand Down Expand Up @@ -278,6 +279,7 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
5 changes: 2 additions & 3 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package metrics

import (
"log"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)

var (
Expand Down Expand Up @@ -201,5 +200,5 @@ func Init() {

func initMetric(name string, metric *prometheus.GaugeVec) {
prometheus.MustRegister(metric)
log.Printf("New Prometheus metric registered: %s", name)
log.Info("New Prometheus metric registered: ", name)
}
64 changes: 43 additions & 21 deletions internal/pihole/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,58 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"

log "github.com/sirupsen/logrus"

"github.com/eko/pihole-exporter/config"
"github.com/eko/pihole-exporter/internal/metrics"
)

type ClientStatus byte

const (
MetricsCollectionInProgress ClientStatus = iota
MetricsCollectionSuccess
MetricsCollectionError
MetricsCollectionTimeout
)

func (status ClientStatus) String() string {
return []string{"MetricsCollectionInProgress", "MetricsCollectionSuccess", "MetricsCollectionError", "MetricsCollectionTimeout"}[status]
}

type ClientChannel struct {
Status ClientStatus
Err error
}

func (c *ClientChannel) String() string {
if c.Err != nil {
return fmt.Sprintf("ClientChannel<Status: %s, Err: '%s'>", c.Status, c.Err.Error())
} else {
return fmt.Sprintf("ClientChannel<Status: %s, Err: <nil>>", c.Status)
}
}

// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
type Client struct {
httpClient http.Client
interval time.Duration
config *config.Config
Status chan *ClientChannel
}

// NewClient method initializes a new PI-Hole client.
func NewClient(config *config.Config) *Client {
func NewClient(config *config.Config, envConfig *config.EnvConfig) *Client {
err := config.Validate()
if err != nil {
log.Print(err)
log.Error(err)
os.Exit(1)
}

Expand All @@ -39,40 +67,34 @@ func NewClient(config *config.Config) *Client {
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: envConfig.Timeout,
},
Status: make(chan *ClientChannel, 1),
}
}

func (c *Client) String() string {
return c.config.PIHoleHostname
}

/*
// Metrics scrapes pihole and sets them
func (c *Client) Metrics() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
stats, err := c.getStatistics()
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte(err.Error()))
return
}
func (c *Client) CollectMetricsAsync(writer http.ResponseWriter, request *http.Request) {
log.Printf("Collecting from %s", c.config.PIHoleHostname)
if stats, err := c.getStatistics(); err == nil {
c.setMetrics(stats)
log.Printf("New tick of statistics: %s", stats.ToString())
promhttp.Handler().ServeHTTP(writer, request)
c.Status <- &ClientChannel{Status: MetricsCollectionSuccess, Err: nil}
log.Printf("New tick of statistics from %s: %s", c.config.PIHoleHostname, stats)
} else {
c.Status <- &ClientChannel{Status: MetricsCollectionError, Err: err}
}
}*/
}

func (c *Client) CollectMetrics(writer http.ResponseWriter, request *http.Request) error {

stats, err := c.getStatistics()
if err != nil {
return err
}
c.setMetrics(stats)

log.Printf("New tick of statistics from %s: %s", c, stats)
log.Printf("New tick of statistics from %s: %s", c.config.PIHoleHostname, stats)
return nil
}

Expand Down Expand Up @@ -137,7 +159,7 @@ func (c *Client) getPHPSessionID() (sessionID string) {

resp, err := c.httpClient.Do(req)
if err != nil {
log.Printf("An error has occured during login to PI-Hole: %v", err)
log.Errorf("An error has occured during login to PI-Hole: %v", err)
}

for _, cookie := range resp.Cookies() {
Expand Down
Loading

0 comments on commit 5ca2852

Please sign in to comment.