Compare commits

...

3 Commits

Author SHA1 Message Date
Marc Brugger
5fca3b1002 better readable env vars (#270)
* better readable env vars
2024-01-08 19:39:47 +01:00
bakito
1edf5ae52a add filter test cases 2024-01-08 19:20:31 +01:00
bakito
39f7f41e6d update readme 2024-01-07 22:44:32 +01:00
9 changed files with 311 additions and 158 deletions

View File

@@ -146,7 +146,7 @@ services:
environment: environment:
LOG_LEVEL: "info" LOG_LEVEL: "info"
ORIGIN_URL: "https://192.168.1.2:3000" ORIGIN_URL: "https://192.168.1.2:3000"
# ORIGIN_WEBURL: "https://some-other.url" # used in the web interface (default: <origin-url> # ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
ORIGIN_USERNAME: "username" ORIGIN_USERNAME: "username"
ORIGIN_PASSWORD: "password" ORIGIN_PASSWORD: "password"
@@ -156,29 +156,29 @@ services:
REPLICA1_URL: "http://192.168.1.4" REPLICA1_URL: "http://192.168.1.4"
REPLICA1_USERNAME: "username" REPLICA1_USERNAME: "username"
REPLICA1_PASSWORD: "password" REPLICA1_PASSWORD: "password"
REPLICA1_APIPATH: "/some/path/control" REPLICA1_API_PATH: "/some/path/control"
# REPLICA1_WEBURL: "https://some-other.url" # used in the web interface (default: <replica-url> # REPLICA1_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
# REPLICA1_AUTOSETUP: true # if true, AdGuardHome is automatically initialized. # REPLICA1_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA1_INTERFACENAME: 'ens18' # use custom dhcp interface name # REPLICA1_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
# REPLICA1_DHCPSERVERENABLED: true/false (optional) enables/disables the dhcp server on the replica # REPLICA1_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
CRON: "*/10 * * * *" # run every 10 minutes CRON: "*/10 * * * *" # run every 10 minutes
RUNONSTART: true RUNONSTART: true
# CONTINUEONERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue # CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
# Configure the sync API server, disabled if api port is 0 # Configure the sync API server, disabled if api port is 0
API_PORT: 8080 API_PORT: 8080
# Configure sync features; by default all features are enabled. # Configure sync features; by default all features are enabled.
# FEATURES_GENERALSETTINGS: true # FEATURES_GENERAL_SETTINGS: true
# FEATURES_QUERYLOGCONFIG: true # FEATURES_QUERY_LOG_CONFIG: true
# FEATURES_STATSCONFIG: true # FEATURES_STATS_CONFIG: true
# FEATURES_CLIENTSETTINGS: true # FEATURES_CLIENT_SETTINGS: true
# FEATURES_SERVICES: true # FEATURES_SERVICES: true
# FEATURES_FILTERS: true # FEATURES_FILTERS: true
# FEATURES_DHCP_SERVERCONFIG: true # FEATURES_DHCP_SERVER_CONFIG: true
# FEATURES_DHCP_STATICLEASES: true # FEATURES_DHCP_STATIC_LEASES: true
# FEATURES_DNS_SERVERCONFIG: true # FEATURES_DNS_SERVER_CONFIG: true
# FEATURES_DNS_ACCESSLISTS: true # FEATURES_DNS_ACCESS_LISTS: true
# FEATURES_DNS_REWRITES: true # FEATURES_DNS_REWRITES: true
ports: ports:
- 8080:8080 - 8080:8080

View File

@@ -4,10 +4,12 @@ import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/version" "github.com/bakito/adguardhome-sync/version"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -15,56 +17,45 @@ import (
) )
const ( const (
configCron = "cron" configCron = "CRON"
configRunOnStart = "runOnStart" configRunOnStart = "RUN_ON_START"
configPrintConfigOnly = "PRINT_CONFIG_ONLY" configPrintConfigOnly = "PRINT_CONFIG_ONLY"
configContinueOnError = "CONTINUE_ON_ERROR" configContinueOnError = "CONTINUE_ON_ERROR"
configLogLevel = "LOG_LEVEL"
configAPIPort = "api.port" configAPIPort = "API.PORT"
configAPIUsername = "api.username" configAPIUsername = "API.USERNAME"
configAPIPassword = "api.password" configAPIPassword = "API.PASSWORD"
configAPIDarkMode = "api.darkMode" configAPIDarkMode = "API.DARK_MODE"
configFeatureDHCPServerConfig = "features.dhcp.serverConfig" configFeatureDHCPServerConfig = "FEATURES.DHCP.SERVER_CONFIG"
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases" configFeatureDHCPStaticLeases = "FEATURES.DHCP.STATIC_LEASES"
configFeatureDNServerConfig = "features.dns.serverConfig" configFeatureDNServerConfig = "FEATURES.DNS.SERVER_CONFIG"
configFeatureDNSPAccessLists = "features.dns.accessLists" configFeatureDNSPAccessLists = "FEATURES.DNS.ACCESS_LISTS"
configFeatureDNSRewrites = "features.dns.rewrites" configFeatureDNSRewrites = "FEATURES.DNS.rewrites"
configFeatureGeneralSettings = "features.generalSettings" configFeatureGeneralSettings = "FEATURES.GENERAL_SETTINGS"
configFeatureQueryLogConfig = "features.queryLogConfig" configFeatureQueryLogConfig = "FEATURES.QUERY_LOG_CONFIG"
configFeatureStatsConfig = "features.statsConfig" configFeatureStatsConfig = "FEATURES.STATS_CONFIG"
configFeatureClientSettings = "features.clientSettings" configFeatureClientSettings = "FEATURES.CLIENT_SETTINGS"
configFeatureServices = "features.services" configFeatureServices = "FEATURES.SERVICES"
configFeatureFilters = "features.filters" configFeatureFilters = "FEATURES.FILTERS"
configOriginURL = "origin.url" configOriginURL = "ORIGIN.URL"
configOriginWebURL = "origin.webURL" configOriginWebURL = "ORIGIN.WEB_URL"
configOriginAPIPath = "origin.apiPath" configOriginAPIPath = "ORIGIN.API_PATH"
configOriginUsername = "origin.username" configOriginUsername = "ORIGIN.USERNAME"
configOriginPassword = "origin.password" configOriginPassword = "ORIGIN.PASSWORD"
configOriginCookie = "origin.cookie" configOriginCookie = "ORIGIN.COOKIE"
configOriginInsecureSkipVerify = "origin.insecureSkipVerify" configOriginInsecureSkipVerify = "ORIGIN.INSECURE_SKIP_VERIFY"
configReplicaURL = "replica.url" configReplicaURL = "REPLICA.URL"
configReplicaWebURL = "replica.webURL" configReplicaWebURL = "REPLICA.WEB_URL"
configReplicaAPIPath = "replica.apiPath" configReplicaAPIPath = "REPLICA.API_PATH"
configReplicaUsername = "replica.username" configReplicaUsername = "REPLICA.USERNAME"
configReplicaPassword = "replica.password" configReplicaPassword = "REPLICA.PASSWORD"
configReplicaCookie = "replica.cookie" configReplicaCookie = "REPLICA.COOKIE"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify" configReplicaInsecureSkipVerify = "REPLICA.INSECURE_SKIP_VERIFY"
configReplicaAutoSetup = "replica.autoSetup" configReplicaAutoSetup = "REPLICA.AUTO_SETUP"
configReplicaInterfaceName = "replica.interfaceName" configReplicaInterfaceName = "REPLICA.INTERFACE_NAME"
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
envReplicasCookieFormat = "REPLICA%s_COOKIE" // #nosec G101
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
envReplicasInterfaceName = "REPLICA%s_INTERFACENAME"
envDHCPServerEnabled = "REPLICA%s_DHCPSERVERENABLED"
envWebURL = "REPLICA%s_WEBURL"
) )
var ( var (
@@ -147,33 +138,116 @@ func getConfig() (*types.Config, error) {
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...) cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
} }
handleDeprecatedEnvVars(cfg)
return cfg, nil return cfg, nil
} }
func handleDeprecatedEnvVars(cfg *types.Config) {
value := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START")
if value != "" {
cfg.RunOnStart, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE")
if value != "" {
cfg.API.DarkMode, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS")
if value != "" {
cfg.Features.GeneralSettings, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG")
if value != "" {
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG")
if value != "" {
cfg.Features.StatsConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS")
if value != "" {
cfg.Features.ClientSettings, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG")
if value != "" {
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES")
if value != "" {
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS")
if value != "" {
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG")
if value != "" {
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(value)
}
if cfg.Replica != nil {
value = checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL")
if value != "" {
cfg.Replica.WebURL = value
}
value = checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP")
if value != "" {
cfg.Replica.AutoSetup, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME")
if value != "" {
cfg.Replica.InterfaceName = value
}
value = checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED")
if value != "" {
if b, err := strconv.ParseBool(value); err != nil {
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
}
}
}
}
func checkDeprecatedEnvVar(oldName string, newName string) string {
old, oldOK := os.LookupEnv(oldName)
if oldOK {
logger.With("deprecated", oldName, "replacement", newName).
Warn("Deprecated env variable is used, please use the correct one")
}
new, newOK := os.LookupEnv(newName)
if newOK {
return new
}
return old
}
func checkDeprecatedReplicaEnvVar(oldPattern, newPattern, replicaID string) string {
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
}
// Manually collect replicas from env. // Manually collect replicas from env.
func collectEnvReplicas() []types.AdGuardInstance { func collectEnvReplicas() []types.AdGuardInstance {
var replicas []types.AdGuardInstance var replicas []types.AdGuardInstance
for _, v := range os.Environ() { for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) { if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v) sm := envReplicasURLPattern.FindStringSubmatch(v)
index := sm[1]
re := types.AdGuardInstance{ re := types.AdGuardInstance{
URL: sm[2], URL: sm[2],
WebURL: os.Getenv(fmt.Sprintf(envWebURL, sm[1])), WebURL: os.Getenv(fmt.Sprintf("REPLICA%s_WEB_URL", index)),
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])), APIPath: checkDeprecatedReplicaEnvVar("REPLICA%s_APIPATH", "REPLICA%s_API_PATH", index),
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])), Username: os.Getenv(fmt.Sprintf("REPLICA%s_USERNAME", index)),
Cookie: os.Getenv(fmt.Sprintf(envReplicasCookieFormat, sm[1])), Password: os.Getenv(fmt.Sprintf("REPLICA%s_PASSWORD", index)),
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])), Cookie: os.Getenv(fmt.Sprintf("REPLICA%s_COOKIE", index)),
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"), InsecureSkipVerify: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_INSECURESKIPVERIFY", "REPLICA%s_INSECURE_SKIP_VERIFY", index), "true"),
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"), AutoSetup: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_AUTOSETUP", "REPLICA%s_AUTO_SETUP", index), "true"),
InterfaceName: os.Getenv(fmt.Sprintf(envReplicasInterfaceName, sm[1])), InterfaceName: checkDeprecatedReplicaEnvVar("REPLICA%s_INTERFACENAME", "REPLICA%s_INTERFACE_NAME", index),
} }
if dhcpEnabled, ok := os.LookupEnv(fmt.Sprintf(envDHCPServerEnabled, sm[1])); ok { dhcpEnabled := checkDeprecatedReplicaEnvVar("REPLICA%s_DHCPSERVERENABLED", "REPLICA%s_DHCP_SERVER_ENABLED", index)
if strings.EqualFold(dhcpEnabled, "true") { if strings.EqualFold(dhcpEnabled, "true") {
re.DHCPServerEnabled = boolPtr(true) re.DHCPServerEnabled = utils.Ptr(true)
} else if strings.EqualFold(dhcpEnabled, "false") { } else if strings.EqualFold(dhcpEnabled, "false") {
re.DHCPServerEnabled = boolPtr(false) re.DHCPServerEnabled = utils.Ptr(false)
}
} }
if re.APIPath == "" { if re.APIPath == "" {
re.APIPath = "/control" re.APIPath = "/control"
@@ -184,7 +258,3 @@ func collectEnvReplicas() []types.AdGuardInstance {
return replicas return replicas
} }
func boolPtr(b bool) *bool {
return &b
}

View File

@@ -10,6 +10,22 @@ import (
) )
var envVars = []string{ var envVars = []string{
"FEATURES_GENERAL_SETTINGS",
"FEATURES_QUERY_LOG_CONFIG",
"FEATURES_STATS_CONFIG",
"FEATURES_CLIENT_SETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVER_CONFIG",
"FEATURES_DHCP_STATIC_LEASES",
"FEATURES_DNS_SERVER_CONFIG",
"FEATURES_DNS_ACCESS_LISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACE_NAME",
"REPLICA1_DHCP_SERVER_ENABLED",
}
var deprecatedEnvVars = []string{
"FEATURES_GENERALSETTINGS", "FEATURES_GENERALSETTINGS",
"FEATURES_QUERYLOGCONFIG", "FEATURES_QUERYLOGCONFIG",
"FEATURES_STATSCONFIG", "FEATURES_STATSCONFIG",
@@ -26,56 +42,83 @@ var envVars = []string{
} }
var _ = Describe("Run", func() { var _ = Describe("Run", func() {
BeforeEach(func() { Context("deprecated", func() {
for _, envVar := range envVars { BeforeEach(func() {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred()) for _, envVar := range deprecatedEnvVars {
}
initConfig()
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
It("features should be true by default", func() {
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred()) Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
} }
cfg, err := getConfig() initConfig()
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
}) })
Context("interface name", func() { AfterEach(func() {
It("should set interface name of replica 1", func() { for _, envVar := range deprecatedEnvVars {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred()) Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred()) }
})
Context("getConfig", func() {
It("features should be false", func() {
cfg, err := getConfig() cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0")) verifyFeatures(cfg, false)
}) })
}) })
Context("dhcp server", func() { })
It("should enable the dhcp server of replica 1", func() { Context("current", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred()) BeforeEach(func() {
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "true")).ShouldNot(HaveOccurred()) for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
initConfig()
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
It("features should be true by default", func() {
cfg, err := getConfig() cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) verifyFeatures(cfg, true)
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
}) })
It("should disable the dhcp server of replica 1", func() { It("features should be true by default", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := getConfig() cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) verifyFeatures(cfg, true)
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse()) })
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
Context("interface name", func() {
It("should set interface name of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
})
Context("dhcp server", func() {
It("should enable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
})
It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
}) })
}) })
}) })

