Skip to content

Commit 5ca2852

Browse files
authored
Merge pull request #110 from Galorhallen/async
Connection to Pi-Hole(s) handled in goroutine
2 parents 5242c87 + c2437c8 commit 5ca2852

File tree

8 files changed

+115
-64
lines changed

8 files changed

+115
-64
lines changed

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ You can run it using the following example and pass configuration environment va
4343
$ docker run \
4444
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
4545
-e 'PIHOLE_PASSWORD=mypassword' \
46-
-e 'INTERVAL=30s' \
4746
-e 'PORT=9617' \
4847
-p 9617:9617 \
4948
ekofr/pihole-exporter:latest
@@ -56,7 +55,6 @@ $ API_TOKEN=$(awk -F= -v key="WEBPASSWORD" '$1==key {print $2}' /etc/pihole/setu
5655
$ docker run \
5756
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
5857
-e "PIHOLE_API_TOKEN=$API_TOKEN" \
59-
-e 'INTERVAL=30s' \
6058
-e 'PORT=9617' \
6159
ekofr/pihole-exporter:latest
6260
```
@@ -69,7 +67,6 @@ $ docker run \
6967
-e 'PIHOLE_PROTOCOL=https' \
7068
-e 'PIHOLE_HOSTNAME=192.168.1.2' \
7169
-e 'PIHOLE_PASSWORD=mypassword' \
72-
-e 'INTERVAL=30s' \
7370
-e 'PORT=9617' \
7471
-v '/etc/ssl/certs:/etc/ssl/certs:ro' \
7572
-p 9617:9617 \
@@ -85,7 +82,6 @@ $ docker run \
8582
-e 'PIHOLE_HOSTNAME="192.168.1.2,192.168.1.3,192.168.1.4"' \
8683
-e "PIHOLE_API_TOKEN="$API_TOKEN1,$API_TOKEN2,$API_TOKEN3" \
8784
-e "PIHOLE_PORT="8080,8081,8080" \
88-
-e 'INTERVAL=30s' \
8985
-e 'PORT=9617' \
9086
ekofr/pihole-exporter:latest
9187
```
@@ -98,7 +94,6 @@ $ docker run \
9894
-e 'PIHOLE_HOSTNAME="192.168.1.2,192.168.1.3,192.168.1.4"' \
9995
-e "PIHOLE_API_TOKEN="$API_TOKEN" \
10096
-e "PIHOLE_PORT="8080" \
101-
-e 'INTERVAL=30s' \
10297
-e 'PORT=9617' \
10398
ekofr/pihole-exporter:latest
10499
```
@@ -147,7 +142,7 @@ $ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_api_token $API_TOKEN
147142
2019/05/09 20:19:52 PIHoleHostname : 192.168.1.10
148143
2019/05/09 20:19:52 PIHolePassword : azerty
149144
2019/05/09 20:19:52 Port : 9617
150-
2019/05/09 20:19:52 Interval : 10s
145+
2019/05/09 20:19:52 Timeout : 5s
151146
2019/05/09 20:19:52 ------------------------------------
152147
2019/05/09 20:19:52 New Prometheus metric registered: domains_blocked
153148
2019/05/09 20:19:52 New Prometheus metric registered: dns_queries_today
@@ -182,15 +177,14 @@ scrape_configs:
182177
183178
## Available CLI options
184179
```bash
185-
# Interval of time the exporter will fetch data from PI-Hole
186-
-interval duration (optional) (default 10s)
187-
188-
# Hostname of the Raspberry PI where PI-Hole is installed
180+
# Hostname of the host(s) where PI-Hole is installed
189181
-pihole_hostname string (optional) (default "127.0.0.1")
190182

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

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

195189
# WEBPASSWORD / api token defined on the PI-Hole interface at `/etc/pihole/setupVars.conf`
196190
-pihole_api_token string (optional)

config/configuration.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
87
"reflect"
98
"strings"
109
"time"
1110

11+
log "github.com/sirupsen/logrus"
12+
1213
"github.com/heetch/confita"
1314
"github.com/heetch/confita/backend"
1415
"github.com/heetch/confita/backend/env"
@@ -31,7 +32,7 @@ type EnvConfig struct {
3132
PIHolePassword []string `config:"pihole_password"`
3233
PIHoleApiToken []string `config:"pihole_api_token"`
3334
Port uint16 `config:"port"`
34-
Interval time.Duration `config:"interval"`
35+
Timeout time.Duration `config:"timeout"`
3536
}
3637

3738
func getDefaultEnvConfig() *EnvConfig {
@@ -42,7 +43,7 @@ func getDefaultEnvConfig() *EnvConfig {
4243
PIHolePassword: []string{},
4344
PIHoleApiToken: []string{},
4445
Port: 9617,
45-
Interval: 10 * time.Second,
46+
Timeout: 5 * time.Second,
4647
}
4748
}
4849

@@ -85,6 +86,7 @@ func (c *Config) String() string {
8586
}
8687
}
8788

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

