From 42998f0d822acdc6fd499ca5ea269697bda3260e Mon Sep 17 00:00:00 2001 From: Pierre Cosson <962185+pierCo@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:30:56 +0100 Subject: [PATCH 1/5] feat(): adding PI Hole URL Context --- config/configuration.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 271ad12..8bd1515 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -22,6 +22,7 @@ type Config struct { PIHoleProtocol string `config:"pihole_protocol"` PIHoleHostname string `config:"pihole_hostname"` PIHolePort uint16 `config:"pihole_port"` + PIHoleContext string `config:"pihole_context"` PIHolePassword string `config:"pihole_password"` PIHoleApiToken string `config:"pihole_api_token"` BindAddr string `config:"bind_addr"` @@ -32,6 +33,7 @@ type EnvConfig struct { PIHoleProtocol []string `config:"pihole_protocol"` PIHoleHostname []string `config:"pihole_hostname"` PIHolePort []uint16 `config:"pihole_port"` + PIHoleContext []string `config:"pihole_context"` PIHolePassword []string `config:"pihole_password"` PIHoleApiToken []string `config:"pihole_api_token"` BindAddr string `config:"bind_addr"` @@ -44,6 +46,7 @@ func getDefaultEnvConfig() *EnvConfig { PIHoleProtocol: []string{"http"}, PIHoleHostname: []string{"127.0.0.1"}, PIHolePort: []uint16{80}, + PIHoleContext: []string{""}, PIHolePassword: []string{}, PIHoleApiToken: []string{}, BindAddr: "0.0.0.0", @@ -173,22 +176,25 @@ func removeEmptyString(source []string) []string { return result } -func (c Config) hostnameURL() string { +func (c Config) piHoleURL() string { s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname) if c.PIHolePort != 0 { s += fmt.Sprintf(":%d", c.PIHolePort) } + if c.PIHoleContext != "" { + s += fmt.Sprintf("/%s", c.PIHoleContext) + } 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&overTimeData10mins&jsonForceObject" + return c.piHoleURL() + "/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&overTimeData10mins&jsonForceObject" } // PIHoleLoginURL returns the login url func (c Config) PIHoleLoginURL() string { - return c.hostnameURL() + "/admin/index.php?login" + return c.piHoleURL() + "/admin/index.php?login" } func (c EnvConfig) show() { From 2f7c4238685d18b763aa042f43e46f14c42c6294 Mon Sep 17 00:00:00 2001 From: Pierre Cosson <962185+pierCo@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:46:55 +0100 Subject: [PATCH 2/5] feat(): Update tests --- config/configuration.go | 8 ++++++++ config/configuration_test.go | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/config/configuration.go b/config/configuration.go index 8bd1515..6d511fe 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -123,6 +123,14 @@ func (c EnvConfig) Split() ([]Config, error) { 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") } + if len(c.PIHoleContext) == 1 { + config.PIHoleContext = c.PIHoleContext[0] + } else if len(c.PIHoleContext) == hostsCount { + config.PIHoleContext = c.PIHoleContext[i] + } else if len(c.PIHoleContext) != 0 { + return nil, errors.New("Wrong number of context. Context can be empty to use default, one value to use for all hosts, or match the number of hosts") + } + if hasData, data, isValid := extractStringConfig(c.PIHoleProtocol, i, hostsCount); hasData { config.PIHoleProtocol = data } else if !isValid { diff --git a/config/configuration_test.go b/config/configuration_test.go index 5f76d34..bf57a27 100644 --- a/config/configuration_test.go +++ b/config/configuration_test.go @@ -10,6 +10,7 @@ import ( const ( PIHOLE_HOSTNAME = "PIHOLE_HOSTNAME" PIHOLE_PORT = "PIHOLE_PORT" + PIHOLE_CONTEXT = "PIHOLE_CONTEXT" PIHOLE_API_TOKEN = "PIHOLE_API_TOKEN" PIHOLE_PROTOCOL = "PIHOLE_PROTOCOL" ) @@ -32,6 +33,7 @@ func TestSplitDefault(t *testing.T) { clientConfig := clientConfigs[0] assert.Equal("127.0.0.1", clientConfig.PIHoleHostname) assert.Equal("http", clientConfig.PIHoleProtocol) + assert.Equal("", clientConfig.PIHoleContext) assert.Equal(uint16(80), clientConfig.PIHolePort) assert.Empty(clientConfig.PIHoleApiToken) assert.Empty(clientConfig.PIHolePassword) @@ -43,6 +45,7 @@ func TestSplitMultipleHostWithSameConfig(t *testing.T) { env := getDefaultEnvConfig() env.PIHoleHostname = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} env.PIHoleApiToken = []string{"api-token"} + env.PIHoleContext = []string{"foo"} env.PIHolePort = []uint16{8080} clientConfigs, err := env.Split() @@ -52,21 +55,25 @@ func TestSplitMultipleHostWithSameConfig(t *testing.T) { testCases := []struct { Host string Port uint16 + Context string Protocol string }{ { Host: "127.0.0.1", Port: 8080, + Context: "foo", Protocol: "http", }, { Host: "127.0.0.2", Port: 8080, + Context: "foo", Protocol: "http", }, { Host: "127.0.0.3", Port: 8080, + Context: "foo", Protocol: "http", }, } @@ -90,6 +97,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { env := getDefaultEnvConfig() env.PIHoleHostname = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} env.PIHoleApiToken = []string{"api-token1", "", "api-token3"} + env.PIHoleContext = []string{"", "foo", "bar"} env.PIHolePassword = []string{"", "password2", ""} env.PIHolePort = []uint16{8081, 8082, 8083} @@ -100,6 +108,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { testCases := []struct { Host string Port uint16 + Context string Protocol string ApiToken string Password string @@ -107,6 +116,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { { Host: "127.0.0.1", Port: 8081, + Context: "", Protocol: "http", ApiToken: "api-token1", Password: "", @@ -114,6 +124,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { { Host: "127.0.0.2", Port: 8082, + Context: "foo", Protocol: "http", ApiToken: "", Password: "password2", @@ -121,6 +132,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { { Host: "127.0.0.3", Port: 8083, + Context: "bar", Protocol: "http", ApiToken: "api-token3", Password: "", @@ -133,6 +145,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { assert.Equal(tc.Host, clientConfig.PIHoleHostname) assert.Equal(tc.Protocol, clientConfig.PIHoleProtocol) + assert.Equal(tc.Context, clientConfig.PIHoleContext) assert.Equal(tc.Port, clientConfig.PIHolePort) assert.Equal(tc.ApiToken, clientConfig.PIHoleApiToken) assert.Equal(tc.Password, clientConfig.PIHolePassword) From a4cc36166e68e7c682d73ddd3e9a7ba95105691e Mon Sep 17 00:00:00 2001 From: Pierre Cosson <962185+pierCo@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:47:19 +0100 Subject: [PATCH 3/5] feat(): Update tests --- config/configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/configuration.go b/config/configuration.go index 6d511fe..092fe1e 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -128,7 +128,7 @@ func (c EnvConfig) Split() ([]Config, error) { } else if len(c.PIHoleContext) == hostsCount { config.PIHoleContext = c.PIHoleContext[i] } else if len(c.PIHoleContext) != 0 { - return nil, errors.New("Wrong number of context. Context can be empty to use default, one value to use for all hosts, or match the number of hosts") + return nil, errors.New("Wrong number of contexts. Context can be empty to use default, one value to use for all hosts, or match the number of hosts") } if hasData, data, isValid := extractStringConfig(c.PIHoleProtocol, i, hostsCount); hasData { From 8c0e26f76aeda227dcb28a4ed892b6c9679a00d9 Mon Sep 17 00:00:00 2001 From: Pierre Cosson <962185+pierCo@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:49:28 +0100 Subject: [PATCH 4/5] feat(): Update tests --- config/configuration.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 092fe1e..689211e 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -123,12 +123,10 @@ func (c EnvConfig) Split() ([]Config, error) { 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") } - if len(c.PIHoleContext) == 1 { - config.PIHoleContext = c.PIHoleContext[0] - } else if len(c.PIHoleContext) == hostsCount { - config.PIHoleContext = c.PIHoleContext[i] - } else if len(c.PIHoleContext) != 0 { - return nil, errors.New("Wrong number of contexts. Context can be empty to use default, one value to use for all hosts, or match the number of hosts") + if hasData, data, isValid := extractStringConfig(c.PIHoleContext, i, hostsCount); hasData { + config.PIHoleContext = data + } else if !isValid { + return nil, errors.New("Wrong number of PIHoleContext. PIHoleContext can be empty to use default, one value to use for all hosts, or match the number of hosts") } if hasData, data, isValid := extractStringConfig(c.PIHoleProtocol, i, hostsCount); hasData { From aa2ce0f5c5fb8a5c747065e24653c67dc9fa7b24 Mon Sep 17 00:00:00 2001 From: Pierre Cosson <962185+pierCo@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:46:25 +0100 Subject: [PATCH 5/5] feat(): change context to admin_context + doc --- README.md | 3 + config/configuration.go | 68 ++++++++++----------- config/configuration_test.go | 114 ++++++++++++++++++++--------------- 3 files changed, 101 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 2284304..226bc72 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,9 @@ scrape_configs: # Address to be used for the exporter -bind_addr string (optional) (default "0.0.0.0") +# URL Context (first segments of URL path) to the PI-hole admin application + -pihole_admin_context string (optional) (default "admin") + # Port to be used for the exporter -port string (optional) (default "9617") ``` diff --git a/config/configuration.go b/config/configuration.go index 689211e..5bf9606 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -19,39 +19,39 @@ import ( // Config is the exporter CLI configuration. type Config struct { - PIHoleProtocol string `config:"pihole_protocol"` - PIHoleHostname string `config:"pihole_hostname"` - PIHolePort uint16 `config:"pihole_port"` - PIHoleContext string `config:"pihole_context"` - PIHolePassword string `config:"pihole_password"` - PIHoleApiToken string `config:"pihole_api_token"` - BindAddr string `config:"bind_addr"` - Port uint16 `config:"port"` + PIHoleProtocol string `config:"pihole_protocol"` + PIHoleHostname string `config:"pihole_hostname"` + PIHolePort uint16 `config:"pihole_port"` + PIHoleAdminContext string `config:"pihole_admin_context"` + PIHolePassword string `config:"pihole_password"` + PIHoleApiToken string `config:"pihole_api_token"` + BindAddr string `config:"bind_addr"` + Port uint16 `config:"port"` } type EnvConfig struct { - PIHoleProtocol []string `config:"pihole_protocol"` - PIHoleHostname []string `config:"pihole_hostname"` - PIHolePort []uint16 `config:"pihole_port"` - PIHoleContext []string `config:"pihole_context"` - PIHolePassword []string `config:"pihole_password"` - PIHoleApiToken []string `config:"pihole_api_token"` - BindAddr string `config:"bind_addr"` - Port uint16 `config:"port"` - Timeout time.Duration `config:"timeout"` + PIHoleProtocol []string `config:"pihole_protocol"` + PIHoleHostname []string `config:"pihole_hostname"` + PIHolePort []uint16 `config:"pihole_port"` + PIHoleAdminContext []string `config:"pihole_admin_context"` + PIHolePassword []string `config:"pihole_password"` + PIHoleApiToken []string `config:"pihole_api_token"` + BindAddr string `config:"bind_addr"` + Port uint16 `config:"port"` + Timeout time.Duration `config:"timeout"` } func getDefaultEnvConfig() *EnvConfig { return &EnvConfig{ - PIHoleProtocol: []string{"http"}, - PIHoleHostname: []string{"127.0.0.1"}, - PIHolePort: []uint16{80}, - PIHoleContext: []string{""}, - PIHolePassword: []string{}, - PIHoleApiToken: []string{}, - BindAddr: "0.0.0.0", - Port: 9617, - Timeout: 5 * time.Second, + PIHoleProtocol: []string{"http"}, + PIHoleHostname: []string{"127.0.0.1"}, + PIHolePort: []uint16{80}, + PIHoleAdminContext: []string{"admin"}, + PIHolePassword: []string{}, + PIHoleApiToken: []string{}, + BindAddr: "0.0.0.0", + Port: 9617, + Timeout: 5 * time.Second, } } @@ -123,10 +123,10 @@ func (c EnvConfig) Split() ([]Config, error) { 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") } - if hasData, data, isValid := extractStringConfig(c.PIHoleContext, i, hostsCount); hasData { - config.PIHoleContext = data + if hasData, data, isValid := extractStringConfig(c.PIHoleAdminContext, i, hostsCount); hasData { + config.PIHoleAdminContext = data } else if !isValid { - return nil, errors.New("Wrong number of PIHoleContext. PIHoleContext can be empty to use default, one value to use for all hosts, or match the number of hosts") + return nil, errors.New("Wrong number of PIHoleAdminContext. PIHoleAdminContext can be empty to use default, one value to use for all hosts, or match the number of hosts") } if hasData, data, isValid := extractStringConfig(c.PIHoleProtocol, i, hostsCount); hasData { @@ -182,25 +182,25 @@ func removeEmptyString(source []string) []string { return result } -func (c Config) piHoleURL() string { +func (c Config) piHoleAdminURL() string { s := fmt.Sprintf("%s://%s", c.PIHoleProtocol, c.PIHoleHostname) if c.PIHolePort != 0 { s += fmt.Sprintf(":%d", c.PIHolePort) } - if c.PIHoleContext != "" { - s += fmt.Sprintf("/%s", c.PIHoleContext) + if c.PIHoleAdminContext != "" { + s += fmt.Sprintf("/%s", c.PIHoleAdminContext) } return s } // PIHoleStatsURL returns the stats url func (c Config) PIHoleStatsURL() string { - return c.piHoleURL() + "/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&overTimeData10mins&jsonForceObject" + return c.piHoleAdminURL() + "/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&overTimeData10mins&jsonForceObject" } // PIHoleLoginURL returns the login url func (c Config) PIHoleLoginURL() string { - return c.piHoleURL() + "/admin/index.php?login" + return c.piHoleAdminURL() + "/index.php?login" } func (c EnvConfig) show() { diff --git a/config/configuration_test.go b/config/configuration_test.go index bf57a27..59295d0 100644 --- a/config/configuration_test.go +++ b/config/configuration_test.go @@ -8,11 +8,11 @@ import ( ) const ( - PIHOLE_HOSTNAME = "PIHOLE_HOSTNAME" - PIHOLE_PORT = "PIHOLE_PORT" - PIHOLE_CONTEXT = "PIHOLE_CONTEXT" - PIHOLE_API_TOKEN = "PIHOLE_API_TOKEN" - PIHOLE_PROTOCOL = "PIHOLE_PROTOCOL" + PIHOLE_HOSTNAME = "PIHOLE_HOSTNAME" + PIHOLE_PORT = "PIHOLE_PORT" + PIHOLE_ADMIN_CONTEXT = "PIHOLE_ADMIN_CONTEXT" + PIHOLE_API_TOKEN = "PIHOLE_API_TOKEN" + PIHOLE_PROTOCOL = "PIHOLE_PROTOCOL" ) type EnvInitiazlier func(*testing.T) @@ -33,7 +33,7 @@ func TestSplitDefault(t *testing.T) { clientConfig := clientConfigs[0] assert.Equal("127.0.0.1", clientConfig.PIHoleHostname) assert.Equal("http", clientConfig.PIHoleProtocol) - assert.Equal("", clientConfig.PIHoleContext) + assert.Equal("admin", clientConfig.PIHoleAdminContext) assert.Equal(uint16(80), clientConfig.PIHolePort) assert.Empty(clientConfig.PIHoleApiToken) assert.Empty(clientConfig.PIHolePassword) @@ -45,7 +45,7 @@ func TestSplitMultipleHostWithSameConfig(t *testing.T) { env := getDefaultEnvConfig() env.PIHoleHostname = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} env.PIHoleApiToken = []string{"api-token"} - env.PIHoleContext = []string{"foo"} + env.PIHoleAdminContext = []string{"foo"} env.PIHolePort = []uint16{8080} clientConfigs, err := env.Split() @@ -53,28 +53,28 @@ func TestSplitMultipleHostWithSameConfig(t *testing.T) { assert.Len(clientConfigs, 3) testCases := []struct { - Host string - Port uint16 - Context string - Protocol string + Host string + Port uint16 + AdminContext string + Protocol string }{ { - Host: "127.0.0.1", - Port: 8080, - Context: "foo", - Protocol: "http", + Host: "127.0.0.1", + Port: 8080, + AdminContext: "foo", + Protocol: "http", }, { - Host: "127.0.0.2", - Port: 8080, - Context: "foo", - Protocol: "http", + Host: "127.0.0.2", + Port: 8080, + AdminContext: "foo", + Protocol: "http", }, { - Host: "127.0.0.3", - Port: 8080, - Context: "foo", - Protocol: "http", + Host: "127.0.0.3", + Port: 8080, + AdminContext: "foo", + Protocol: "http", }, } @@ -97,7 +97,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { env := getDefaultEnvConfig() env.PIHoleHostname = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} env.PIHoleApiToken = []string{"api-token1", "", "api-token3"} - env.PIHoleContext = []string{"", "foo", "bar"} + env.PIHoleAdminContext = []string{"", "foo", "bar"} env.PIHolePassword = []string{"", "password2", ""} env.PIHolePort = []uint16{8081, 8082, 8083} @@ -106,36 +106,36 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { assert.Len(clientConfigs, 3) testCases := []struct { - Host string - Port uint16 - Context string - Protocol string - ApiToken string - Password string + Host string + Port uint16 + AdminContext string + Protocol string + ApiToken string + Password string }{ { - Host: "127.0.0.1", - Port: 8081, - Context: "", - Protocol: "http", - ApiToken: "api-token1", - Password: "", + Host: "127.0.0.1", + Port: 8081, + AdminContext: "", + Protocol: "http", + ApiToken: "api-token1", + Password: "", }, { - Host: "127.0.0.2", - Port: 8082, - Context: "foo", - Protocol: "http", - ApiToken: "", - Password: "password2", + Host: "127.0.0.2", + Port: 8082, + AdminContext: "foo", + Protocol: "http", + ApiToken: "", + Password: "password2", }, { - Host: "127.0.0.3", - Port: 8083, - Context: "bar", - Protocol: "http", - ApiToken: "api-token3", - Password: "", + Host: "127.0.0.3", + Port: 8083, + AdminContext: "bar", + Protocol: "http", + ApiToken: "api-token3", + Password: "", }, } @@ -145,7 +145,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { assert.Equal(tc.Host, clientConfig.PIHoleHostname) assert.Equal(tc.Protocol, clientConfig.PIHoleProtocol) - assert.Equal(tc.Context, clientConfig.PIHoleContext) + assert.Equal(tc.AdminContext, clientConfig.PIHoleAdminContext) assert.Equal(tc.Port, clientConfig.PIHolePort) assert.Equal(tc.ApiToken, clientConfig.PIHoleApiToken) assert.Equal(tc.Password, clientConfig.PIHolePassword) @@ -153,7 +153,7 @@ func TestSplitMultipleHostWithMultipleConfigs(t *testing.T) { } } -func TestWrongParams(t *testing.T) { +func TestWrongNumberOfApiTokenParams(t *testing.T) { assert := assert.New(t) env := getDefaultEnvConfig() @@ -165,3 +165,17 @@ func TestWrongParams(t *testing.T) { assert.Errorf(err, "Wrong number of PIHoleApiToken. PIHoleApiToken can be empty to use default, one value to use for all hosts, or match the number of hosts") assert.Nil(clientConfigs) } + +func TestWrongNumberOfAdminContextParams(t *testing.T) { + assert := assert.New(t) + + env := getDefaultEnvConfig() + env.PIHoleHostname = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} + env.PIHoleApiToken = []string{"api-token"} + env.PIHoleAdminContext = []string{"admin1", "admin2"} + env.PIHolePort = []uint16{808} + + clientConfigs, err := env.Split() + assert.Errorf(err, "Wrong number of PIHoleAdminContext. PIHoleAdminContext can be empty to use default, one value to use for all hosts, or match the number of hosts") + assert.Nil(clientConfigs) +}