View File

@@ -54,9 +54,6 @@ func init() {
"will not fail on single errors, but will log the errors and continue.") "will not fail on single errors, but will log the errors and continue.")
_ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError")) _ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError"))
doCmd.PersistentFlags().String("logLevel", "info", "The log level to set. One of (debug, info, warn and error)")
_ = viper.BindPFlag(configLogLevel, doCmd.PersistentFlags().Lookup("logLevel"))
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.") doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port")) _ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
doCmd.PersistentFlags().String("api-username", "", "Sync API username") doCmd.PersistentFlags().String("api-username", "", "Sync API username")
@@ -93,8 +90,8 @@ func init() {
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url") doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url")) _ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
doCmd.PersistentFlags().String("origin-weburl", "", "Origin instance web url used in the web interface (default: <origin-url>)") doCmd.PersistentFlags().String("origin-web-url", "", "Origin instance web url used in the web interface (default: <origin-url>)")
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-weburl")) _ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-web-url"))
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path") doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path")) _ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username") doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
@@ -108,8 +105,8 @@ func init() {
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url") doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url")) _ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
doCmd.PersistentFlags().String("replica-weburl", "", "Replica instance web url used in the web interface (default: <replica-url>)") doCmd.PersistentFlags().String("replica-web-url", "", "Replica instance web url used in the web interface (default: <replica-url>)")
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("replica-weburl")) _ = viper.BindPFlag(configReplicaWebURL, doCmd.PersistentFlags().Lookup("replica-web-url"))
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path") doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path")) _ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username") doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")

