mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-21 22:29:08 +02:00
[management,signal,proxy,relay,misc] Unify service configuration loading
Service entry points currently resolve defaults, files, environment variables, and flags differently, which makes precedence inconsistent and prevents some services from using config files. Introduce one Viper-backed loader and migrate Combined, Management, Relay, Signal, and Proxy while preserving compatibility aliases and Management template expansion.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadConfigPrecedence(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "relay.yaml")
|
||||
require.NoError(t, os.WriteFile(path, []byte(`
|
||||
listenAddress: ":8443"
|
||||
exposedAddress: "relay.example.com:443"
|
||||
authSecret: file-secret
|
||||
metricsPort: 9091
|
||||
enableSTUN: true
|
||||
stunPorts: [3478, 3479]
|
||||
`), 0o600))
|
||||
t.Setenv("NB_METRICS_PORT", "9191")
|
||||
require.NoError(t, rootCmd.ParseFlags(nil))
|
||||
|
||||
listenFlag := rootCmd.Flags().Lookup("listen-address")
|
||||
oldListen := listenFlag.Value.String()
|
||||
oldChanged := listenFlag.Changed
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, listenFlag.Value.Set(oldListen))
|
||||
listenFlag.Changed = oldChanged
|
||||
})
|
||||
require.NoError(t, listenFlag.Value.Set(":7443"))
|
||||
listenFlag.Changed = true
|
||||
|
||||
oldConfigPath := configPath
|
||||
configPath = path
|
||||
t.Cleanup(func() {
|
||||
configPath = oldConfigPath
|
||||
})
|
||||
|
||||
cfg, err := loadConfig(rootCmd)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ":7443", cfg.ListenAddress, "Flags should override the configuration file")
|
||||
assert.Equal(t, 9191, cfg.MetricsPort, "Environment should override the configuration file")
|
||||
assert.Equal(t, "relay.example.com:443", cfg.ExposedAddress, "File values should override defaults")
|
||||
assert.True(t, cfg.EnableSTUN, "STUN should be configurable from the file")
|
||||
assert.Equal(t, []int{3478, 3479}, cfg.STUNPorts, "STUN ports should load from the file")
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// setFlagsFromEnvVars reads and updates flag values from environment variables with prefix NB_
|
||||
func setFlagsFromEnvVars(cmd *cobra.Command) {
|
||||
flags := cmd.PersistentFlags()
|
||||
flags.VisitAll(func(f *pflag.Flag) {
|
||||
newEnvVar := flagNameToEnvVar(f.Name, "NB_")
|
||||
value, present := os.LookupEnv(newEnvVar)
|
||||
if !present {
|
||||
return
|
||||
}
|
||||
|
||||
err := flags.Set(f.Name, value)
|
||||
if err != nil {
|
||||
log.Infof("unable to configure flag %s using variable %s, err: %v", f.Name, newEnvVar, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// flagNameToEnvVar converts flag name to environment var name adding a prefix,
|
||||
// replacing dashes and making all uppercase (e.g. setup-keys is converted to NB_SETUP_KEYS according to the input prefix)
|
||||
func flagNameToEnvVar(cmdFlag string, prefix string) string {
|
||||
parsed := strings.ReplaceAll(cmdFlag, "-", "_")
|
||||
upper := strings.ToUpper(parsed)
|
||||
return prefix + upper
|
||||
}
|
||||
+57
-28
@@ -26,33 +26,47 @@ import (
|
||||
"github.com/netbirdio/netbird/stun"
|
||||
"github.com/netbirdio/netbird/trustedproxy"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
configloader "github.com/netbirdio/netbird/util/config"
|
||||
)
|
||||
|
||||
// Config contains relay service startup configuration.
|
||||
type Config struct {
|
||||
ListenAddress string
|
||||
ListenAddress string `yaml:"listenAddress" env:"NB_LISTEN_ADDRESS" flag:"listen-address"`
|
||||
// in HA every peer connect to a common domain, the instance domain has been distributed during the p2p connection
|
||||
// it is a domain:port or ip:port
|
||||
ExposedAddress string
|
||||
MetricsPort int
|
||||
LetsencryptEmail string
|
||||
LetsencryptDataDir string
|
||||
LetsencryptDomains []string
|
||||
ExposedAddress string `yaml:"exposedAddress" env:"NB_EXPOSED_ADDRESS" flag:"exposed-address"`
|
||||
MetricsPort int `yaml:"metricsPort" env:"NB_METRICS_PORT" flag:"metrics-port"`
|
||||
LetsencryptEmail string `yaml:"letsencryptEmail" env:"NB_LETSENCRYPT_EMAIL" flag:"letsencrypt-email"`
|
||||
LetsencryptDataDir string `yaml:"letsencryptDataDir" env:"NB_LETSENCRYPT_DATA_DIR" flag:"letsencrypt-data-dir"`
|
||||
LetsencryptDomains []string `yaml:"letsencryptDomains" env:"NB_LETSENCRYPT_DOMAINS" flag:"letsencrypt-domains"`
|
||||
// in case of using Route 53 for DNS challenge the credentials should be provided in the environment variables or
|
||||
// in the AWS credentials file
|
||||
LetsencryptAWSRoute53 bool
|
||||
TlsCertFile string
|
||||
TlsKeyFile string
|
||||
AuthSecret string
|
||||
LogLevel string
|
||||
LogFile string
|
||||
HealthcheckListenAddress string
|
||||
LetsencryptAWSRoute53 bool `yaml:"letsencryptAWSRoute53" env:"NB_LETSENCRYPT_AWS_ROUTE53" flag:"letsencrypt-aws-route53"`
|
||||
TlsCertFile string `yaml:"tlsCertFile" env:"NB_TLS_CERT_FILE" flag:"tls-cert-file"`
|
||||
TlsKeyFile string `yaml:"tlsKeyFile" env:"NB_TLS_KEY_FILE" flag:"tls-key-file"`
|
||||
AuthSecret string `yaml:"authSecret" env:"NB_AUTH_SECRET" flag:"auth-secret"`
|
||||
LogLevel string `yaml:"logLevel" env:"NB_LOG_LEVEL" flag:"log-level"`
|
||||
LogFile string `yaml:"logFile" env:"NB_LOG_FILE" flag:"log-file"`
|
||||
HealthcheckListenAddress string `yaml:"healthcheckListenAddress" env:"NB_HEALTH_LISTEN_ADDRESS" flag:"health-listen-address"`
|
||||
// TrustedProxies is a comma-separated list of upstream proxy CIDRs/IPs whose
|
||||
// X-Real-Ip/X-Real-Port headers are trusted. Empty means never trust these headers.
|
||||
TrustedProxies string
|
||||
TrustedProxies string `yaml:"trustedProxies" env:"NB_TRUSTED_PROXIES" flag:"trusted-proxies"`
|
||||
// STUN server configuration
|
||||
EnableSTUN bool
|
||||
STUNPorts []int
|
||||
STUNLogLevel string
|
||||
EnableSTUN bool `yaml:"enableSTUN" env:"NB_ENABLE_STUN" flag:"enable-stun"`
|
||||
STUNPorts []int `yaml:"stunPorts" env:"NB_STUN_PORTS" flag:"stun-ports"`
|
||||
STUNLogLevel string `yaml:"stunLogLevel" env:"NB_STUN_LOG_LEVEL" flag:"stun-log-level"`
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
ListenAddress: ":443",
|
||||
MetricsPort: 9090,
|
||||
LogLevel: "info",
|
||||
LogFile: "console",
|
||||
HealthcheckListenAddress: ":9000",
|
||||
STUNPorts: []int{3478},
|
||||
STUNLogLevel: "info",
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
@@ -93,6 +107,7 @@ func (c Config) HasLetsEncrypt() bool {
|
||||
}
|
||||
|
||||
var (
|
||||
configPath string
|
||||
cobraConfig *Config
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "relay",
|
||||
@@ -106,10 +121,11 @@ var (
|
||||
|
||||
func init() {
|
||||
_ = util.InitLog("trace", util.LogConsole)
|
||||
cobraConfig = &Config{}
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.ListenAddress, "listen-address", "l", ":443", "listen address")
|
||||
cobraConfig = defaultConfig()
|
||||
rootCmd.PersistentFlags().StringVar(&configPath, "config", "", "path to configuration file")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.ListenAddress, "listen-address", "l", cobraConfig.ListenAddress, "listen address")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.ExposedAddress, "exposed-address", "e", "", "instance domain address (or ip) and port, it will be distributes between peers")
|
||||
rootCmd.PersistentFlags().IntVar(&cobraConfig.MetricsPort, "metrics-port", 9090, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
|
||||
rootCmd.PersistentFlags().IntVar(&cobraConfig.MetricsPort, "metrics-port", cobraConfig.MetricsPort, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.LetsencryptDataDir, "letsencrypt-data-dir", "d", "", "a directory to store Let's Encrypt data. Required if Let's Encrypt is enabled.")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&cobraConfig.LetsencryptDomains, "letsencrypt-domains", "a", nil, "list of domains to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.LetsencryptEmail, "letsencrypt-email", "", "email address to use for Let's Encrypt certificate registration")
|
||||
@@ -117,15 +133,13 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.TlsCertFile, "tls-cert-file", "c", "", "")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.TlsKeyFile, "tls-key-file", "k", "", "")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.AuthSecret, "auth-secret", "s", "", "auth secret")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.LogLevel, "log-level", "info", "log level")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.LogFile, "log-file", "console", "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.HealthcheckListenAddress, "health-listen-address", "H", ":9000", "listen address of healthcheck server")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.LogLevel, "log-level", cobraConfig.LogLevel, "log level")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.LogFile, "log-file", cobraConfig.LogFile, "log file")
|
||||
rootCmd.PersistentFlags().StringVarP(&cobraConfig.HealthcheckListenAddress, "health-listen-address", "H", cobraConfig.HealthcheckListenAddress, "listen address of healthcheck server")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.TrustedProxies, "trusted-proxies", "", "comma-separated list of upstream proxy CIDRs or IPs whose X-Real-Ip/X-Real-Port headers are trusted; leave empty to always use the direct connection address")
|
||||
rootCmd.PersistentFlags().BoolVar(&cobraConfig.EnableSTUN, "enable-stun", false, "enable embedded STUN server")
|
||||
rootCmd.PersistentFlags().IntSliceVar(&cobraConfig.STUNPorts, "stun-ports", []int{3478}, "ports for the embedded STUN server (can be specified multiple times or comma-separated)")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.STUNLogLevel, "stun-log-level", "info", "log level for STUN server (panic, fatal, error, warn, info, debug, trace)")
|
||||
|
||||
setFlagsFromEnvVars(rootCmd)
|
||||
rootCmd.PersistentFlags().IntSliceVar(&cobraConfig.STUNPorts, "stun-ports", cobraConfig.STUNPorts, "ports for the embedded STUN server (can be specified multiple times or comma-separated)")
|
||||
rootCmd.PersistentFlags().StringVar(&cobraConfig.STUNLogLevel, "stun-log-level", cobraConfig.STUNLogLevel, "log level for STUN server (panic, fatal, error, warn, info, debug, trace)")
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
@@ -139,8 +153,14 @@ func waitForExitSignal() {
|
||||
}
|
||||
|
||||
func execute(cmd *cobra.Command, args []string) error {
|
||||
loadedConfig, err := loadConfig(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
cobraConfig = loadedConfig
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
err := cobraConfig.Validate()
|
||||
err = cobraConfig.Validate()
|
||||
if err != nil {
|
||||
log.Debugf("invalid config: %s", err)
|
||||
return fmt.Errorf("invalid config: %s", err)
|
||||
@@ -228,6 +248,15 @@ func execute(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func loadConfig(cmd *cobra.Command) (*Config, error) {
|
||||
return configloader.Load(configPath, defaultConfig(), configloader.Options{
|
||||
TagName: "yaml",
|
||||
AllowMissing: configPath == "",
|
||||
FlagSet: cmd.Flags(),
|
||||
Strict: true,
|
||||
})
|
||||
}
|
||||
|
||||
func startServers(wg *sync.WaitGroup, metricsServer *metrics.Metrics, srv *server.Server, srvListenerCfg server.ListenerConfig, httpHealthcheck *healthcheck.Server, stunServer *stun.Server) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
||||
Reference in New Issue
Block a user