@@ -156,6 +158,16 @@ func extractStringConfig(data []string, idx int, hostsCount int) (bool, string,
156158
return false, "", true
157159
}
158160

161+
func removeEmptyString(source []string) []string {
162+
var result []string
163+
for _, s := range source {
164+
if s != "" {
165+
result = append(result, s)
166+
}
167+
}
168+
return result
169+
}
170+
159171
func (c Config) hostnameURL() string {
160172
s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname)
161173
if c.PIHolePort != 0 {
@@ -176,25 +188,25 @@ func (c Config) PIHoleLoginURL() string {
176188

177189
func (c EnvConfig) show() {
178190
val := reflect.ValueOf(&c).Elem()
179-
log.Println("------------------------------------")
180-
log.Println("- PI-Hole exporter configuration -")
181-
log.Println("------------------------------------")
191+
log.Info("------------------------------------")
192+
log.Info("- PI-Hole exporter configuration -")
193+
log.Info("------------------------------------")
182194
for i := 0; i < val.NumField(); i++ {
183195
valueField := val.Field(i)
184196
typeField := val.Type().Field(i)
185197

186198
// Do not print password or api token but do print the authentication method
187199
if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" {
188-
log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
200+
log.Info(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
189201
} else {
190202
showAuthenticationMethod(typeField.Name, valueField.Len())
191203
}
192204
}
193-
log.Println("------------------------------------")
205+
log.Info("------------------------------------")
194206
}
195207

196208
func showAuthenticationMethod(name string, length int) {
197209
if length > 0 {
198-
log.Println(fmt.Sprintf("Pi-Hole Authentication Method : %s", name))
210+
log.Info(fmt.Sprintf("Pi-Hole Authentication Method : %s", name))
199211
}
200212
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.17
55
require (
66
github.com/heetch/confita v0.10.0
77
github.com/prometheus/client_golang v1.12.1
8+
github.com/sirupsen/logrus v1.6.0
89
github.com/stretchr/testify v1.7.0
910
github.com/xonvanetta/shutdown v0.0.3
1011
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
@@ -15,6 +16,7 @@ require (
1516
github.com/cespare/xxhash/v2 v2.1.2 // indirect
1617
github.com/davecgh/go-spew v1.1.1 // indirect
1718
github.com/golang/protobuf v1.5.2 // indirect
19+
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
1820
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
1921
github.com/pkg/errors v0.9.1 // indirect
2022
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
199199
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
200200
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
201201
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
202+
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
202203
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
203204
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
204205
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -278,6 +279,7 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
278279
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
279280
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
280281
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
282+
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
281283
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
282284
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
283285
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

internal/metrics/metrics.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package metrics
22

33
import (
4-
"log"
5-
64
"github.com/prometheus/client_golang/prometheus"
5+
log "github.com/sirupsen/logrus"
76
)
87

98
var (
@@ -201,5 +200,5 @@ func Init() {
201200

202201
func initMetric(name string, metric *prometheus.GaugeVec) {
203202
prometheus.MustRegister(metric)
204-
log.Printf("New Prometheus metric registered: %s", name)
203+
log.Info("New Prometheus metric registered: ", name)
205204
}

internal/pihole/client.go

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,58 @@ import (
44
"encoding/json"
55
"fmt"
66
"io/ioutil"
7-
"log"
87
"net/http"
98
"net/url"
109
"os"
1110
"strconv"
1211
"strings"
1312
"time"
1413

14+
log "github.com/sirupsen/logrus"
15+
1516
"github.com/eko/pihole-exporter/config"
1617
"github.com/eko/pihole-exporter/internal/metrics"
1718
)
1819

20+
type ClientStatus byte
21+
22+
const (
23+
MetricsCollectionInProgress ClientStatus = iota
24+
MetricsCollectionSuccess
25+
MetricsCollectionError
26+
MetricsCollectionTimeout
27+
)
28+
29+
func (status ClientStatus) String() string {
30+
return []string{"MetricsCollectionInProgress", "MetricsCollectionSuccess", "MetricsCollectionError", "MetricsCollectionTimeout"}[status]
31+
}
32+
33+
type ClientChannel struct {
34+
Status ClientStatus
35+
Err error
36+
}
37+
38+
func (c *ClientChannel) String() string {
39+
if c.Err != nil {
40+
return fmt.Sprintf("ClientChannel<Status: %s, Err: '%s'>", c.Status, c.Err.Error())
41+
} else {
42+
return fmt.Sprintf("ClientChannel<Status: %s, Err: <nil>>", c.Status)
43+
}
44+
}
45+
1946
// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
2047
type Client struct {
2148
httpClient http.Client
2249
interval time.Duration
2350
config *config.Config
51+
Status chan *ClientChannel
2452
}
2553

2654
// NewClient method initializes a new PI-Hole client.
27-
func NewClient(config *config.Config) *Client {
55+
func NewClient(config *config.Config, envConfig *config.EnvConfig) *Client {
2856
err := config.Validate()
2957
if err != nil {
30-
log.Print(err)
58+
log.Error(err)
3159
os.Exit(1)
3260
}
3361

@@ -39,40 +67,34 @@ func NewClient(config *config.Config) *Client {
3967
CheckRedirect: func(req *http.Request, via []*http.Request) error {
4068
return http.ErrUseLastResponse
4169
},
70+
Timeout: envConfig.Timeout,
4271
},
72+
Status: make(chan *ClientChannel, 1),
4373
}
4474
}
4575

4676
func (c *Client) String() string {
4777
return c.config.PIHoleHostname
4878
}
4979

50-
/*
51-
// Metrics scrapes pihole and sets them
52-
func (c *Client) Metrics() http.HandlerFunc {
53-
return func(writer http.ResponseWriter, request *http.Request) {
54-
stats, err := c.getStatistics()
55-
if err != nil {
56-
writer.WriteHeader(http.StatusBadRequest)
57-
_, _ = writer.Write([]byte(err.Error()))
58-
return
59-
}
80+
func (c *Client) CollectMetricsAsync(writer http.ResponseWriter, request *http.Request) {
81+
log.Printf("Collecting from %s", c.config.PIHoleHostname)
82+
if stats, err := c.getStatistics(); err == nil {
6083
c.setMetrics(stats)
61-
62-
log.Printf("New tick of statistics: %s", stats.ToString())
63-
promhttp.Handler().ServeHTTP(writer, request)
84+
c.Status <- &ClientChannel{Status: MetricsCollectionSuccess, Err: nil}
85+
log.Printf("New tick of statistics from %s: %s", c.config.PIHoleHostname, stats)
86+
} else {
87+
c.Status <- &ClientChannel{Status: MetricsCollectionError, Err: err}
6488
}
65-
}*/
89+
}
6690

6791
func (c *Client) CollectMetrics(writer http.ResponseWriter, request *http.Request) error {
68-
6992
stats, err := c.getStatistics()
7093
if err != nil {
7194
return err
7295
}
7396
c.setMetrics(stats)
74-
75-
log.Printf("New tick of statistics from %s: %s", c, stats)
97+
log.Printf("New tick of statistics from %s: %s", c.config.PIHoleHostname, stats)
7698
return nil
7799
}
78100

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

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

143165
for _, cookie := range resp.Cookies() {

0 commit comments

Comments
 (0)