2
go.mod
View File

@@ -62,7 +62,7 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.16.0 // indirect

4
go.sum
View File

@@ -171,8 +171,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=

View File

@@ -425,6 +425,50 @@ var _ = Describe("Sync", func() {
err := actionFilters(ac) err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should add a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete a filter", func() {
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().DeleteFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}})
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should abort after failed added filter", func() {
ac.continueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
err := actionFilters(ac)
Ω(err).Should(HaveOccurred())
})
It("should continue after failed added filter", func() {
ac.continueOnError = true
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
}) })
Context("actionDNSAccessLists", func() { Context("actionDNSAccessLists", func() {

View File

@@ -6,26 +6,26 @@ import (
// Features feature flags // Features feature flags
type Features struct { type Features struct {
DNS DNS `json:"dns" yaml:"dns"` DNS DNS `json:"dns" yaml:"dns" mapstructure:"DNS"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"` DHCP DHCP `json:"dhcp" yaml:"dhcp" mapstructure:"DHCP"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings"` GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" mapstructure:"GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig"` QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" mapstructure:"QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig"` StatsConfig bool `json:"statsConfig" yaml:"statsConfig" mapstructure:"STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings"` ClientSettings bool `json:"clientSettings" yaml:"clientSettings" mapstructure:"CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services"` Services bool `json:"services" yaml:"services" mapstructure:"SERVICES"`
Filters bool `json:"filters" yaml:"filters"` Filters bool `json:"filters" yaml:"filters" mapstructure:"FILTERS"`
} }
// DHCP features // DHCP features
type DHCP struct { type DHCP struct {
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"` ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases"` StaticLeases bool `json:"staticLeases" yaml:"staticLeases" mapstructure:"STATIC_LEASES"`
} }
// DNS features // DNS features
type DNS struct { type DNS struct {
AccessLists bool `json:"accessLists" yaml:"accessLists"` AccessLists bool `json:"accessLists" yaml:"accessLists" mapstructure:"ACCESS_LISTS"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"` ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"`
Rewrites bool `json:"rewrites" yaml:"rewrites"` Rewrites bool `json:"rewrites" yaml:"rewrites"`
} }

View File

@@ -15,24 +15,23 @@ const (
// Config application configuration struct // Config application configuration struct
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
type Config struct { type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin"` Origin AdGuardInstance `json:"origin" yaml:"origin" mapstructure:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"` Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" mapstructure:"REPLICA"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"` Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"` Cron string `json:"cron,omitempty" yaml:"cron,omitempty" mapstructure:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"` RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" mapstructure:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" mapstructure:"PRINT_CONFIG_ONLY"` PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" mapstructure:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" mapstructure:"CONTINUE_ON_ERROR"` ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" mapstructure:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty"` API API `json:"api,omitempty" yaml:"api,omitempty" mapstructure:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty"` Features Features `json:"features,omitempty" yaml:"features,omitempty" mapstructure:"FEATURES"`
LogLevel string `json:"logLevel,omitempty" yaml:"logLevel,omitempty" mapstructure:"LOG_LEVEL"`
} }
// API configuration // API configuration
type API struct { type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty"` Port int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty"` Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty"` Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty"` DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" mapstructure:"DARK_MODE"`
} }
// UniqueReplicas get unique replication instances // UniqueReplicas get unique replication instances
@@ -90,16 +89,16 @@ func (cfg *Config) Init() error {
// AdGuardInstance AdguardHome config instance // AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
type AdGuardInstance struct { type AdGuardInstance struct {
URL string `json:"url" yaml:"url"` URL string `json:"url" yaml:"url" mapstructure:"URL"`
WebURL string `json:"webURL" yaml:"webURL"` WebURL string `json:"webURL" yaml:"webURL" mapstructure:"WEB_URL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"` APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" mapstructure:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty"` Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty"` Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty"` Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" mapstructure:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"` InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" mapstructure:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"` AutoSetup bool `json:"autoSetup" yaml:"autoSetup" mapstructure:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"` InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" mapstructure:"INTERFACE_NAME"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"` DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" mapstructure:"DHCP_SERVER_ENABLED"`
Host string `json:"-" yaml:"-"` Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"` WebHost string `json:"-" yaml:"-"`