only scrape when asked
will only scape metrics on pihole when asked on /metrics allow port 0 to be used when using strict https redirects Resolves: #49
This commit is contained in:
parent
ad8778b523
commit
26ddc36a4e
6 changed files with 112 additions and 105 deletions
|
@ -56,6 +56,32 @@ func Load() *Config {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Validate check if the config is valid
|
||||||
|
func (c Config) Validate() error {
|
||||||
|
if c.PIHoleProtocol != "http" && c.PIHoleProtocol != "https" {
|
||||||
|
return fmt.Errorf("protocol %s is invalid. Must be http or https", c.PIHoleProtocol)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) hostnameURL() string {
|
||||||
|
s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname)
|
||||||
|
if c.PIHolePort != 0 {
|
||||||
|
s += fmt.Sprintf(":%d", c.PIHolePort)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
//PIHoleStatsURL returns the stats url
|
||||||
|
func (c Config) PIHoleStatsURL() string {
|
||||||
|
return c.hostnameURL() + "/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&jsonForceObject"
|
||||||
|
}
|
||||||
|
|
||||||
|
//PIHoleLoginURL returns the login url
|
||||||
|
func (c Config) PIHoleLoginURL() string {
|
||||||
|
return c.hostnameURL() + "/admin/index.php?login"
|
||||||
|
}
|
||||||
|
|
||||||
func (c Config) show() {
|
func (c Config) show() {
|
||||||
val := reflect.ValueOf(&c).Elem()
|
val := reflect.ValueOf(&c).Elem()
|
||||||
log.Println("------------------------------------")
|
log.Println("------------------------------------")
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,5 +5,6 @@ go 1.15
|
||||||
require (
|
require (
|
||||||
github.com/heetch/confita v0.10.0
|
github.com/heetch/confita v0.10.0
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
|
github.com/xonvanetta/shutdown v0.0.2
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||||
)
|
)
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -198,12 +198,15 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
|
||||||
|
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
github.com/xonvanetta/shutdown v0.0.2 h1:FTbzm/55K8GPgaFdllI71V/cH7IRsyNJD6Z3JVR66Zs=
|
||||||
|
github.com/xonvanetta/shutdown v0.0.2/go.mod h1:bYnVnX8ITK2E9GpuH/YVfctve/d5oOIvWsyhFj/N450=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
@ -296,7 +299,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -12,40 +12,28 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/eko/pihole-exporter/config"
|
||||||
"github.com/eko/pihole-exporter/internal/metrics"
|
"github.com/eko/pihole-exporter/internal/metrics"
|
||||||
)
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
var (
|
|
||||||
loginURLPattern = "%s://%s:%d/admin/index.php?login"
|
|
||||||
statsURLPattern = "%s://%s:%d/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&jsonForceObject"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
|
// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
httpClient http.Client
|
httpClient http.Client
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
protocol string
|
config *config.Config
|
||||||
hostname string
|
|
||||||
port uint16
|
|
||||||
password string
|
|
||||||
sessionID string
|
|
||||||
apiToken string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient method initializes a new PI-Hole client.
|
// NewClient method initializes a new PI-Hole client.
|
||||||
func NewClient(protocol, hostname string, port uint16, password, apiToken string, interval time.Duration) *Client {
|
func NewClient(config *config.Config) *Client {
|
||||||
if protocol != "http" && protocol != "https" {
|
err := config.Validate()
|
||||||
log.Printf("protocol %s is invalid. Must be http or https.", protocol)
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
protocol: protocol,
|
config: config,
|
||||||
hostname: hostname,
|
|
||||||
port: port,
|
|
||||||
password: password,
|
|
||||||
apiToken: apiToken,
|
|
||||||
interval: interval,
|
|
||||||
httpClient: http.Client{
|
httpClient: http.Client{
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
@ -54,67 +42,70 @@ func NewClient(protocol, hostname string, port uint16, password, apiToken string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrape method authenticates and retrieves statistics from PI-Hole JSON API
|
// Metrics scrapes pihole and sets them
|
||||||
// and then pass them as Prometheus metrics.
|
func (c *Client) Metrics() http.HandlerFunc {
|
||||||
func (c *Client) Scrape() {
|
return func(writer http.ResponseWriter, request *http.Request) {
|
||||||
for range time.Tick(c.interval) {
|
stats, err := c.getStatistics()
|
||||||
stats := c.getStatistics()
|
if err != nil {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, _ = writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
c.setMetrics(stats)
|
c.setMetrics(stats)
|
||||||
|
|
||||||
log.Printf("New tick of statistics: %s", stats.ToString())
|
log.Printf("New tick of statistics: %s", stats.ToString())
|
||||||
|
promhttp.Handler().ServeHTTP(writer, request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) setMetrics(stats *Stats) {
|
func (c *Client) setMetrics(stats *Stats) {
|
||||||
metrics.DomainsBlocked.WithLabelValues(c.hostname).Set(float64(stats.DomainsBeingBlocked))
|
metrics.DomainsBlocked.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DomainsBeingBlocked))
|
||||||
metrics.DNSQueriesToday.WithLabelValues(c.hostname).Set(float64(stats.DNSQueriesToday))
|
metrics.DNSQueriesToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesToday))
|
||||||
metrics.AdsBlockedToday.WithLabelValues(c.hostname).Set(float64(stats.AdsBlockedToday))
|
metrics.AdsBlockedToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.AdsBlockedToday))
|
||||||
metrics.AdsPercentageToday.WithLabelValues(c.hostname).Set(float64(stats.AdsPercentageToday))
|
metrics.AdsPercentageToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.AdsPercentageToday))
|
||||||
metrics.UniqueDomains.WithLabelValues(c.hostname).Set(float64(stats.UniqueDomains))
|
metrics.UniqueDomains.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.UniqueDomains))
|
||||||
metrics.QueriesForwarded.WithLabelValues(c.hostname).Set(float64(stats.QueriesForwarded))
|
metrics.QueriesForwarded.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.QueriesForwarded))
|
||||||
metrics.QueriesCached.WithLabelValues(c.hostname).Set(float64(stats.QueriesCached))
|
metrics.QueriesCached.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.QueriesCached))
|
||||||
metrics.ClientsEverSeen.WithLabelValues(c.hostname).Set(float64(stats.ClientsEverSeen))
|
metrics.ClientsEverSeen.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.ClientsEverSeen))
|
||||||
metrics.UniqueClients.WithLabelValues(c.hostname).Set(float64(stats.UniqueClients))
|
metrics.UniqueClients.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.UniqueClients))
|
||||||
metrics.DNSQueriesAllTypes.WithLabelValues(c.hostname).Set(float64(stats.DNSQueriesAllTypes))
|
metrics.DNSQueriesAllTypes.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesAllTypes))
|
||||||
|
|
||||||
metrics.Reply.WithLabelValues(c.hostname, "no_data").Set(float64(stats.ReplyNoData))
|
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "no_data").Set(float64(stats.ReplyNoData))
|
||||||
metrics.Reply.WithLabelValues(c.hostname, "nx_domain").Set(float64(stats.ReplyNxDomain))
|
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "nx_domain").Set(float64(stats.ReplyNxDomain))
|
||||||
metrics.Reply.WithLabelValues(c.hostname, "cname").Set(float64(stats.ReplyCname))
|
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "cname").Set(float64(stats.ReplyCname))
|
||||||
metrics.Reply.WithLabelValues(c.hostname, "ip").Set(float64(stats.ReplyIP))
|
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "ip").Set(float64(stats.ReplyIP))
|
||||||
|
|
||||||
var isEnabled int = 0
|
var isEnabled int = 0
|
||||||
if stats.Status == enabledStatus {
|
if stats.Status == enabledStatus {
|
||||||
isEnabled = 1
|
isEnabled = 1
|
||||||
}
|
}
|
||||||
metrics.Status.WithLabelValues(c.hostname).Set(float64(isEnabled))
|
metrics.Status.WithLabelValues(c.config.PIHoleHostname).Set(float64(isEnabled))
|
||||||
|
|
||||||
for domain, value := range stats.TopQueries {
|
for domain, value := range stats.TopQueries {
|
||||||
metrics.TopQueries.WithLabelValues(c.hostname, domain).Set(float64(value))
|
metrics.TopQueries.WithLabelValues(c.config.PIHoleHostname, domain).Set(float64(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
for domain, value := range stats.TopAds {
|
for domain, value := range stats.TopAds {
|
||||||
metrics.TopAds.WithLabelValues(c.hostname, domain).Set(float64(value))
|
metrics.TopAds.WithLabelValues(c.config.PIHoleHostname, domain).Set(float64(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
for source, value := range stats.TopSources {
|
for source, value := range stats.TopSources {
|
||||||
metrics.TopSources.WithLabelValues(c.hostname, source).Set(float64(value))
|
metrics.TopSources.WithLabelValues(c.config.PIHoleHostname, source).Set(float64(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
for destination, value := range stats.ForwardDestinations {
|
for destination, value := range stats.ForwardDestinations {
|
||||||
metrics.ForwardDestinations.WithLabelValues(c.hostname, destination).Set(value)
|
metrics.ForwardDestinations.WithLabelValues(c.config.PIHoleHostname, destination).Set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
for queryType, value := range stats.QueryTypes {
|
for queryType, value := range stats.QueryTypes {
|
||||||
metrics.QueryTypes.WithLabelValues(c.hostname, queryType).Set(value)
|
metrics.QueryTypes.WithLabelValues(c.config.PIHoleHostname, queryType).Set(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getPHPSessionID() (sessionID string) {
|
func (c *Client) getPHPSessionID() (sessionID string) {
|
||||||
loginURL := fmt.Sprintf(loginURLPattern, c.protocol, c.hostname, c.port)
|
values := url.Values{"pw": []string{c.config.PIHolePassword}}
|
||||||
values := url.Values{"pw": []string{c.password}}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", loginURL, strings.NewReader(values.Encode()))
|
req, err := http.NewRequest("POST", c.config.PIHoleLoginURL(), strings.NewReader(values.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("An error has occured when creating HTTP statistics request", err)
|
log.Fatal("An error has occured when creating HTTP statistics request", err)
|
||||||
}
|
}
|
||||||
|
@ -137,18 +128,18 @@ func (c *Client) getPHPSessionID() (sessionID string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getStatistics() *Stats {
|
func (c *Client) getStatistics() (*Stats, error) {
|
||||||
var stats Stats
|
stats := new(Stats)
|
||||||
|
|
||||||
statsURL := fmt.Sprintf(statsURLPattern, c.protocol, c.hostname, c.port)
|
statsURL := c.config.PIHoleStatsURL()
|
||||||
|
|
||||||
if c.isUsingApiToken() {
|
if c.isUsingApiToken() {
|
||||||
statsURL = fmt.Sprintf("%s&auth=%s", statsURL, c.apiToken)
|
statsURL = fmt.Sprintf("%s&auth=%s", statsURL, c.config.PIHoleApiToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", statsURL, nil)
|
req, err := http.NewRequest("GET", statsURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("An error has occured when creating HTTP statistics request", err)
|
return nil, fmt.Errorf("an error has occured when creating HTTP statistics request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.isUsingPassword() {
|
if c.isUsingPassword() {
|
||||||
|
@ -157,28 +148,28 @@ func (c *Client) getStatistics() *Stats {
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("An error has occured during retrieving PI-Hole statistics", err)
|
return nil, fmt.Errorf("an error has occured during retrieving PI-Hole statistics: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to read PI-Hole statistics HTTP response", err)
|
return nil, fmt.Errorf("unable to read PI-Hole statistics HTTP response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(body, &stats)
|
err = json.Unmarshal(body, stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to unmarshal PI-Hole statistics to statistics struct model", err)
|
return nil, fmt.Errorf("unable to unmarshal PI-Hole statistics to statistics struct model: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &stats
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) isUsingPassword() bool {
|
func (c *Client) isUsingPassword() bool {
|
||||||
return len(c.password) > 0
|
return len(c.config.PIHolePassword) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) isUsingApiToken() bool {
|
func (c *Client) isUsingApiToken() bool {
|
||||||
return len(c.apiToken) > 0
|
return len(c.config.PIHoleApiToken) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) authenticateRequest(req *http.Request) {
|
func (c *Client) authenticateRequest(req *http.Request) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/eko/pihole-exporter/internal/pihole"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ 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) *Server {
|
func NewServer(port string, client *pihole.Client) *Server {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
httpServer := &http.Server{Addr: ":" + port, Handler: mux}
|
httpServer := &http.Server{Addr: ":" + port, Handler: mux}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func NewServer(port string) *Server {
|
||||||
httpServer: httpServer,
|
httpServer: httpServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
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())
|
||||||
|
|
||||||
|
@ -50,19 +50,19 @@ func (s *Server) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) readinessHandler() http.HandlerFunc {
|
func (s *Server) readinessHandler() http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
if s.isReady() {
|
if s.isReady() {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) livenessHandler() http.HandlerFunc {
|
func (s *Server) livenessHandler() http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) isReady() bool {
|
func (s *Server) isReady() bool {
|
||||||
|
|
55
main.go
55
main.go
|
@ -2,23 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/eko/pihole-exporter/config"
|
"github.com/eko/pihole-exporter/config"
|
||||||
"github.com/eko/pihole-exporter/internal/metrics"
|
"github.com/eko/pihole-exporter/internal/metrics"
|
||||||
"github.com/eko/pihole-exporter/internal/pihole"
|
"github.com/eko/pihole-exporter/internal/pihole"
|
||||||
"github.com/eko/pihole-exporter/internal/server"
|
"github.com/eko/pihole-exporter/internal/server"
|
||||||
)
|
"github.com/xonvanetta/shutdown/pkg/shutdown"
|
||||||
|
|
||||||
const (
|
|
||||||
name = "pihole-exporter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
s *server.Server
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -26,28 +15,24 @@ func main() {
|
||||||
|
|
||||||
metrics.Init()
|
metrics.Init()
|
||||||
|
|
||||||
initPiHoleClient(conf.PIHoleProtocol, conf.PIHoleHostname, conf.PIHolePort, conf.PIHolePassword, conf.PIHoleApiToken, conf.Interval)
|
serverDead := make(chan struct{})
|
||||||
initHttpServer(conf.Port)
|
s := server.NewServer(conf.Port, pihole.NewClient(conf))
|
||||||
|
go func() {
|
||||||
|
s.ListenAndServe()
|
||||||
|
close(serverDead)
|
||||||
|
}()
|
||||||
|
|
||||||
handleExitSignal()
|
ctx := shutdown.Context()
|
||||||
}
|
|
||||||
|
go func() {
|
||||||
func initPiHoleClient(protocol, hostname string, port uint16, password, apiToken string, interval time.Duration) {
|
<-ctx.Done()
|
||||||
client := pihole.NewClient(protocol, hostname, port, password, apiToken, interval)
|
s.Stop()
|
||||||
go client.Scrape()
|
}()
|
||||||
}
|
|
||||||
|
select {
|
||||||
func initHttpServer(port string) {
|
case <-ctx.Done():
|
||||||
s = server.NewServer(port)
|
case <-serverDead:
|
||||||
go s.ListenAndServe()
|
}
|
||||||
}
|
|
||||||
|
fmt.Println("pihole-exporter HTTP server stopped")
|
||||||
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