From 4c1e56ccce4d6625fc8cfa0bf3edf9269d6ab856 Mon Sep 17 00:00:00 2001 From: Marc Brugger Date: Sun, 14 Jan 2024 13:29:36 +0100 Subject: [PATCH] Fix config issues with new env library (#273) * correct config issues #271 #272 * rename type tags * replace env lib * move to config module * read flags * show e2e logs on error * extract env * replace deprecated env var * increment index * check replica numbers do not start with 0 * remove test suite * error handling * refactor flags * flags test * file test * file test * config tests * extend tests * test mixed mode * simplify * simplify * test mask * correct uniqe replicas * Update types_test.go * e2e test with file mode --- .github/workflows/e2e.yaml | 7 +- .gitignore | 2 +- Makefile | 1 + README.md | 11 +- cmd/root.go | 221 +------------- cmd/run.go | 111 +++---- go.mod | 17 +- go.sum | 36 +-- pkg/config/config.go | 84 ++++++ .../config/config_suite_test.go | 4 +- pkg/config/config_test.go | 194 ++++++++++++ .../config/deprecated_env_test.go | 25 +- pkg/config/env.go | 139 +++++++++ pkg/config/env_test.go | 25 ++ pkg/config/file.go | 34 +++ pkg/config/file_test.go | 32 ++ pkg/config/flag-names.go | 44 +++ pkg/config/flags.go | 283 ++++++++++++++++++ pkg/config/flags_test.go | 235 +++++++++++++++ pkg/mocks/flags/mock.go | 93 ++++++ pkg/types/features.go | 59 ++-- pkg/types/types.go | 71 +++-- pkg/types/types_test.go | 109 +++++-- testdata/.gitignore | 1 + testdata/config_test_replica.yaml | 36 +++ testdata/config_test_replicas.yaml | 36 +++ .../config_test_replicas_and_replica.yaml | 45 +++ testdata/e2e/bin/install-chart.sh | 2 +- testdata/e2e/bin/wait-for-sync.sh | 6 +- .../e2e/templates/configmap-sync-env.yaml | 18 ++ .../e2e/templates/configmap-sync-file.yaml | 22 ++ testdata/e2e/templates/configmap-sync.yaml | 17 -- ...ync.yaml => pod-adguardhome-sync-env.yaml} | 7 +- .../templates/pod-adguardhome-sync-file.yaml | 36 +++ testdata/e2e/values.yaml | 6 + 35 files changed, 1614 insertions(+), 455 deletions(-) create mode 100644 pkg/config/config.go rename cmd/cmd_suite_test.go => pkg/config/config_suite_test.go (74%) create mode 100644 pkg/config/config_test.go rename cmd/root_test.go => pkg/config/deprecated_env_test.go (90%) create mode 100644 pkg/config/env.go create mode 100644 pkg/config/env_test.go create mode 100644 pkg/config/file.go create mode 100644 pkg/config/file_test.go create mode 100644 pkg/config/flag-names.go create mode 100644 pkg/config/flags.go create mode 100644 pkg/config/flags_test.go create mode 100644 pkg/mocks/flags/mock.go create mode 100644 testdata/config_test_replica.yaml create mode 100644 testdata/config_test_replicas.yaml create mode 100644 testdata/config_test_replicas_and_replica.yaml create mode 100644 testdata/e2e/templates/configmap-sync-env.yaml create mode 100644 testdata/e2e/templates/configmap-sync-file.yaml delete mode 100644 testdata/e2e/templates/configmap-sync.yaml rename testdata/e2e/templates/{pod-adguardhome-sync.yaml => pod-adguardhome-sync-env.yaml} (75%) create mode 100644 testdata/e2e/templates/pod-adguardhome-sync-file.yaml diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 09611a1..3b06737 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -11,6 +11,11 @@ on: jobs: e2e: runs-on: ubuntu-latest + strategy: + matrix: + build: + - mode: env + - mode: file steps: - name: Checkout uses: actions/checkout@v4 @@ -22,7 +27,7 @@ jobs: run: ./testdata/e2e/bin/build-image.sh - name: Install Helm Chart - run: ./testdata/e2e/bin/install-chart.sh + run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }} - name: Wait for sync to finish run: ./testdata/e2e/bin/wait-for-sync.sh - name: Show origin Logs diff --git a/.gitignore b/.gitignore index f26fce9..c138d54 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ main .adguardhome-sync.yaml tmp bin -config.yaml +config*.yaml *.log diff --git a/Makefile b/Makefile index f4a9cc8..8ff46d5 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ test-ci: mocks tidy mocks: mockgen $(MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client + $(MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags release: semver goreleaser @version=$$($(LOCALBIN)/semver); \ diff --git a/README.md b/README.md index dc7a8c5..3ba1160 100644 --- a/README.md +++ b/README.md @@ -237,16 +237,7 @@ origin: password: password # cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE -# replica instance (optional, if only one) -replica: - # url of the replica instance - url: http://192.168.1.3 - username: username - password: password - # cookie: Replica-Cookie-Name=CCCOOOKKKIIIEEE - # webURL: "https://some-other.url" # used in the web interface (default: - -# replicas instances (optional, if more than one) +# replicas instances replicas: # url of the replica instance - url: http://192.168.1.3 diff --git a/cmd/root.go b/cmd/root.go index 1d83f70..595eceb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,65 +3,15 @@ package cmd import ( "fmt" "os" - "regexp" - "strconv" - "strings" "github.com/bakito/adguardhome-sync/pkg/log" - "github.com/bakito/adguardhome-sync/pkg/types" - "github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/version" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -const ( - configCron = "CRON" - configRunOnStart = "RUN_ON_START" - configPrintConfigOnly = "PRINT_CONFIG_ONLY" - configContinueOnError = "CONTINUE_ON_ERROR" - - configAPIPort = "API.PORT" - configAPIUsername = "API.USERNAME" - configAPIPassword = "API.PASSWORD" - configAPIDarkMode = "API.DARK_MODE" - - configFeatureDHCPServerConfig = "FEATURES.DHCP.SERVER_CONFIG" - configFeatureDHCPStaticLeases = "FEATURES.DHCP.STATIC_LEASES" - configFeatureDNServerConfig = "FEATURES.DNS.SERVER_CONFIG" - configFeatureDNSPAccessLists = "FEATURES.DNS.ACCESS_LISTS" - configFeatureDNSRewrites = "FEATURES.DNS.rewrites" - configFeatureGeneralSettings = "FEATURES.GENERAL_SETTINGS" - configFeatureQueryLogConfig = "FEATURES.QUERY_LOG_CONFIG" - configFeatureStatsConfig = "FEATURES.STATS_CONFIG" - configFeatureClientSettings = "FEATURES.CLIENT_SETTINGS" - configFeatureServices = "FEATURES.SERVICES" - configFeatureFilters = "FEATURES.FILTERS" - - configOriginURL = "ORIGIN.URL" - configOriginWebURL = "ORIGIN.WEB_URL" - configOriginAPIPath = "ORIGIN.API_PATH" - configOriginUsername = "ORIGIN.USERNAME" - configOriginPassword = "ORIGIN.PASSWORD" - configOriginCookie = "ORIGIN.COOKIE" - configOriginInsecureSkipVerify = "ORIGIN.INSECURE_SKIP_VERIFY" - - configReplicaURL = "REPLICA.URL" - configReplicaWebURL = "REPLICA.WEB_URL" - configReplicaAPIPath = "REPLICA.API_PATH" - configReplicaUsername = "REPLICA.USERNAME" - configReplicaPassword = "REPLICA.PASSWORD" - configReplicaCookie = "REPLICA.COOKIE" - configReplicaInsecureSkipVerify = "REPLICA.INSECURE_SKIP_VERIFY" - configReplicaAutoSetup = "REPLICA.AUTO_SETUP" - configReplicaInterfaceName = "REPLICA.INTERFACE_NAME" ) var ( - cfgFile string - logger = log.GetLogger("root") - envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`) + cfgFile string + logger = log.GetLogger("root") ) // rootCmd represents the base command when called without any subcommands @@ -81,8 +31,6 @@ func Execute() { } func init() { - cobra.OnInitialize(initConfig) - // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. @@ -93,168 +41,3 @@ func init() { // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Search config in home directory with name ".adguardhome-sync" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".adguardhome-sync") - } - viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - logger.Info("Using config file:", viper.ConfigFileUsed()) - } else if cfgFile != "" { - fmt.Println(err) - os.Exit(1) - } -} - -func getConfig() (*types.Config, error) { - cfg := &types.Config{} - if err := viper.Unmarshal(cfg); err != nil { - return nil, err - } - if cfg.Replica != nil && - cfg.Replica.URL == "" && - cfg.Replica.Username == "" { - cfg.Replica = nil - } - - if len(cfg.Replicas) == 0 { - cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...) - } - - handleDeprecatedEnvVars(cfg) - - 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. -func collectEnvReplicas() []types.AdGuardInstance { - var replicas []types.AdGuardInstance - for _, v := range os.Environ() { - if envReplicasURLPattern.MatchString(v) { - sm := envReplicasURLPattern.FindStringSubmatch(v) - index := sm[1] - re := types.AdGuardInstance{ - URL: sm[2], - WebURL: os.Getenv(fmt.Sprintf("REPLICA%s_WEB_URL", index)), - APIPath: checkDeprecatedReplicaEnvVar("REPLICA%s_APIPATH", "REPLICA%s_API_PATH", index), - Username: os.Getenv(fmt.Sprintf("REPLICA%s_USERNAME", index)), - Password: os.Getenv(fmt.Sprintf("REPLICA%s_PASSWORD", index)), - Cookie: os.Getenv(fmt.Sprintf("REPLICA%s_COOKIE", index)), - InsecureSkipVerify: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_INSECURESKIPVERIFY", "REPLICA%s_INSECURE_SKIP_VERIFY", index), "true"), - AutoSetup: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_AUTOSETUP", "REPLICA%s_AUTO_SETUP", index), "true"), - InterfaceName: checkDeprecatedReplicaEnvVar("REPLICA%s_INTERFACENAME", "REPLICA%s_INTERFACE_NAME", index), - } - - dhcpEnabled := checkDeprecatedReplicaEnvVar("REPLICA%s_DHCPSERVERENABLED", "REPLICA%s_DHCP_SERVER_ENABLED", index) - if strings.EqualFold(dhcpEnabled, "true") { - re.DHCPServerEnabled = utils.Ptr(true) - } else if strings.EqualFold(dhcpEnabled, "false") { - re.DHCPServerEnabled = utils.Ptr(false) - } - if re.APIPath == "" { - re.APIPath = "/control" - } - replicas = append(replicas, re) - } - } - - return replicas -} diff --git a/cmd/run.go b/cmd/run.go index 6f870d0..bba67ed 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,10 +1,10 @@ package cmd import ( + "github.com/bakito/adguardhome-sync/pkg/config" "github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/sync" "github.com/spf13/cobra" - "github.com/spf13/viper" "gopkg.in/yaml.v3" ) @@ -15,7 +15,7 @@ var doCmd = &cobra.Command{ Long: `Synchronizes the configuration form an origin instance to a replica`, RunE: func(cmd *cobra.Command, args []string) error { logger = log.GetLogger("run") - cfg, err := getConfig() + cfg, err := config.Get(cfgFile, cmd.Flags()) if err != nil { logger.Error(err) return err @@ -43,82 +43,47 @@ var doCmd = &cobra.Command{ func init() { rootCmd.AddCommand(doCmd) - doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode") - _ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron")) - doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.") - _ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart")) - doCmd.PersistentFlags().Bool("printConfigOnly", false, "Prints the configuration only and exists. "+ + doCmd.PersistentFlags().String(config.FlagCron, "", "The cron expression to run in daemon mode") + doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.") + doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+ "Can be used to debug the config E.g: when having authentication issues.") - _ = viper.BindPFlag(configPrintConfigOnly, doCmd.PersistentFlags().Lookup("printConfigOnly")) - doCmd.PersistentFlags().Bool("continueOnError", false, "If enabled, the synchronisation task "+ + doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+ "will not fail on single errors, but will log the errors and continue.") - _ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError")) - 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")) - doCmd.PersistentFlags().String("api-username", "", "Sync API username") - _ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username")) - doCmd.PersistentFlags().String("api-password", "", "Sync API password") - _ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password")) - doCmd.PersistentFlags().String("api-darkMode", "", "API UI in dark mode") - _ = viper.BindPFlag(configAPIDarkMode, doCmd.PersistentFlags().Lookup("api-darkMode")) + doCmd.PersistentFlags().Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.") + doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username") + doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password") + doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode") - doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature") - _ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config")) - doCmd.PersistentFlags().Bool("feature-dhcp-static-leases", true, "Enable DHCP server static leases feature") - _ = viper.BindPFlag(configFeatureDHCPStaticLeases, doCmd.PersistentFlags().Lookup("feature-dhcp-static-leases")) + doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature") - doCmd.PersistentFlags().Bool("feature-dns-server-config", true, "Enable DNS server config feature") - _ = viper.BindPFlag(configFeatureDNServerConfig, doCmd.PersistentFlags().Lookup("feature-dns-server-config")) - doCmd.PersistentFlags().Bool("feature-dns-access-lists", true, "Enable DNS server access lists feature") - _ = viper.BindPFlag(configFeatureDNSPAccessLists, doCmd.PersistentFlags().Lookup("feature-dns-access-lists")) - doCmd.PersistentFlags().Bool("feature-dns-rewrites", true, "Enable DNS rewrites feature") - _ = viper.BindPFlag(configFeatureDNSRewrites, doCmd.PersistentFlags().Lookup("feature-dns-rewrites")) - doCmd.PersistentFlags().Bool("feature-general-settings", true, "Enable general settings feature") - _ = viper.BindPFlag(configFeatureGeneralSettings, doCmd.PersistentFlags().Lookup("feature-general-settings")) - _ = viper.BindPFlag("features.generalSettings", doCmd.PersistentFlags().Lookup("feature-general-settings")) - doCmd.PersistentFlags().Bool("feature-query-log-config", true, "Enable query log config feature") - _ = viper.BindPFlag(configFeatureQueryLogConfig, doCmd.PersistentFlags().Lookup("feature-query-log-config")) - doCmd.PersistentFlags().Bool("feature-stats-config", true, "Enable stats config feature") - _ = viper.BindPFlag(configFeatureStatsConfig, doCmd.PersistentFlags().Lookup("feature-stats-config")) - doCmd.PersistentFlags().Bool("feature-client-settings", true, "Enable client settings feature") - _ = viper.BindPFlag(configFeatureClientSettings, doCmd.PersistentFlags().Lookup("feature-client-settings")) - doCmd.PersistentFlags().Bool("feature-services", true, "Enable services sync feature") - _ = viper.BindPFlag(configFeatureServices, doCmd.PersistentFlags().Lookup("feature-services")) - doCmd.PersistentFlags().Bool("feature-filters", true, "Enable filters sync feature") - _ = viper.BindPFlag(configFeatureFilters, doCmd.PersistentFlags().Lookup("feature-filters")) + doCmd.PersistentFlags().Bool(config.FlagFeatureDnsServerConfig, true, "Enable DNS server config feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureDnsAccessLists, true, "Enable DNS server access lists feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature") - doCmd.PersistentFlags().String("origin-url", "", "Origin instance url") - _ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url")) - doCmd.PersistentFlags().String("origin-web-url", "", "Origin instance web url used in the web interface (default: )") - _ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-web-url")) - doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path") - _ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path")) - doCmd.PersistentFlags().String("origin-username", "", "Origin instance username") - _ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username")) - doCmd.PersistentFlags().String("origin-password", "", "Origin instance password") - _ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password")) - doCmd.PersistentFlags().String("origin-cookie", "", "If Set, uses a cookie for authentication") - _ = viper.BindPFlag(configOriginCookie, doCmd.PersistentFlags().Lookup("origin-cookie")) - doCmd.PersistentFlags().Bool("origin-insecure-skip-verify", false, "Enable Origin instance InsecureSkipVerify") - _ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify")) + doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureStats, true, "Enable stats config feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature") + doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature") - doCmd.PersistentFlags().String("replica-url", "", "Replica instance url") - _ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url")) - doCmd.PersistentFlags().String("replica-web-url", "", "Replica instance web url used in the web interface (default: )") - _ = viper.BindPFlag(configReplicaWebURL, doCmd.PersistentFlags().Lookup("replica-web-url")) - doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path") - _ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path")) - doCmd.PersistentFlags().String("replica-username", "", "Replica instance username") - _ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username")) - doCmd.PersistentFlags().String("replica-password", "", "Replica instance password") - _ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password")) - doCmd.PersistentFlags().String("replica-cookie", "", "If Set, uses a cookie for authentication") - _ = viper.BindPFlag(configReplicaCookie, doCmd.PersistentFlags().Lookup("replica-cookie")) - doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify") - _ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify")) - doCmd.PersistentFlags().Bool("replica-auto-setup", false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.") - _ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup")) - doCmd.PersistentFlags().String("replica-interface-name", "", "Optional change the interface name of the replica if it differs from the master") - _ = viper.BindPFlag(configReplicaInterfaceName, doCmd.PersistentFlags().Lookup("replica-interface-name")) + doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url") + doCmd.PersistentFlags().String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: )") + doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path") + doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username") + doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password") + doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication") + doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify") + + doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url") + doCmd.PersistentFlags().String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: )") + doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path") + doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username") + doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password") + doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication") + doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify") + doCmd.PersistentFlags().Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.") + doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master") } diff --git a/go.mod b/go.mod index e3f23d5..162b33e 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,17 @@ module github.com/bakito/adguardhome-sync go 1.21 require ( + github.com/caarlos0/env/v10 v10.0.0 github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.9.1 github.com/golang/mock v1.6.0 github.com/google/uuid v1.5.0 github.com/jinzhu/copier v0.4.0 - github.com/mitchellh/go-homedir v1.1.0 github.com/oapi-codegen/runtime v1.1.1 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 go.uber.org/zap v1.26.0 golang.org/x/mod v0.14.0 gopkg.in/yaml.v3 v3.0.1 @@ -26,7 +25,7 @@ require ( github.com/bytedance/sonic v1.10.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.3.0 // indirect @@ -39,31 +38,22 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.2.4 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.18.0 // indirect - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect @@ -71,7 +61,6 @@ require ( google.golang.org/protobuf v1.32.0 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/go.sum b/go.sum index 77372a3..4a7828f 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,10 +23,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -63,8 +61,6 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -79,22 +75,14 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -115,25 +103,11 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -147,8 +121,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -173,8 +145,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= 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/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -252,8 +222,6 @@ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLF gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..2080eed --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,84 @@ +package config + +import ( + "errors" + "regexp" + + "github.com/bakito/adguardhome-sync/pkg/log" + "github.com/bakito/adguardhome-sync/pkg/types" + "github.com/caarlos0/env/v10" +) + +var ( + envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`) + logger = log.GetLogger("config") +) + +func Get(configFile string, flags Flags) (*types.Config, error) { + path, err := configFilePath(configFile) + if err != nil { + return nil, err + } + + cfg := initialConfig() + + // read yaml config + if err := readFile(cfg, path); err != nil { + return nil, err + } + + // overwrite from command flags + if err := readFlags(cfg, flags); err != nil { + return nil, err + } + + // overwrite from env vars + + if err := env.Parse(cfg); err != nil { + return nil, err + } + if err := env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil { + return nil, err + } + if err := env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil { + return nil, err + } + + if cfg.Replica != nil && + cfg.Replica.URL == "" && + cfg.Replica.Username == "" { + cfg.Replica = nil + } + + if len(cfg.Replicas) > 0 && cfg.Replica != nil { + return nil, errors.New("mixed replica config in use. " + + "Do not use single replica and numbered (list) replica config combined") + } + + handleDeprecatedEnvVars(cfg) + + if cfg.Replica != nil { + cfg.Replicas = []types.AdGuardInstance{*cfg.Replica} + cfg.Replica = nil + } + + cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas) + + return cfg, err +} + +func initialConfig() *types.Config { + return &types.Config{ + RunOnStart: true, + Origin: types.AdGuardInstance{ + APIPath: "/control", + }, + Replica: &types.AdGuardInstance{ + APIPath: "/control", + }, + API: types.API{ + Port: 8080, + }, + Features: types.NewFeatures(true), + } +} diff --git a/cmd/cmd_suite_test.go b/pkg/config/config_suite_test.go similarity index 74% rename from cmd/cmd_suite_test.go rename to pkg/config/config_suite_test.go index 0278cc0..ff57615 100644 --- a/cmd/cmd_suite_test.go +++ b/pkg/config/config_suite_test.go @@ -1,4 +1,4 @@ -package cmd_test +package config_test import ( "testing" @@ -9,5 +9,5 @@ import ( func TestCmd(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Cmd Suite") + RunSpecs(t, "Config Suite") } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..25bf6c2 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,194 @@ +package config_test + +import ( + "os" + + "github.com/bakito/adguardhome-sync/pkg/config" + flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags" + gm "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Config", func() { + Context("Get", func() { + var ( + flags *flagsmock.MockFlags + mockCtrl *gm.Controller + ) + BeforeEach(func() { + mockCtrl = gm.NewController(GinkgoT()) + flags = flagsmock.NewMockFlags(mockCtrl) + }) + AfterEach(func() { + defer mockCtrl.Finish() + }) + Context("Get", func() { + Context("Mixed Config", func() { + It("should have the origin URL from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + _, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags) + Ω(err).Should(HaveOccurred()) + Ω(err.Error()).Should(ContainSubstring("mixed replica config in use")) + }) + }) + Context("Origin Url", func() { + It("should have the origin URL from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443")) + }) + It("should have the origin URL from the config flags", func() { + flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443")) + }) + It("should have the origin URL from the config env var", func() { + os.Setenv("ORIGIN_URL", "https://origin-env:443") + defer func() { + _ = os.Unsetenv("ORIGIN_URL") + }() + flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443")) + }) + }) + Context("Replica insecure skip verify", func() { + It("should have the insecure skip verify from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) + }) + It("should have the insecure skip verify from the config flags", func() { + flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue()) + }) + It("should have the insecure skip verify from the config env var", func() { + os.Setenv("REPLICA_INSECURE_SKIP_VERIFY", "false") + defer func() { + _ = os.Unsetenv("REPLICA_INSECURE_SKIP_VERIFY") + }() + flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) + }) + }) + + Context("Replica 1 insecure skip verify", func() { + It("should have the insecure skip verify from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) + }) + It("should have the insecure skip verify from the config env var", func() { + os.Setenv("REPLICA1_INSECURE_SKIP_VERIFY", "true") + defer func() { + _ = os.Unsetenv("REPLICA1_INSECURE_SKIP_VERIFY") + }() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue()) + }) + }) + Context("API Port", func() { + It("should have the api port from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.API.Port).Should(Equal(9090)) + }) + It("should have the api port from the config flags", func() { + flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.API.Port).Should(Equal(9990)) + }) + It("should have the api port from the config env var", func() { + os.Setenv("API_PORT", "9999") + defer func() { + _ = os.Unsetenv("API_PORT") + }() + flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.API.Port).Should(Equal(9999)) + }) + }) + Context("Feature DNS Server Config", func() { + It("should have the feature dns server config from the config file", func() { + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) + }) + It("should have the feature dns server config from the config flags", func() { + flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue()) + }) + It("should have the feature dns server config from the config env var", func() { + os.Setenv("FEATURES_DNS_SERVER_CONFIG", "false") + defer func() { + _ = os.Unsetenv("FEATURES_DNS_SERVER_CONFIG") + }() + flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) + }) + It("should have the feature dns server config from the config DEPRECATED env var", func() { + os.Setenv("FEATURES_DNS_SERVERCONFIG", "false") + defer func() { + _ = os.Unsetenv("FEATURES_DNS_SERVERCONFIG") + }() + flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() + + cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) + }) + }) + }) + }) +}) diff --git a/cmd/root_test.go b/pkg/config/deprecated_env_test.go similarity index 90% rename from cmd/root_test.go rename to pkg/config/deprecated_env_test.go index c5d2de5..51169d3 100644 --- a/cmd/root_test.go +++ b/pkg/config/deprecated_env_test.go @@ -1,9 +1,10 @@ -package cmd +package config_test import ( "fmt" "os" + "github.com/bakito/adguardhome-sync/pkg/config" "github.com/bakito/adguardhome-sync/pkg/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -41,22 +42,21 @@ var deprecatedEnvVars = []string{ "REPLICA1_DHCPSERVERENABLED", } -var _ = Describe("Run", func() { +var _ = Describe("Config", func() { Context("deprecated", func() { BeforeEach(func() { for _, envVar := range deprecatedEnvVars { Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred()) } - initConfig() }) AfterEach(func() { for _, envVar := range deprecatedEnvVars { Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred()) } }) - Context("getConfig", func() { + Context("Get", func() { It("features should be false", func() { - cfg, err := getConfig() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) verifyFeatures(cfg, false) }) @@ -67,21 +67,20 @@ var _ = Describe("Run", func() { for _, envVar := range envVars { Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred()) } - initConfig() }) AfterEach(func() { for _, envVar := range envVars { Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred()) } }) - Context("getConfig", func() { + Context("Get", func() { It("features should be true by default", func() { - cfg, err := getConfig() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) verifyFeatures(cfg, true) }) It("features should be true by default", func() { - cfg, err := getConfig() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) verifyFeatures(cfg, true) }) @@ -89,7 +88,7 @@ var _ = Describe("Run", func() { for _, envVar := range envVars { Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred()) } - cfg, err := getConfig() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) verifyFeatures(cfg, false) }) @@ -97,7 +96,7 @@ var _ = Describe("Run", 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() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0")) }) @@ -106,7 +105,7 @@ var _ = Describe("Run", 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() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue()) @@ -114,7 +113,7 @@ var _ = Describe("Run", func() { 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() + cfg, err := config.Get("", nil) Ω(err).ShouldNot(HaveOccurred()) Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse()) diff --git a/pkg/config/env.go b/pkg/config/env.go new file mode 100644 index 0000000..69c0075 --- /dev/null +++ b/pkg/config/env.go @@ -0,0 +1,139 @@ +package config + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/bakito/adguardhome-sync/pkg/types" + "github.com/bakito/adguardhome-sync/pkg/utils" + "github.com/caarlos0/env/v10" +) + +func handleDeprecatedEnvVars(cfg *types.Config) { + if val, ok := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START"); ok { + cfg.RunOnStart, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE"); ok { + cfg.API.DarkMode, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS"); ok { + cfg.Features.GeneralSettings, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG"); ok { + cfg.Features.QueryLogConfig, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG"); ok { + cfg.Features.StatsConfig, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS"); ok { + cfg.Features.ClientSettings, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG"); ok { + cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES"); ok { + cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS"); ok { + cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG"); ok { + cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(val) + } + + if cfg.Replica != nil { + if val, ok := checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL"); ok { + cfg.Replica.WebURL = val + } + if val, ok := checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP"); ok { + cfg.Replica.AutoSetup, _ = strconv.ParseBool(val) + } + if val, ok := checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME"); ok { + cfg.Replica.InterfaceName = val + } + if val, ok := checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED"); ok { + if b, err := strconv.ParseBool(val); err != nil { + cfg.Replica.DHCPServerEnabled = utils.Ptr(b) + } + } + } +} + +func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) { + 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, true + } + return old, oldOK +} + +func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) { + return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID)) +} + +// Manually collect replicas from env. +func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) { + var replicas []types.AdGuardInstance + for _, v := range os.Environ() { + if envReplicasURLPattern.MatchString(v) { + sm := envReplicasURLPattern.FindStringSubmatch(v) + id, _ := strconv.Atoi(sm[1]) + + if id <= 0 { + return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v) + } + + if id > len(initialReplicas) { + replicas = append(replicas, types.AdGuardInstance{URL: sm[2]}) + } else { + re := initialReplicas[id-1] + re.URL = sm[2] + replicas = append(replicas, re) + } + } + } + + if len(replicas) == 0 { + replicas = initialReplicas + } + + for i := range replicas { + reID := i + 1 + + if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil { + return nil, err + } + if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok { + replicas[i].APIPath = val + } + if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INSECURESKIPVERIFY", "REPLICA%d_INSECURE_SKIP_VERIFY", reID); ok { + replicas[i].InsecureSkipVerify = strings.EqualFold(val, "true") + } + if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_AUTOSETUP", "REPLICA%d_AUTO_SETUP", reID); ok { + replicas[i].AutoSetup = strings.EqualFold(val, "true") + } + if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INTERFACENAME", "REPLICA%d_INTERFACE_NAME", reID); ok { + replicas[i].InterfaceName = val + } + + if dhcpEnabled, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_DHCPSERVERENABLED", "REPLICA%d_DHCP_SERVER_ENABLED", reID); ok { + if strings.EqualFold(dhcpEnabled, "true") { + replicas[i].DHCPServerEnabled = utils.Ptr(true) + } else if strings.EqualFold(dhcpEnabled, "false") { + replicas[i].DHCPServerEnabled = utils.Ptr(false) + } + } + if replicas[i].APIPath == "" { + replicas[i].APIPath = "/control" + } + } + + return replicas, nil +} diff --git a/pkg/config/env_test.go b/pkg/config/env_test.go new file mode 100644 index 0000000..d3f34e2 --- /dev/null +++ b/pkg/config/env_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Config", func() { + Context("env", func() { + Context("enrichReplicasFromEnv", func() { + It("should have the origin URL from the config env var", func() { + _ = os.Setenv("REPLICA0_URL", "https://origin-env:443") + defer func() { + _ = os.Unsetenv("REPLICA0_URL") + }() + _, err := enrichReplicasFromEnv(nil) + + Ω(err).Should(HaveOccurred()) + Ω(err.Error()).Should(ContainSubstring("numbered replica env variables must have a number id >= 1")) + }) + }) + }) +}) diff --git a/pkg/config/file.go b/pkg/config/file.go new file mode 100644 index 0000000..b315206 --- /dev/null +++ b/pkg/config/file.go @@ -0,0 +1,34 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/bakito/adguardhome-sync/pkg/types" + "gopkg.in/yaml.v3" +) + +func readFile(cfg *types.Config, path string) error { + if _, err := os.Stat(path); err == nil { + b, err := os.ReadFile(path) + if err != nil { + return err + } + if err := yaml.Unmarshal(b, cfg); err != nil { + return err + } + } + return nil +} + +func configFilePath(configFile string) (string, error) { + if configFile == "" { + // Find home directory. + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, ".adguardhome-sync"), nil + } + return configFile, nil +} diff --git a/pkg/config/file_test.go b/pkg/config/file_test.go new file mode 100644 index 0000000..55b6c9c --- /dev/null +++ b/pkg/config/file_test.go @@ -0,0 +1,32 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Config", func() { + var () + BeforeEach(func() { + }) + Context("configFilePath", func() { + It("should return the same value", func() { + path := uuid.NewString() + result, err := configFilePath(path) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(result).Should(Equal(path)) + }) + It("should the file in HOME dir", func() { + home := os.Getenv("HOME") + result, err := configFilePath("") + + Ω(err).ShouldNot(HaveOccurred()) + Ω(result).Should(Equal(filepath.Join(home, "/.adguardhome-sync"))) + }) + }) +}) diff --git a/pkg/config/flag-names.go b/pkg/config/flag-names.go new file mode 100644 index 0000000..34ce7cd --- /dev/null +++ b/pkg/config/flag-names.go @@ -0,0 +1,44 @@ +package config + +const ( + FlagCron = "cron" + FlagRunOnStart = "runOnStart" + FlagPrintConfigOnly = "printConfigOnly" + FlagContinueOnError = "continueOnError" + + FlagApiPort = "api-port" + FlagApiUsername = "api-username" + FlagApiPassword = "api-password" + FlagApiDarkMode = "api-dark-mode" + + FlagFeatureDhcpServerConfig = "feature-dhcp-server-config" + FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases" + FlagFeatureDnsServerConfig = "feature-dns-server-config" + FlagFeatureDnsAccessLists = "feature-dns-access-lists" + FlagFeatureDnsRewrites = "feature-dns-rewrites" + FlagFeatureGeneral = "feature-general-settings" + FlagFeatureQueryLog = "feature-query-log-config" + FlagFeatureStats = "feature-stats-config" + FlagFeatureClient = "feature-client-settings" + FlagFeatureServices = "feature-services" + FlagFeatureFilters = "feature-filters" + + FlagOriginURL = "origin-url" + FlagOriginWebURL = "origin-web-url" + FlagOriginApiPath = "origin-api-path" + FlagOriginUsername = "origin-username" + + FlagOriginPassword = "origin-password" + FlagOriginCookie = "origin-cookie" + FlagOriginISV = "origin-insecure-skip-verify" + + FlagReplicaURL = "replica-url" + FlagReplicaWebURL = "replica-web-url" + FlagReplicaApiPath = "replica-api-path" + FlagReplicaUsername = "replica-username" + FlagReplicaPassword = "replica-password" + FlagReplicaCookie = "replica-cookie" + FlagReplicaISV = "replica-insecure-skip-verify" + FlagReplicaAutoSetup = "replica-auto-setup" + FlagReplicaInterfaceName = "replica-interface-name" +) diff --git a/pkg/config/flags.go b/pkg/config/flags.go new file mode 100644 index 0000000..42dc2e4 --- /dev/null +++ b/pkg/config/flags.go @@ -0,0 +1,283 @@ +package config + +import ( + "github.com/bakito/adguardhome-sync/pkg/types" +) + +func readFlags(cfg *types.Config, flags Flags) error { + if flags == nil { + return nil + } + + fr := &flagReader{ + cfg: cfg, + flags: flags, + } + + if err := fr.readRootFlags(); err != nil { + return err + } + + if err := fr.readApiFlags(); err != nil { + return err + } + + if err := fr.readFeatureFlags(); err != nil { + return err + } + + if err := fr.readOriginFlags(); err != nil { + return err + } + + if err := fr.readReplicaFlags(); err != nil { + return err + } + + return nil +} + +type flagReader struct { + cfg *types.Config + flags Flags +} + +func (fr *flagReader) readReplicaFlags() error { + if err := fr.setStringFlag(FlagReplicaURL, func(cgf *types.Config, value string) { + fr.cfg.Replica.URL = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaWebURL, func(cgf *types.Config, value string) { + fr.cfg.Replica.WebURL = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) { + fr.cfg.Replica.APIPath = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaUsername, func(cgf *types.Config, value string) { + fr.cfg.Replica.Username = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaPassword, func(cgf *types.Config, value string) { + fr.cfg.Replica.Password = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaCookie, func(cgf *types.Config, value string) { + fr.cfg.Replica.Cookie = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagReplicaISV, func(cgf *types.Config, value bool) { + fr.cfg.Replica.InsecureSkipVerify = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagReplicaAutoSetup, func(cgf *types.Config, value bool) { + fr.cfg.Replica.AutoSetup = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) { + fr.cfg.Replica.InterfaceName = value + }); err != nil { + return err + } + return nil +} + +func (fr *flagReader) readOriginFlags() error { + if err := fr.setStringFlag(FlagOriginURL, func(cgf *types.Config, value string) { + fr.cfg.Origin.URL = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagOriginWebURL, func(cgf *types.Config, value string) { + fr.cfg.Origin.WebURL = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) { + fr.cfg.Origin.APIPath = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagOriginUsername, func(cgf *types.Config, value string) { + fr.cfg.Origin.Username = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagOriginPassword, func(cgf *types.Config, value string) { + fr.cfg.Origin.Password = value + }); err != nil { + return err + } + if err := fr.setStringFlag(FlagOriginCookie, func(cgf *types.Config, value string) { + fr.cfg.Origin.Cookie = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) { + fr.cfg.Origin.InsecureSkipVerify = value + }); err != nil { + return err + } + return nil +} + +func (fr *flagReader) readFeatureFlags() error { + if err := fr.setBoolFlag(FlagFeatureDhcpServerConfig, func(cgf *types.Config, value bool) { + fr.cfg.Features.DHCP.ServerConfig = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureDhcpStaticLeases, func(cgf *types.Config, value bool) { + fr.cfg.Features.DHCP.StaticLeases = value + }); err != nil { + return err + } + + if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) { + fr.cfg.Features.DNS.ServerConfig = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) { + fr.cfg.Features.DNS.AccessLists = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) { + fr.cfg.Features.DNS.Rewrites = value + }); err != nil { + return err + } + + if err := fr.setBoolFlag(FlagFeatureGeneral, func(cgf *types.Config, value bool) { + fr.cfg.Features.GeneralSettings = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureQueryLog, func(cgf *types.Config, value bool) { + fr.cfg.Features.QueryLogConfig = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureStats, func(cgf *types.Config, value bool) { + fr.cfg.Features.StatsConfig = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureClient, func(cgf *types.Config, value bool) { + fr.cfg.Features.ClientSettings = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureServices, func(cgf *types.Config, value bool) { + fr.cfg.Features.Services = value + }); err != nil { + return err + } + if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) { + fr.cfg.Features.Filters = value + }); err != nil { + return err + } + + return nil +} + +func (fr *flagReader) readApiFlags() (err error) { + if err = fr.setIntFlag(FlagApiPort, func(cgf *types.Config, value int) { + fr.cfg.API.Port = value + }); err != nil { + return + } + if err = fr.setStringFlag(FlagApiUsername, func(cgf *types.Config, value string) { + fr.cfg.API.Username = value + }); err != nil { + return + } + if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) { + fr.cfg.API.Password = value + }); err != nil { + return + } + if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) { + fr.cfg.API.DarkMode = value + }); err != nil { + return + } + return +} + +func (fr *flagReader) readRootFlags() (err error) { + if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) { + fr.cfg.Cron = value + }); err != nil { + return + } + if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) { + fr.cfg.RunOnStart = value + }); err != nil { + return + } + if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) { + fr.cfg.PrintConfigOnly = value + }); err != nil { + return + } + if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) { + fr.cfg.ContinueOnError = value + }); err != nil { + return + } + return +} + +type Flags interface { + Changed(name string) bool + GetString(name string) (string, error) + GetInt(name string) (int, error) + GetBool(name string) (bool, error) +} + +func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) { + if fr.flags.Changed(name) { + if value, err := fr.flags.GetString(name); err != nil { + return err + } else { + cb(fr.cfg, value) + } + } + return nil +} + +func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) { + if fr.flags.Changed(name) { + if value, err := fr.flags.GetBool(name); err != nil { + return err + } else { + cb(fr.cfg, value) + } + } + return nil +} + +func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) { + if fr.flags.Changed(name) { + if value, err := fr.flags.GetInt(name); err != nil { + return err + } else { + cb(fr.cfg, value) + } + } + return nil +} + +type callback[T any] func(cgf *types.Config, value T) diff --git a/pkg/config/flags_test.go b/pkg/config/flags_test.go new file mode 100644 index 0000000..92fa138 --- /dev/null +++ b/pkg/config/flags_test.go @@ -0,0 +1,235 @@ +package config + +import ( + "strings" + + flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags" + "github.com/bakito/adguardhome-sync/pkg/types" + gm "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Config", func() { + var ( + cfg *types.Config + flags *flagsmock.MockFlags + mockCtrl *gm.Controller + ) + BeforeEach(func() { + cfg = &types.Config{ + Replica: &types.AdGuardInstance{}, + Features: types.Features{ + DNS: types.DNS{ + AccessLists: true, + ServerConfig: true, + Rewrites: true, + }, + DHCP: types.DHCP{ + ServerConfig: true, + StaticLeases: true, + }, + GeneralSettings: true, + QueryLogConfig: true, + StatsConfig: true, + ClientSettings: true, + Services: true, + Filters: true, + }, + } + mockCtrl = gm.NewController(GinkgoT()) + flags = flagsmock.NewMockFlags(mockCtrl) + }) + AfterEach(func() { + defer mockCtrl.Finish() + }) + Context("readFlags", func() { + It("should not change the config with nil flags", func() { + clone := cfg.DeepCopy() + err := readFlags(cfg, nil) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg).Should(Equal(clone)) + }) + It("should not change the config with no changed flags", func() { + clone := cfg.DeepCopy() + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg).Should(Equal(clone)) + }) + }) + Context("readFeatureFlags", func() { + It("should disable all flags", func() { + flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool { + return strings.HasPrefix(name, "feature") + }).AnyTimes() + flags.EXPECT().GetBool(gm.Any()).Return(false, nil).AnyTimes() + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Features).Should(Equal(types.Features{ + DNS: types.DNS{ + AccessLists: false, + ServerConfig: false, + Rewrites: false, + }, + DHCP: types.DHCP{ + ServerConfig: false, + StaticLeases: false, + }, + GeneralSettings: false, + QueryLogConfig: false, + StatsConfig: false, + ClientSettings: false, + Services: false, + Filters: false, + })) + }) + }) + Context("readApiFlags", func() { + It("should change all values", func() { + cfg.API = types.API{ + Port: 1111, + Username: "2222", + Password: "3333", + DarkMode: false, + } + flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool { + return strings.HasPrefix(name, "api") + }).AnyTimes() + flags.EXPECT().GetInt(FlagApiPort).Return(9999, nil) + flags.EXPECT().GetString(FlagApiUsername).Return("aaaa", nil) + flags.EXPECT().GetString(FlagApiPassword).Return("bbbb", nil) + flags.EXPECT().GetBool(FlagApiDarkMode).Return(true, nil) + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.API).Should(Equal(types.API{ + Port: 9999, + Username: "aaaa", + Password: "bbbb", + DarkMode: true, + })) + }) + }) + Context("readRootFlags", func() { + It("should change all values", func() { + cfg.Cron = "*/10 * * * *" + cfg.PrintConfigOnly = false + cfg.ContinueOnError = false + cfg.RunOnStart = false + + flags.EXPECT().Changed(FlagCron).Return(true) + flags.EXPECT().Changed(FlagRunOnStart).Return(true) + flags.EXPECT().Changed(FlagPrintConfigOnly).Return(true) + flags.EXPECT().Changed(FlagContinueOnError).Return(true) + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + flags.EXPECT().GetString(FlagCron).Return("*/30 * * * *", nil) + flags.EXPECT().GetBool(FlagRunOnStart).Return(true, nil) + flags.EXPECT().GetBool(FlagPrintConfigOnly).Return(true, nil) + flags.EXPECT().GetBool(FlagContinueOnError).Return(true, nil) + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Cron).Should(Equal("*/30 * * * *")) + Ω(cfg.RunOnStart).Should(BeTrue()) + Ω(cfg.PrintConfigOnly).Should(BeTrue()) + Ω(cfg.ContinueOnError).Should(BeTrue()) + }) + }) + Context("readOriginFlags", func() { + It("should change all values", func() { + cfg.Origin = types.AdGuardInstance{ + URL: "1", + WebURL: "2", + APIPath: "3", + Username: "4", + Password: "5", + Cookie: "6", + InsecureSkipVerify: false, + } + + flags.EXPECT().Changed(FlagOriginURL).Return(true) + flags.EXPECT().Changed(FlagOriginWebURL).Return(true) + flags.EXPECT().Changed(FlagOriginApiPath).Return(true) + flags.EXPECT().Changed(FlagOriginUsername).Return(true) + flags.EXPECT().Changed(FlagOriginPassword).Return(true) + flags.EXPECT().Changed(FlagOriginCookie).Return(true) + flags.EXPECT().Changed(FlagOriginISV).Return(true) + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + flags.EXPECT().GetString(FlagOriginURL).Return("a", nil) + flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil) + flags.EXPECT().GetString(FlagOriginApiPath).Return("c", nil) + flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil) + flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil) + flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil) + flags.EXPECT().GetBool(FlagOriginISV).Return(true, nil) + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Origin).Should(Equal(types.AdGuardInstance{ + URL: "a", + WebURL: "b", + APIPath: "c", + Username: "d", + Password: "e", + Cookie: "f", + InsecureSkipVerify: true, + })) + }) + }) + Context("readReplicaFlags", func() { + It("should change all values", func() { + cfg.Replica = &types.AdGuardInstance{ + URL: "1", + WebURL: "2", + APIPath: "3", + Username: "4", + Password: "5", + Cookie: "6", + InsecureSkipVerify: false, + AutoSetup: false, + InterfaceName: "7", + } + + flags.EXPECT().Changed(FlagReplicaURL).Return(true) + flags.EXPECT().Changed(FlagReplicaWebURL).Return(true) + flags.EXPECT().Changed(FlagReplicaApiPath).Return(true) + flags.EXPECT().Changed(FlagReplicaUsername).Return(true) + flags.EXPECT().Changed(FlagReplicaPassword).Return(true) + flags.EXPECT().Changed(FlagReplicaCookie).Return(true) + flags.EXPECT().Changed(FlagReplicaISV).Return(true) + flags.EXPECT().Changed(FlagReplicaAutoSetup).Return(true) + flags.EXPECT().Changed(FlagReplicaInterfaceName).Return(true) + flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() + + flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil) + flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil) + flags.EXPECT().GetString(FlagReplicaApiPath).Return("c", nil) + flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil) + flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil) + flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil) + flags.EXPECT().GetBool(FlagReplicaISV).Return(true, nil) + flags.EXPECT().GetBool(FlagReplicaAutoSetup).Return(true, nil) + flags.EXPECT().GetString(FlagReplicaInterfaceName).Return("g", nil) + err := readFlags(cfg, flags) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(cfg.Replica).Should(Equal(&types.AdGuardInstance{ + URL: "a", + WebURL: "b", + APIPath: "c", + Username: "d", + Password: "e", + Cookie: "f", + InsecureSkipVerify: true, + AutoSetup: true, + InterfaceName: "g", + })) + }) + }) +}) diff --git a/pkg/mocks/flags/mock.go b/pkg/mocks/flags/mock.go new file mode 100644 index 0000000..2a22a52 --- /dev/null +++ b/pkg/mocks/flags/mock.go @@ -0,0 +1,93 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags) + +// Package client is a generated GoMock package. +package client + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockFlags is a mock of Flags interface. +type MockFlags struct { + ctrl *gomock.Controller + recorder *MockFlagsMockRecorder +} + +// MockFlagsMockRecorder is the mock recorder for MockFlags. +type MockFlagsMockRecorder struct { + mock *MockFlags +} + +// NewMockFlags creates a new mock instance. +func NewMockFlags(ctrl *gomock.Controller) *MockFlags { + mock := &MockFlags{ctrl: ctrl} + mock.recorder = &MockFlagsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFlags) EXPECT() *MockFlagsMockRecorder { + return m.recorder +} + +// Changed mocks base method. +func (m *MockFlags) Changed(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Changed", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Changed indicates an expected call of Changed. +func (mr *MockFlagsMockRecorder) Changed(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), arg0) +} + +// GetBool mocks base method. +func (m *MockFlags) GetBool(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBool", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBool indicates an expected call of GetBool. +func (mr *MockFlagsMockRecorder) GetBool(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), arg0) +} + +// GetInt mocks base method. +func (m *MockFlags) GetInt(arg0 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInt", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInt indicates an expected call of GetInt. +func (mr *MockFlagsMockRecorder) GetInt(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), arg0) +} + +// GetString mocks base method. +func (m *MockFlags) GetString(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetString", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetString indicates an expected call of GetString. +func (mr *MockFlagsMockRecorder) GetString(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), arg0) +} diff --git a/pkg/types/features.go b/pkg/types/features.go index 465bdaf..657f4f8 100644 --- a/pkg/types/features.go +++ b/pkg/types/features.go @@ -4,33 +4,61 @@ import ( "go.uber.org/zap" ) +func NewFeatures(enabled bool) Features { + return Features{ + DNS: DNS{ + AccessLists: enabled, + ServerConfig: enabled, + Rewrites: enabled, + }, + DHCP: DHCP{ + ServerConfig: enabled, + StaticLeases: enabled, + }, + GeneralSettings: enabled, + QueryLogConfig: enabled, + StatsConfig: enabled, + ClientSettings: enabled, + Services: enabled, + Filters: enabled, + } +} + // Features feature flags type Features struct { - DNS DNS `json:"dns" yaml:"dns" mapstructure:"DNS"` - DHCP DHCP `json:"dhcp" yaml:"dhcp" mapstructure:"DHCP"` - GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" mapstructure:"GENERAL_SETTINGS"` - QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" mapstructure:"QUERY_LOG_CONFIG"` - StatsConfig bool `json:"statsConfig" yaml:"statsConfig" mapstructure:"STATS_CONFIG"` - ClientSettings bool `json:"clientSettings" yaml:"clientSettings" mapstructure:"CLIENT_SETTINGS"` - Services bool `json:"services" yaml:"services" mapstructure:"SERVICES"` - Filters bool `json:"filters" yaml:"filters" mapstructure:"FILTERS"` + DNS DNS `json:"dns" yaml:"dns"` + DHCP DHCP `json:"dhcp" yaml:"dhcp"` + GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"` + QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"` + StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"` + ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"` + Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"` + Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"` } // DHCP features type DHCP struct { - ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"` - StaticLeases bool `json:"staticLeases" yaml:"staticLeases" mapstructure:"STATIC_LEASES"` + ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"` + StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"` } // DNS features type DNS struct { - AccessLists bool `json:"accessLists" yaml:"accessLists" mapstructure:"ACCESS_LISTS"` - ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"` - Rewrites bool `json:"rewrites" yaml:"rewrites"` + AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"` + ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"` + Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"` } // LogDisabled log all disabled features func (f *Features) LogDisabled(l *zap.SugaredLogger) { + features := f.collectDisabled() + + if len(features) > 0 { + l.With("features", features).Info("Disabled features") + } +} + +func (f *Features) collectDisabled() []string { var features []string if !f.DHCP.ServerConfig { features = append(features, "DHCP.ServerConfig") @@ -65,8 +93,5 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) { if !f.Filters { features = append(features, "Filters") } - - if len(features) > 0 { - l.With("features", features).Info("Disabled features") - } + return features } diff --git a/pkg/types/types.go b/pkg/types/types.go index 1fd5ea4..bb33eb1 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -3,6 +3,7 @@ package types import ( "fmt" "net/url" + "strings" "go.uber.org/zap" ) @@ -15,32 +16,44 @@ const ( // Config application configuration struct // +k8s:deepcopy-gen=true type Config struct { - Origin AdGuardInstance `json:"origin" yaml:"origin" mapstructure:"ORIGIN"` - Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" mapstructure:"REPLICA"` + Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"` + Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"` Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"` - Cron string `json:"cron,omitempty" yaml:"cron,omitempty" mapstructure:"CRON"` - RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" mapstructure:"RUN_ON_START"` - PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" mapstructure:"PRINT_CONFIG_ONLY"` - ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" mapstructure:"CONTINUE_ON_ERROR"` - API API `json:"api,omitempty" yaml:"api,omitempty" mapstructure:"API"` - Features Features `json:"features,omitempty" yaml:"features,omitempty" mapstructure:"FEATURES"` + Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"` + RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"` + PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"` + ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"` + API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"` + Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"` } // API configuration type API struct { - Port int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"PORT"` - Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"` - Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"` - DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" mapstructure:"DARK_MODE"` + Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"` + Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"` + Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"` + DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"` +} + +// Mask maks username and password +func (a *API) Mask() { + a.Username = mask(a.Username) + a.Password = mask(a.Password) } // UniqueReplicas get unique replication instances func (cfg *Config) UniqueReplicas() []AdGuardInstance { dedup := make(map[string]AdGuardInstance) if cfg.Replica != nil && cfg.Replica.URL != "" { + if cfg.Replica.APIPath == "" { + cfg.Replica.APIPath = DefaultAPIPath + } dedup[cfg.Replica.Key()] = *cfg.Replica } for _, replica := range cfg.Replicas { + if replica.APIPath == "" { + replica.APIPath = DefaultAPIPath + } if replica.URL != "" { dedup[replica.Key()] = replica } @@ -48,9 +61,6 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance { var r []AdGuardInstance for _, replica := range dedup { - if replica.APIPath == "" { - replica.APIPath = DefaultAPIPath - } r = append(r, replica) } return r @@ -58,6 +68,11 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance { // Log the current config func (cfg *Config) Log(l *zap.SugaredLogger) { + c := cfg.mask() + l.With("config", c).Debug("Using config") +} + +func (cfg *Config) mask() *Config { c := cfg.DeepCopy() c.Origin.Mask() if c.Replica != nil { @@ -70,7 +85,8 @@ func (cfg *Config) Log(l *zap.SugaredLogger) { for i := range c.Replicas { c.Replicas[i].Mask() } - l.With("config", c).Debug("Using config") + c.API.Mask() + return c } func (cfg *Config) Init() error { @@ -89,16 +105,16 @@ func (cfg *Config) Init() error { // AdGuardInstance AdguardHome config instance // +k8s:deepcopy-gen=true type AdGuardInstance struct { - URL string `json:"url" yaml:"url" mapstructure:"URL"` - WebURL string `json:"webURL" yaml:"webURL" mapstructure:"WEB_URL"` - APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" mapstructure:"API_PATH"` - Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"` - Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"` - Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" mapstructure:"COOKIE"` - InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" mapstructure:"INSECURE_SKIP_VERIFY"` - AutoSetup bool `json:"autoSetup" yaml:"autoSetup" mapstructure:"AUTO_SETUP"` - InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" mapstructure:"INTERFACE_NAME"` - DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" mapstructure:"DHCP_SERVER_ENABLED"` + URL string `json:"url" yaml:"url" env:"URL"` + WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"` + APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"` + Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"` + Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"` + Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"` + InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"` + AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"` + InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"` + DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"` Host string `json:"-" yaml:"-"` WebHost string `json:"-" yaml:"-"` @@ -139,7 +155,8 @@ func mask(s string) string { if s == "" { return "***" } - return fmt.Sprintf("%v***%v", string(s[0]), string(s[len(s)-1])) + mask := strings.Repeat("*", len(s)-2) + return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1])) } // Protection API struct diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go index 492513b..2364565 100644 --- a/pkg/types/types_test.go +++ b/pkg/types/types_test.go @@ -1,38 +1,99 @@ -package types_test +package types import ( - "github.com/bakito/adguardhome-sync/pkg/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -var _ = Describe("AdGuardInstance", func() { - var inst types.AdGuardInstance +var _ = Describe("Types", func() { + Context("AdGuardInstance", func() { + var inst AdGuardInstance - BeforeEach(func() { - inst = types.AdGuardInstance{} - }) - Context("Init", func() { BeforeEach(func() { - inst.URL = "https://localhost:3000" + inst = AdGuardInstance{} }) - It("should correctly set Host and WebHost if only URL is set", func() { - err := inst.Init() - Ω(err).ShouldNot(HaveOccurred()) - Ω(inst.Host).Should(Equal("localhost:3000")) - Ω(inst.WebHost).Should(Equal("localhost:3000")) - Ω(inst.URL).Should(Equal("https://localhost:3000")) - Ω(inst.WebURL).Should(Equal("https://localhost:3000")) + Context("Instance Init", func() { + BeforeEach(func() { + inst.URL = "https://localhost:3000" + }) + It("should correctly set Host and WebHost if only URL is set", func() { + err := inst.Init() + Ω(err).ShouldNot(HaveOccurred()) + Ω(inst.Host).Should(Equal("localhost:3000")) + Ω(inst.WebHost).Should(Equal("localhost:3000")) + Ω(inst.URL).Should(Equal("https://localhost:3000")) + Ω(inst.WebURL).Should(Equal("https://localhost:3000")) + }) + It("should correctly set Host and WebHost if URL and WebURL are set", func() { + inst.WebURL = "https://127.0.0.1:4000" + err := inst.Init() + Ω(err).ShouldNot(HaveOccurred()) + Ω(inst.Host).Should(Equal("localhost:3000")) + Ω(inst.WebHost).Should(Equal("127.0.0.1:4000")) + Ω(inst.WebURL).Should(Equal(inst.WebURL)) + Ω(inst.URL).Should(Equal("https://localhost:3000")) + Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000")) + }) }) - It("should correctly set Host and WebHost if URL and WebURL are set", func() { - inst.WebURL = "https://127.0.0.1:4000" - err := inst.Init() + }) + Context("Config", func() { + Context("init", func() { + cfg := Config{ + Replicas: []AdGuardInstance{ + {URL: "https://localhost:3000"}, + }, + } + err := cfg.Init() Ω(err).ShouldNot(HaveOccurred()) - Ω(inst.Host).Should(Equal("localhost:3000")) - Ω(inst.WebHost).Should(Equal("127.0.0.1:4000")) - Ω(inst.WebURL).Should(Equal(inst.WebURL)) - Ω(inst.URL).Should(Equal("https://localhost:3000")) - Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000")) + Ω(cfg.Replicas[0].Host).Should(Equal("localhost:3000")) + Ω(cfg.Replicas[0].WebHost).Should(Equal("localhost:3000")) + Ω(cfg.Replicas[0].URL).Should(Equal("https://localhost:3000")) + Ω(cfg.Replicas[0].WebURL).Should(Equal("https://localhost:3000")) + }) + Context("UniqueReplicas", func() { + It("should return unique replicas in the array", func() { + cfg := Config{ + Replicas: []AdGuardInstance{ + {URL: "a"}, + {URL: "a", APIPath: DefaultAPIPath}, + {URL: "a", APIPath: "foo"}, + {URL: "b", APIPath: DefaultAPIPath}, + }, + Replica: &AdGuardInstance{URL: "b"}, + } + replicas := cfg.UniqueReplicas() + Ω(replicas).Should(HaveLen(3)) + }) + }) + Context("mask", func() { + It("should mask all names and passwords", func() { + cfg := Config{ + Replicas: []AdGuardInstance{ + {URL: "a", Username: "user", Password: "pass"}, + }, + Replica: &AdGuardInstance{URL: "a", Username: "user", Password: "pass"}, + API: API{Username: "user", Password: "pass"}, + } + masked := cfg.mask() + Ω(masked.Replicas[0].Username).Should(Equal("u**r")) + Ω(masked.Replicas[0].Password).Should(Equal("p**s")) + Ω(masked.Replica.Username).Should(Equal("u**r")) + Ω(masked.Replica.Password).Should(Equal("p**s")) + Ω(masked.API.Username).Should(Equal("u**r")) + Ω(masked.API.Password).Should(Equal("p**s")) + }) + }) + }) + Context("Feature", func() { + Context("LogDisabled", func() { + It("should log all features", func() { + f := NewFeatures(false) + Ω(f.collectDisabled()).Should(HaveLen(11)) + }) + It("should log no features", func() { + f := NewFeatures(true) + Ω(f.collectDisabled()).Should(BeEmpty()) + }) }) }) }) diff --git a/testdata/.gitignore b/testdata/.gitignore index 35f78b1..3d42dc7 100644 --- a/testdata/.gitignore +++ b/testdata/.gitignore @@ -1 +1,2 @@ docker-compose.yaml +!config_test_*.yaml diff --git a/testdata/config_test_replica.yaml b/testdata/config_test_replica.yaml new file mode 100644 index 0000000..412003c --- /dev/null +++ b/testdata/config_test_replica.yaml @@ -0,0 +1,36 @@ +origin: + url: https://origin-file:443 + webURL: https://origin-file:443 + apiPath: /control + username: foo + password: '*********' + insecureSkipVerify: true + autoSetup: false +replica: + url: https://replica-file:443 + webURL: https://replica-file:443 + apiPath: /control + username: bar + password: '*********' + insecureSkipVerify: false + autoSetup: false + interfaceName: eth3 +cron: '*/15 * * * *' +runOnStart: true +printConfigOnly: true +api: + port: 9090 +features: + dns: + accessLists: true + serverConfig: false + rewrites: true + dhcp: + serverConfig: true + staticLeases: true + generalSettings: true + queryLogConfig: true + statsConfig: true + clientSettings: true + services: true + filters: true diff --git a/testdata/config_test_replicas.yaml b/testdata/config_test_replicas.yaml new file mode 100644 index 0000000..ca0c5a0 --- /dev/null +++ b/testdata/config_test_replicas.yaml @@ -0,0 +1,36 @@ +origin: + url: https://origin-file:443 + webURL: https://origin-file:443 + apiPath: /control + username: foo + password: '*********' + insecureSkipVerify: true + autoSetup: false +replicas: + - url: https://replica-file:443 + webURL: https://replica-file:443 + apiPath: /control + username: bar + password: '*********' + insecureSkipVerify: false + autoSetup: false + interfaceName: eth3 +cron: '*/15 * * * *' +runOnStart: true +printConfigOnly: true +api: + port: 9090 +features: + dns: + accessLists: true + serverConfig: false + rewrites: true + dhcp: + serverConfig: true + staticLeases: true + generalSettings: true + queryLogConfig: true + statsConfig: true + clientSettings: true + services: true + filters: true diff --git a/testdata/config_test_replicas_and_replica.yaml b/testdata/config_test_replicas_and_replica.yaml new file mode 100644 index 0000000..1c445ca --- /dev/null +++ b/testdata/config_test_replicas_and_replica.yaml @@ -0,0 +1,45 @@ +origin: + url: https://origin-file:443 + webURL: https://origin-file:443 + apiPath: /control + username: foo + password: '*********' + insecureSkipVerify: true + autoSetup: false +replica: + url: https://replica-file:443 + webURL: https://replica-file:443 + apiPath: /control + username: bar + password: '*********' + insecureSkipVerify: false + autoSetup: false + interfaceName: eth3 +replicas: + - url: https://replicas-file:443 + webURL: https://replicas-file:443 + apiPath: /control + username: bar + password: '*********' + insecureSkipVerify: false + autoSetup: false + interfaceName: eth3 +cron: '*/15 * * * *' +runOnStart: true +printConfigOnly: true +api: + port: 9090 +features: + dns: + accessLists: true + serverConfig: false + rewrites: true + dhcp: + serverConfig: true + staticLeases: true + generalSettings: true + queryLogConfig: true + statsConfig: true + clientSettings: true + services: true + filters: true diff --git a/testdata/e2e/bin/install-chart.sh b/testdata/e2e/bin/install-chart.sh index 71b8ed9..878c35c 100755 --- a/testdata/e2e/bin/install-chart.sh +++ b/testdata/e2e/bin/install-chart.sh @@ -6,4 +6,4 @@ kubectl config set-context --current --namespace=agh-e2e if [[ $(helm list --no-headers -n agh-e2e | grep agh-e2e | wc -l) == "1" ]]; then helm delete agh-e2e -n agh-e2e --wait fi -helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace +helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace --set mode=${1} diff --git a/testdata/e2e/bin/wait-for-sync.sh b/testdata/e2e/bin/wait-for-sync.sh index 5bdfa38..6458e81 100755 --- a/testdata/e2e/bin/wait-for-sync.sh +++ b/testdata/e2e/bin/wait-for-sync.sh @@ -1,4 +1,8 @@ #!/bin/bash -set -e kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/adguardhome-sync --timeout=1m +RESULT=$? +if [[ "${RESULT}" != "0" ]]; then + kubectl logs adguardhome-sync +fi +exit ${RESULT} diff --git a/testdata/e2e/templates/configmap-sync-env.yaml b/testdata/e2e/templates/configmap-sync-env.yaml new file mode 100644 index 0000000..29d7fb5 --- /dev/null +++ b/testdata/e2e/templates/configmap-sync-env.yaml @@ -0,0 +1,18 @@ +{{- if eq .Values.mode "env" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: sync-conf + namespace: {{ .Release.Namespace }} +data: + API_PORT: '0' + ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000' + ORIGIN_PASSWORD: 'password' + ORIGIN_USERNAME: 'username' + {{- range $i,$version := .Values.replica.versions }} + REPLICA{{ add 1 $i }}_AUTO_SETUP: 'true' + REPLICA{{ add 1 $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000' + REPLICA{{ add 1 $i }}_PASSWORD: 'password' + REPLICA{{ add 1 $i }}_USERNAME: 'username' + {{- end }} +{{- end }} diff --git a/testdata/e2e/templates/configmap-sync-file.yaml b/testdata/e2e/templates/configmap-sync-file.yaml new file mode 100644 index 0000000..8ed7c05 --- /dev/null +++ b/testdata/e2e/templates/configmap-sync-file.yaml @@ -0,0 +1,22 @@ +{{- if eq .Values.mode "file" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: sync-conf + namespace: {{ .Release.Namespace }} +data: + config.yaml: | + origin: + url: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000' + username: username + password: password + api: + port: 0 + replicas: + {{- range $i,$version := .Values.replica.versions }} + - url: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000' + username: username + password: password + autoSetup: true + {{- end }} +{{- end }} diff --git a/testdata/e2e/templates/configmap-sync.yaml b/testdata/e2e/templates/configmap-sync.yaml deleted file mode 100644 index 7405c1a..0000000 --- a/testdata/e2e/templates/configmap-sync.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: sync-conf - namespace: {{ .Release.Namespace }} -data: - API_PORT: '0' - LOG_LEVEL: 'debug' - ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000' - ORIGIN_PASSWORD: 'password' - ORIGIN_USERNAME: 'username' - {{ range $i,$version := .Values.replica.versions }} - REPLICA{{ $i }}_AUTOSETUP: 'true' - REPLICA{{ $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000' - REPLICA{{ $i }}_PASSWORD: 'password' - REPLICA{{ $i }}_USERNAME: 'username' -{{- end }} diff --git a/testdata/e2e/templates/pod-adguardhome-sync.yaml b/testdata/e2e/templates/pod-adguardhome-sync-env.yaml similarity index 75% rename from testdata/e2e/templates/pod-adguardhome-sync.yaml rename to testdata/e2e/templates/pod-adguardhome-sync-env.yaml index 0a3210b..a7227aa 100644 --- a/testdata/e2e/templates/pod-adguardhome-sync.yaml +++ b/testdata/e2e/templates/pod-adguardhome-sync-env.yaml @@ -1,3 +1,4 @@ +{{- if eq .Values.mode "env" }} apiVersion: v1 kind: Pod metadata: @@ -7,7 +8,7 @@ spec: serviceAccountName: agh-e2e initContainers: - name: wait-for-others - image: bitnami/kubectl:1.24 + image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }} command: - /bin/bash - -c @@ -19,7 +20,11 @@ spec: command: - /opt/go/adguardhome-sync - run + env: + - name: LOG_LEVEL + value: 'debug' envFrom: - configMapRef: name: sync-conf restartPolicy: Never + {{- end }} diff --git a/testdata/e2e/templates/pod-adguardhome-sync-file.yaml b/testdata/e2e/templates/pod-adguardhome-sync-file.yaml new file mode 100644 index 0000000..bab5254 --- /dev/null +++ b/testdata/e2e/templates/pod-adguardhome-sync-file.yaml @@ -0,0 +1,36 @@ +{{- if eq .Values.mode "file" }} +apiVersion: v1 +kind: Pod +metadata: + name: adguardhome-sync + namespace: {{ $.Release.Namespace }} +spec: + serviceAccountName: agh-e2e + initContainers: + - name: wait-for-others + image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }} + command: + - /bin/bash + - -c + - | + {{- .Files.Get "bin/wait-for-agh-pods.sh" | nindent 10}} + containers: + - name: adguardhome-sync + image: localhost:5001/adguardhome-sync:e2e + command: + - /opt/go/adguardhome-sync + - run + - '--config' + - /etc/go/adguardhome-sync/config.yaml + env: + - name: LOG_LEVEL + value: 'debug' + volumeMounts: + - name: config + mountPath: /etc/go/adguardhome-sync/ + volumes: + - name: config + configMap: + name: sync-conf + restartPolicy: Never +{{- end }} diff --git a/testdata/e2e/values.yaml b/testdata/e2e/values.yaml index 7575e0f..adb54a0 100644 --- a/testdata/e2e/values.yaml +++ b/testdata/e2e/values.yaml @@ -3,3 +3,9 @@ replica: - v0.107.40 - v0.107.43 - latest + +mode: env + +kubectl: + repository: bitnami/kubectl + tag: 1.27