Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection to Pi-Hole(s) handled in goroutine #110

Merged
merged 19 commits into from
Feb 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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