mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-29 01:59:07 +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:
+8
-19
@@ -5,20 +5,18 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
filePath "path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
configloader "github.com/netbirdio/netbird/util/config"
|
||||
"github.com/netbirdio/netbird/util/crypt"
|
||||
|
||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||
)
|
||||
|
||||
// CombinedConfig is the root configuration for the combined server.
|
||||
@@ -440,26 +438,17 @@ func (c *CombinedConfig) autoConfigureClientSettings(exposedProto, exposedHost,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from a YAML file
|
||||
// LoadConfig loads the combined server configuration.
|
||||
func LoadConfig(configPath string) (*CombinedConfig, error) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
if configPath == "" {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
cfg, err := configloader.Load(configPath, DefaultConfig(), configloader.Options{
|
||||
TagName: "yaml",
|
||||
AllowMissing: configPath == "",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||
}
|
||||
|
||||
// Populate internal configs from server settings
|
||||
cfg.ApplySimplifiedDefaults()
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadConfigEnvironmentWithoutFile(t *testing.T) {
|
||||
t.Setenv("NB_SERVER_LOGLEVEL", "debug")
|
||||
|
||||
cfg, err := LoadConfig("")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "debug", cfg.Server.LogLevel, "Environment should override defaults without a file")
|
||||
}
|
||||
|
||||
func TestLoadConfigEnvironmentOverridesFileAndDefaults(t *testing.T) {
|
||||
configPath := writeCombinedConfig(t, "config.yaml", `
|
||||
server:
|
||||
exposedAddress: "https://netbird.example.com"
|
||||
authSecret: "file-secret"
|
||||
logLevel: "info"
|
||||
relay:
|
||||
logLevel: "ignored-relay-level"
|
||||
signal:
|
||||
logLevel: "ignored-signal-level"
|
||||
management:
|
||||
logLevel: "ignored-management-level"
|
||||
`)
|
||||
t.Setenv("NB_SERVER_LOGLEVEL", "debug")
|
||||
t.Setenv("NB_SERVER_METRICSPORT", "9191")
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "debug", cfg.Server.LogLevel, "Environment should override the config file")
|
||||
assert.Equal(t, 9191, cfg.Server.MetricsPort, "Environment should override a default absent from the file")
|
||||
assert.Equal(t, ":443", cfg.Server.ListenAddress, "Unchanged defaults should be preserved")
|
||||
assert.Equal(t, "debug", cfg.Relay.LogLevel, "Internal relay configuration should not be decoded")
|
||||
assert.Equal(t, "debug", cfg.Signal.LogLevel, "Internal signal configuration should not be decoded")
|
||||
assert.Equal(t, "debug", cfg.Management.LogLevel, "Internal management configuration should not be decoded")
|
||||
}
|
||||
|
||||
func TestLoadConfigEnvironmentCreatesOptionalConfig(t *testing.T) {
|
||||
configPath := writeCombinedConfig(t, "config.yaml", `
|
||||
server:
|
||||
exposedAddress: "https://netbird.example.com"
|
||||
authSecret: "file-secret"
|
||||
`)
|
||||
t.Setenv("NB_SERVER_AUTH_OWNER_EMAIL", "owner@example.com")
|
||||
t.Setenv("NB_SERVER_AUTH_OWNER_PASSWORD", "password-hash")
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg.Server.Auth.Owner, "Environment should create the optional owner configuration")
|
||||
assert.Equal(t, "owner@example.com", cfg.Server.Auth.Owner.Email, "Owner email should come from the environment")
|
||||
assert.Equal(t, "password-hash", cfg.Server.Auth.Owner.Password, "Owner password should come from the environment")
|
||||
}
|
||||
|
||||
func TestLoadConfigSupportsYAMLWithoutKnownExtension(t *testing.T) {
|
||||
for _, name := range []string{"config", "config.conf"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
configPath := writeCombinedConfig(t, name, `
|
||||
server:
|
||||
exposedAddress: "https://netbird.example.com"
|
||||
authSecret: "yaml-secret"
|
||||
`)
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "yaml-secret", cfg.Server.AuthSecret, "Unknown extensions should remain YAML-compatible")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigSupportsLegacyYAMLBooleans(t *testing.T) {
|
||||
configPath := writeCombinedConfig(t, "config.yaml", `
|
||||
server:
|
||||
exposedAddress: "https://netbird.example.com"
|
||||
authSecret: "file-secret"
|
||||
disableAnonymousMetrics: yes
|
||||
`)
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cfg.Server.DisableAnonymousMetrics, "Legacy YAML boolean values should remain supported")
|
||||
}
|
||||
|
||||
func TestLoadConfigSupportsTOML(t *testing.T) {
|
||||
configPath := writeCombinedConfig(t, "config.toml", `
|
||||
[server]
|
||||
exposedAddress = "https://netbird.example.com"
|
||||
authSecret = "toml-secret"
|
||||
logLevel = "warn"
|
||||
`)
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://netbird.example.com", cfg.Server.ExposedAddress, "Exposed address should be decoded from TOML")
|
||||
assert.Equal(t, "toml-secret", cfg.Server.AuthSecret, "Auth secret should be decoded from TOML")
|
||||
assert.Equal(t, "warn", cfg.Server.LogLevel, "Log level should be decoded from TOML")
|
||||
assert.Equal(t, ":443", cfg.Server.ListenAddress, "Defaults should survive decoding")
|
||||
}
|
||||
|
||||
func writeCombinedConfig(t *testing.T, name, contents string) string {
|
||||
t.Helper()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), name)
|
||||
require.NoError(t, os.WriteFile(configPath, []byte(contents), 0o600))
|
||||
return configPath
|
||||
}
|
||||
@@ -56,7 +56,8 @@ var (
|
||||
All services (Management, Signal, Relay) are multiplexed on a single port.
|
||||
Optional STUN server runs on separate UDP ports.
|
||||
|
||||
Configuration is loaded from a YAML file specified with --config.`,
|
||||
Configuration is loaded from a file specified with --config. Values can be
|
||||
overridden with NB_-prefixed environment variables, such as NB_SERVER_LOGLEVEL.`,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: execute,
|
||||
@@ -64,7 +65,7 @@ Configuration is loaded from a YAML file specified with --config.`,
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "path to YAML configuration file (required)")
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "path to configuration file (required)")
|
||||
_ = rootCmd.MarkPersistentFlagRequired("config")
|
||||
|
||||
rootCmd.AddCommand(newAdminCommands())
|
||||
|
||||
Reference in New Issue
Block a user