2019-05-08 23:45:04 +02:00
package config
import (
"context"
2021-12-26 23:36:30 +01:00
"errors"
2019-05-08 23:45:04 +02:00
"fmt"
"reflect"
2021-12-10 15:39:22 +01:00
"strings"
2019-05-08 23:45:04 +02:00
"time"
2022-01-05 20:38:33 +01:00
log "github.com/sirupsen/logrus"
2019-05-08 23:45:04 +02:00
"github.com/heetch/confita"
"github.com/heetch/confita/backend"
"github.com/heetch/confita/backend/env"
"github.com/heetch/confita/backend/flags"
)
2019-05-09 21:11:31 +02:00
// Config is the exporter CLI configuration.
2019-05-08 23:45:04 +02:00
type Config struct {
2021-12-10 04:48:28 +01:00
PIHoleProtocol string ` config:"pihole_protocol" `
PIHoleHostname string ` config:"pihole_hostname" `
PIHolePort uint16 ` config:"pihole_port" `
PIHolePassword string ` config:"pihole_password" `
PIHoleApiToken string ` config:"pihole_api_token" `
}
type EnvConfig struct {
PIHoleProtocol [ ] string ` config:"pihole_protocol" `
PIHoleHostname [ ] string ` config:"pihole_hostname" `
PIHolePort [ ] uint16 ` config:"pihole_port" `
PIHolePassword [ ] string ` config:"pihole_password" `
PIHoleApiToken [ ] string ` config:"pihole_api_token" `
Port uint16 ` config:"port" `
2020-04-07 00:31:30 +02:00
Interval time . Duration ` config:"interval" `
2019-05-08 23:45:04 +02:00
}
2021-12-10 04:48:28 +01:00
func getDefaultEnvConfig ( ) * EnvConfig {
return & EnvConfig {
PIHoleProtocol : [ ] string { "http" } ,
PIHoleHostname : [ ] string { "127.0.0.1" } ,
PIHolePort : [ ] uint16 { 80 } ,
PIHolePassword : [ ] string { } ,
PIHoleApiToken : [ ] string { } ,
Port : 9617 ,
2020-04-07 00:31:30 +02:00
Interval : 10 * time . Second ,
2019-05-08 23:45:04 +02:00
}
}
2019-05-09 21:11:31 +02:00
// Load method loads the configuration by using both flag or environment variables.
2021-12-26 23:36:30 +01:00
func Load ( ) ( * EnvConfig , [ ] Config , error ) {
2019-05-08 23:45:04 +02:00
loaders := [ ] backend . Backend {
env . NewBackend ( ) ,
flags . NewBackend ( ) ,
}
loader := confita . NewLoader ( loaders ... )
2021-12-10 04:48:28 +01:00
cfg := getDefaultEnvConfig ( )
2019-05-08 23:45:04 +02:00
err := loader . Load ( context . Background ( ) , cfg )
if err != nil {
panic ( err )
}
cfg . show ( )
2021-12-26 23:36:30 +01:00
if clientsConfig , err := cfg . Split ( ) ; err != nil {
return cfg , nil , err
} else {
return cfg , clientsConfig , nil
}
2019-05-08 23:45:04 +02:00
}
2021-12-10 15:39:22 +01:00
func ( c * Config ) String ( ) string {
ref := reflect . ValueOf ( c )
fields := ref . Elem ( )
buffer := make ( [ ] string , fields . NumField ( ) , fields . NumField ( ) )
for i := 0 ; i < fields . NumField ( ) ; i ++ {
valueField := fields . Field ( i )
typeField := fields . Type ( ) . Field ( i )
buffer [ i ] = fmt . Sprintf ( "%s=%v" , typeField . Name , valueField . Interface ( ) )
}
return fmt . Sprintf ( "<Config@%X %s>" , & c , strings . Join ( buffer , ", " ) )
}
2021-08-16 23:55:05 +02:00
//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
}
2021-12-26 23:36:30 +01:00
func ( c EnvConfig ) Split ( ) ( [ ] Config , error ) {
hostsCount := len ( c . PIHoleHostname )
result := make ( [ ] Config , 0 , hostsCount )
2021-12-10 04:48:28 +01:00
for i , hostname := range c . PIHoleHostname {
config := Config {
2021-12-23 12:05:26 +01:00
PIHoleHostname : strings . TrimSpace ( hostname ) ,
2021-12-10 04:48:28 +01:00
}
2021-12-26 23:36:30 +01:00
if len ( c . PIHolePort ) == 1 {
config . PIHolePort = c . PIHolePort [ 0 ]
} else if len ( c . PIHolePort ) == hostsCount {
config . PIHolePort = c . PIHolePort [ i ]
} else if len ( c . PIHolePort ) != 0 {
return nil , errors . New ( "Wrong number of ports. Port can be empty to use default, one value to use for all hosts, or match the number of hosts" )
2021-12-10 04:48:28 +01:00
}
2021-12-26 23:36:30 +01:00
if hasData , data , isValid := extractStringConfig ( c . PIHoleProtocol , i , hostsCount ) ; hasData {
config . PIHoleProtocol = data
} else if ! isValid {
return nil , errors . New ( "Wrong number of PIHoleProtocol. PIHoleProtocol can be empty to use default, one value to use for all hosts, or match the number of hosts" )
2021-12-23 12:05:26 +01:00
}
2021-12-26 23:36:30 +01:00
if hasData , data , isValid := extractStringConfig ( c . PIHoleApiToken , i , hostsCount ) ; hasData {
2021-12-23 12:05:26 +01:00
config . PIHoleApiToken = data
2021-12-26 23:36:30 +01:00
} else if ! isValid {
return nil , errors . New ( fmt . Sprintf ( "Wrong number of PIHoleApiToken %d (Hosts: %d). PIHoleApiToken can be empty to use default, one value to use for all hosts, or match the number of hosts" , len ( c . PIHoleApiToken ) , hostsCount ) )
2021-12-23 12:05:26 +01:00
}
2021-12-26 23:36:30 +01:00
if hasData , data , isValid := extractStringConfig ( c . PIHolePassword , i , hostsCount ) ; hasData {
2021-12-23 12:05:26 +01:00
config . PIHolePassword = data
2021-12-26 23:36:30 +01:00
} else if ! isValid {
return nil , errors . New ( "Wrong number of PIHolePassword. PIHolePassword can be empty to use default, one value to use for all hosts, or match the number of hosts" )
2021-12-10 04:48:28 +01:00
}
result = append ( result , config )
}
2021-12-26 23:36:30 +01:00
return result , nil
2021-12-10 04:48:28 +01:00
}
2021-12-26 23:36:30 +01:00
func extractStringConfig ( data [ ] string , idx int , hostsCount int ) ( bool , string , bool ) {
2021-12-23 12:05:26 +01:00
if len ( data ) == 1 {
v := strings . TrimSpace ( data [ 0 ] )
if v != "" {
2021-12-26 23:36:30 +01:00
return true , v , true
2021-12-23 12:05:26 +01:00
}
2021-12-26 23:36:30 +01:00
} else if len ( data ) == hostsCount {
2021-12-23 12:05:26 +01:00
v := strings . TrimSpace ( data [ idx ] )
if v != "" {
2021-12-26 23:36:30 +01:00
return true , v , true
2021-12-23 12:05:26 +01:00
}
2021-12-26 23:36:30 +01:00
} else if len ( data ) != 0 { //Host count missmatch
return false , "" , false
2021-12-23 12:05:26 +01:00
}
2021-12-26 23:36:30 +01:00
// Empty
return false , "" , true
2021-12-23 12:05:26 +01:00
}
2021-08-16 23:55:05 +02:00
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"
}
2021-12-10 04:48:28 +01:00
func ( c EnvConfig ) show ( ) {
2019-05-08 23:45:04 +02:00
val := reflect . ValueOf ( & c ) . Elem ( )
2022-01-05 20:38:33 +01:00
log . Info ( "------------------------------------" )
log . Info ( "- PI-Hole exporter configuration -" )
log . Info ( "------------------------------------" )
2019-05-08 23:45:04 +02:00
for i := 0 ; i < val . NumField ( ) ; i ++ {
valueField := val . Field ( i )
typeField := val . Type ( ) . Field ( i )
2020-04-07 00:31:30 +02:00
// Do not print password or api token but do print the authentication method
if typeField . Name != "PIHolePassword" && typeField . Name != "PIHoleApiToken" {
2022-01-05 20:38:33 +01:00
log . Info ( fmt . Sprintf ( "%s : %v" , typeField . Name , valueField . Interface ( ) ) )
2020-04-07 00:31:30 +02:00
} else {
2021-12-10 04:48:28 +01:00
showAuthenticationMethod ( typeField . Name , valueField . Len ( ) )
2020-04-04 20:59:31 +02:00
}
2019-05-08 23:45:04 +02:00
}
2022-01-05 20:38:33 +01:00
log . Info ( "------------------------------------" )
2019-05-08 23:45:04 +02:00
}
2020-04-07 00:31:30 +02:00
2021-12-10 04:48:28 +01:00
func showAuthenticationMethod ( name string , length int ) {
if length > 0 {
2022-01-05 20:38:33 +01:00
log . Info ( fmt . Sprintf ( "Pi-Hole Authentication Method : %s" , name ) )
2020-04-07 00:31:30 +02:00
}
}