Initialize
This commit is contained in:
commit
7457599d3e
11 changed files with 563 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
search-api
|
||||
bin
|
||||
tmp
|
||||
.vscode
|
||||
report.xml
|
||||
debug
|
||||
.idea/
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2019 Vincent Composieux <github@composieux.fr>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
46
README.md
Normal file
46
README.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# PI-Hole Prometheus Exporter
|
||||
|
||||
This is a Prometheus exporter for [PI-Hole](https://pi-hole.net/)'s Raspberry PI ad blocker.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* [Go](https://golang.org/doc/)
|
||||
|
||||
## Installation
|
||||
|
||||
### Manually
|
||||
|
||||
First, retrieve the project:
|
||||
```bash
|
||||
$ go get -u github.com/eko/pihole-exporter
|
||||
# or
|
||||
$ git clone https://github.com/eko/pihole-exporter.git
|
||||
```
|
||||
|
||||
Then, build the binary:
|
||||
```bash
|
||||
$ GOOS=linux GOARCH=arm GOARM=7 go build -o pihole_exporter .
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
In order to run the exporter, type the following command (arguments are optional):
|
||||
|
||||
```bash
|
||||
$ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_password azerty
|
||||
```
|
||||
|
||||
## Available options
|
||||
```bash
|
||||
# Interval of time the exporter will fetch data from PI-Hole
|
||||
-interval duration (optional) (default 5s)
|
||||
|
||||
# Hostname of the Raspberry PI 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)
|
||||
|
||||
# Port to be used for the exporter
|
||||
-port string (optional) (default "9311")
|
||||
```
|
65
config/configuration.go
Normal file
65
config/configuration.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/heetch/confita"
|
||||
"github.com/heetch/confita/backend"
|
||||
"github.com/heetch/confita/backend/env"
|
||||
"github.com/heetch/confita/backend/flags"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
PIHoleHostname string `config:"pihole_hostname"`
|
||||
PIHolePassword string `config:"pihole_password"`
|
||||
|
||||
Port string `config:"port"`
|
||||
Interval time.Duration `config:"interval"`
|
||||
}
|
||||
|
||||
func getDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PIHoleHostname: "127.0.0.1",
|
||||
PIHolePassword: "",
|
||||
|
||||
Port: "9311",
|
||||
Interval: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
loaders := []backend.Backend{
|
||||
env.NewBackend(),
|
||||
flags.NewBackend(),
|
||||
}
|
||||
|
||||
loader := confita.NewLoader(loaders...)
|
||||
|
||||
cfg := getDefaultConfig()
|
||||
err := loader.Load(context.Background(), cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg.show()
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c Config) show() {
|
||||
val := reflect.ValueOf(&c).Elem()
|
||||
log.Println("------------------------------------")
|
||||
log.Println("- PI-Hole exporter configuration -")
|
||||
log.Println("------------------------------------")
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
valueField := val.Field(i)
|
||||
typeField := val.Type().Field(i)
|
||||
|
||||
log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface()))
|
||||
}
|
||||
log.Println("------------------------------------")
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module github.com/eko/pihole-exporter
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/heetch/confita v0.5.1
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/prometheus/client_golang v0.9.2
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c
|
||||
)
|
33
go.sum
Normal file
33
go.sum
Normal file
|
@ -0,0 +1,33 @@
|
|||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/heetch/confita v0.5.1 h1:EiE32j+Ze0sI0YBeJDSdqTZ32uKz2XCTQIzSgwgfnvk=
|
||||
github.com/heetch/confita v0.5.1/go.mod h1:S8Em4kuK8pR5vfTiaNkFLfNDMlGF/EtQUaCxDhXRpCs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
118
internal/metrics/metrics.go
Normal file
118
internal/metrics/metrics.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// DomainsBlocked - The number of domains being blocked by PI-Hole.
|
||||
DomainsBlocked = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "domains_being_blocked",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of domains being blocked",
|
||||
},
|
||||
)
|
||||
|
||||
// DNSQueriesToday - The number of DNS requests made over PI-Hole over the current day.
|
||||
DNSQueriesToday = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "dns_queries_today",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of DNS queries made over the current day",
|
||||
},
|
||||
)
|
||||
|
||||
// AdsBlockedToday - The number of ads blocked by PI-Hole over the current day.
|
||||
AdsBlockedToday = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "ads_blocked_today",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of ads blocked over the current day",
|
||||
},
|
||||
)
|
||||
|
||||
// AdsPercentageToday - The percentage of ads blocked by PI-Hole over the current day.
|
||||
AdsPercentageToday = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "ads_percentage_today",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the percentage of ads blocked over the current day",
|
||||
},
|
||||
)
|
||||
|
||||
// UniqueDomains - The number of unique domains seen by PI-Hole.
|
||||
UniqueDomains = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "unique_domains",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of unique domains seen",
|
||||
},
|
||||
)
|
||||
|
||||
// QueriesForwarded - The number of queries forwarded by PI-Hole.
|
||||
QueriesForwarded = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "queries_forwarded",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of queries forwarded",
|
||||
},
|
||||
)
|
||||
|
||||
// QueriesCached - The number of queries cached by PI-Hole.
|
||||
QueriesCached = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "queries_cached",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of queries cached",
|
||||
},
|
||||
)
|
||||
|
||||
// ClientsEverSeen - The number of clients ever seen by PI-Hole.
|
||||
ClientsEverSeen = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "clients_ever_seen",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of clients ever seen",
|
||||
},
|
||||
)
|
||||
|
||||
// UniqueClients - The number of unique clients seen by PI-Hole.
|
||||
UniqueClients = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "unique_clients",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of unique clients seen",
|
||||
},
|
||||
)
|
||||
|
||||
// DnsQueriesAllTypes - The number of DNS queries made for all types by PI-Hole.
|
||||
DnsQueriesAllTypes = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "dns_queries_all_types",
|
||||
Namespace: "pihole",
|
||||
Help: "This represent the number of DNS queries made for all types",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
// Init initializes Prometheus metrics
|
||||
func Init() {
|
||||
initMetric(DomainsBlocked)
|
||||
initMetric(DNSQueriesToday)
|
||||
initMetric(AdsBlockedToday)
|
||||
initMetric(AdsPercentageToday)
|
||||
initMetric(UniqueDomains)
|
||||
initMetric(QueriesForwarded)
|
||||
initMetric(QueriesCached)
|
||||
initMetric(ClientsEverSeen)
|
||||
initMetric(UniqueClients)
|
||||
initMetric(DnsQueriesAllTypes)
|
||||
}
|
||||
|
||||
func initMetric(metric prometheus.Gauge) {
|
||||
prometheus.MustRegister(metric)
|
||||
log.Printf("New prometheus metric registered: %s", metric.Desc().String())
|
||||
}
|
131
internal/pihole/client.go
Normal file
131
internal/pihole/client.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package pihole
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eko/pihole-exporter/internal/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
loginURLPattern = "http://%s/admin/index.php?login"
|
||||
statsURLPattern = "http://%s/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&jsonForceObject"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
hostname string
|
||||
password string
|
||||
interval time.Duration
|
||||
httpClient http.Client
|
||||
}
|
||||
|
||||
func NewClient(hostname, password string, interval time.Duration) *Client {
|
||||
return &Client{
|
||||
hostname: hostname,
|
||||
password: password,
|
||||
interval: interval,
|
||||
httpClient: http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Fetch() {
|
||||
for range time.Tick(c.interval) {
|
||||
sessionID := c.getPHPSessionID()
|
||||
if sessionID == nil {
|
||||
log.Println("Unable to retrieve session identifier")
|
||||
return
|
||||
}
|
||||
|
||||
stats := c.getStatistics(*sessionID)
|
||||
|
||||
log.Println("New tick of statistics", stats)
|
||||
|
||||
metrics.DomainsBlocked.Set(float64(stats.DomainsBeingBlocked))
|
||||
metrics.DNSQueriesToday.Set(float64(stats.DNSQueriesToday))
|
||||
metrics.AdsBlockedToday.Set(float64(stats.AdsBlockedToday))
|
||||
metrics.AdsPercentageToday.Set(float64(stats.AdsPercentageToday))
|
||||
metrics.UniqueDomains.Set(float64(stats.UniqueDomains))
|
||||
metrics.QueriesForwarded.Set(float64(stats.QueriesForwarded))
|
||||
metrics.QueriesCached.Set(float64(stats.QueriesCached))
|
||||
metrics.ClientsEverSeen.Set(float64(stats.ClientsEverSeen))
|
||||
metrics.UniqueClients.Set(float64(stats.UniqueClients))
|
||||
metrics.DnsQueriesAllTypes.Set(float64(stats.DnsQueriesAllTypes))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getPHPSessionID() *string {
|
||||
var sessionID string
|
||||
|
||||
loginURL := fmt.Sprintf(loginURLPattern, c.hostname)
|
||||
values := url.Values{"pw": []string{c.password}}
|
||||
|
||||
req, err := http.NewRequest("POST", loginURL, strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
log.Fatal("An error has occured when creating HTTP statistics request", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Content-Length", strconv.Itoa(len(values.Encode())))
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("An error has occured during login to PI-Hole: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusFound {
|
||||
log.Printf("Unable to login to PI-Hole, got a HTTP status code response '%d' instead of '%d'", resp.StatusCode, http.StatusFound)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == "PHPSESSID" {
|
||||
sessionID = cookie.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &sessionID
|
||||
}
|
||||
|
||||
func (c *Client) getStatistics(sessionID string) *Stats {
|
||||
var stats Stats
|
||||
|
||||
statsURL := fmt.Sprintf(statsURLPattern, c.hostname)
|
||||
|
||||
req, err := http.NewRequest("GET", statsURL, nil)
|
||||
if err != nil {
|
||||
log.Fatal("An error has occured when creating HTTP statistics request", err)
|
||||
}
|
||||
|
||||
cookie := http.Cookie{Name: "PHPSESSID", Value: sessionID}
|
||||
req.AddCookie(&cookie)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Println("An error has occured during retrieving PI-Hole statistics", err)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println("Unable to read PI-Hole statistics HTTP response", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &stats)
|
||||
if err != nil {
|
||||
log.Println("Unable to unmarshal PI-Hole statistics to statistics struct model", err)
|
||||
}
|
||||
|
||||
return &stats
|
||||
}
|
15
internal/pihole/model.go
Normal file
15
internal/pihole/model.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package pihole
|
||||
|
||||
// Stats is the PI-Hole statistics JSON API corresponding model
|
||||
type Stats struct {
|
||||
DomainsBeingBlocked int `json:"domains_being_blocked"`
|
||||
DNSQueriesToday int `json:"dns_queries_today"`
|
||||
AdsBlockedToday int `json:"ads_blocked_today"`
|
||||
AdsPercentageToday float64 `json:"ads_percentage_today"`
|
||||
UniqueDomains int `json:"unique_domains"`
|
||||
QueriesForwarded int `json:"queries_forwarded"`
|
||||
QueriesCached int `json:"queries_cached"`
|
||||
ClientsEverSeen int `json:"clients_ever_seen"`
|
||||
UniqueClients int `json:"unique_clients"`
|
||||
DnsQueriesAllTypes int `json:"dns_queries_all_types"`
|
||||
}
|
65
internal/server/server.go
Normal file
65
internal/server/server.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
}
|
||||
|
||||
func NewServer(port string) *Server {
|
||||
mux := http.NewServeMux()
|
||||
httpServer := &http.Server{Addr: ":" + port, Handler: mux}
|
||||
|
||||
s := &Server{
|
||||
httpServer: httpServer,
|
||||
}
|
||||
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
mux.Handle("/readiness", s.readinessHandler())
|
||||
mux.Handle("/liveness", s.livenessHandler())
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() {
|
||||
log.Println("Starting HTTP server")
|
||||
|
||||
err := s.httpServer.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Printf("Failed to start serving HTTP requests: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
s.httpServer.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (s *Server) readinessHandler() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if s.isReady() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) livenessHandler() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) isReady() bool {
|
||||
return s.httpServer != nil
|
||||
}
|
53
main.go
Normal file
53
main.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/eko/pihole-exporter/config"
|
||||
"github.com/eko/pihole-exporter/internal/metrics"
|
||||
"github.com/eko/pihole-exporter/internal/pihole"
|
||||
"github.com/eko/pihole-exporter/internal/server"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "pihole-exporter"
|
||||
)
|
||||
|
||||
var (
|
||||
s *server.Server
|
||||
)
|
||||
|
||||
func main() {
|
||||
conf := config.Load()
|
||||
|
||||
metrics.Init()
|
||||
|
||||
initPiholeClient(conf.PIHoleHostname, conf.PIHolePassword, conf.Interval)
|
||||
initHttpServer(conf.Port)
|
||||
|
||||
handleExitSignal()
|
||||
}
|
||||
|
||||
func initPiholeClient(hostname, password string, interval time.Duration) {
|
||||
client := pihole.NewClient(hostname, password, interval)
|
||||
go client.Fetch()
|
||||
}
|
||||
|
||||
func initHttpServer(port string) {
|
||||
s = server.NewServer(port)
|
||||
go s.ListenAndServe()
|
||||
}
|
||||
|
||||
func handleExitSignal() {
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
<-stop
|
||||
|
||||
s.Stop()
|
||||
fmt.Println(fmt.Sprintf("\n%s HTTP server stopped", name))
|
||||
}
|
Loading…
Reference in a new issue