[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:
jnfrati
2026-08-24 18:18:59 +02:00
parent f03853867b
commit c8cd6b4dca
27 changed files with 1296 additions and 339 deletions
+2 -2
View File
@@ -114,8 +114,8 @@ func withAdminConfig(cmd *cobra.Command, applyIDPDefaults bool, fn func(ctx cont
}
func loadAdminMgmtConfig(ctx context.Context, applyIDPDefaults bool) (*nbconfig.Config, string, error) {
config := &nbconfig.Config{}
if _, err := util.ReadJsonWithEnvSub(nbconfig.MgmtConfigPath, config); err != nil {
config, err := loadManagementConfig(nbconfig.MgmtConfigPath)
if err != nil {
return nil, "", err
}
+13
View File
@@ -0,0 +1,13 @@
package cmd
import (
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
configloader "github.com/netbirdio/netbird/util/config"
)
func loadManagementConfig(configPath string) (*nbconfig.Config, error) {
return configloader.Load(configPath, &nbconfig.Config{Datadir: defaultMgmtDataDir}, configloader.Options{
TagName: "json",
Transform: configloader.ExpandEnvTemplate,
})
}
+50
View File
@@ -0,0 +1,50 @@
package cmd
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadManagementConfigSources(t *testing.T) {
t.Setenv("MANAGEMENT_DATA_DIR", "/template-data")
t.Setenv("MANAGEMENT_ENCRYPTION_KEY", "template-key")
t.Setenv("NB_DATADIR", "/environment-data")
t.Setenv("NB_HTTPCONFIG_AUTHAUDIENCE", "environment-audience")
configPath := filepath.Join(t.TempDir(), "management.json")
require.NoError(t, os.WriteFile(configPath, []byte(`{
"Datadir": "{{ .MANAGEMENT_DATA_DIR }}",
"DataStoreEncryptionKey": "{{ .MANAGEMENT_ENCRYPTION_KEY }}",
"HttpConfig": {
"AuthAudience": "file-audience"
},
"TURNConfig": {
"CredentialsTTL": "1h"
}
}`), 0o600))
cfg, err := loadManagementConfig(configPath)
require.NoError(t, err)
assert.Equal(t, "/environment-data", cfg.Datadir, "Bound environment values should override template values")
assert.Equal(t, "template-key", cfg.DataStoreEncryptionKey, "Template environment values should be expanded")
require.NotNil(t, cfg.HttpConfig, "Nested configuration should be decoded")
assert.Equal(t, "environment-audience", cfg.HttpConfig.AuthAudience, "Bound environment values should override the file")
require.NotNil(t, cfg.TURNConfig, "TURN configuration should be decoded")
assert.Equal(t, time.Hour, cfg.TURNConfig.CredentialsTTL.Duration, "JSON duration types should be decoded")
datadirFlag := mgmtCmd.Flags().Lookup("datadir")
oldDatadir := datadirFlag.Value.String()
oldChanged := datadirFlag.Changed
t.Cleanup(func() {
require.NoError(t, datadirFlag.Value.Set(oldDatadir))
datadirFlag.Changed = oldChanged
})
require.NoError(t, datadirFlag.Value.Set("/flag-data"))
datadirFlag.Changed = true
ApplyCommandLineOverrides(cfg, mgmtCmd.Flags())
assert.Equal(t, "/flag-data", cfg.Datadir, "Flags should override environment values")
}
+16 -11
View File
@@ -19,6 +19,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/netbirdio/netbird/management/server/types"
@@ -60,7 +61,7 @@ var (
// detect whether user specified a port
userPort := cmd.Flag("port").Changed
config, err = LoadMgmtConfig(ctx, nbconfig.MgmtConfigPath)
config, err = LoadMgmtConfig(ctx, nbconfig.MgmtConfigPath, cmd.Flags())
if err != nil {
return fmt.Errorf("failed reading provided config file: %s: %v", nbconfig.MgmtConfigPath, err)
}
@@ -70,7 +71,7 @@ var (
}
var tlsEnabled bool
if mgmtLetsencryptDomain != "" || (config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "") {
if config.HttpConfig.LetsEncryptDomain != "" || (config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "") {
tlsEnabled = true
}
@@ -171,15 +172,15 @@ var (
}
)
func LoadMgmtConfig(ctx context.Context, mgmtConfigPath string) (*nbconfig.Config, error) {
loadedConfig := &nbconfig.Config{}
if _, err := util.ReadJsonWithEnvSub(mgmtConfigPath, loadedConfig); err != nil {
func LoadMgmtConfig(ctx context.Context, mgmtConfigPath string, flags *pflag.FlagSet) (*nbconfig.Config, error) {
loadedConfig, err := loadManagementConfig(mgmtConfigPath)
if err != nil {
return nil, err
}
ApplyCommandLineOverrides(loadedConfig)
ApplyCommandLineOverrides(loadedConfig, flags)
err := grpc.ValidateSyncMessageVersion(loadedConfig.HighestSupportedSyncMessageVersion)
err = grpc.ValidateSyncMessageVersion(loadedConfig.HighestSupportedSyncMessageVersion)
if err != nil {
return nil, err
}
@@ -211,14 +212,18 @@ func LoadMgmtConfig(ctx context.Context, mgmtConfigPath string) (*nbconfig.Confi
}
// ApplyCommandLineOverrides applies command-line flag overrides to the config
func ApplyCommandLineOverrides(cfg *nbconfig.Config) {
if mgmtLetsencryptDomain != "" {
func ApplyCommandLineOverrides(cfg *nbconfig.Config, flags *pflag.FlagSet) {
hasCertOverride := flags.Changed("cert-key") && flags.Changed("cert-file")
if (flags.Changed("letsencrypt-domain") || hasCertOverride) && cfg.HttpConfig == nil {
cfg.HttpConfig = &nbconfig.HttpServerConfig{}
}
if flags.Changed("letsencrypt-domain") {
cfg.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain
}
if mgmtDataDir != "" {
if flags.Changed("datadir") {
cfg.Datadir = mgmtDataDir
}
if certKey != "" && certFile != "" {
if hasCertOverride {
cfg.HttpConfig.CertFile = certFile
cfg.HttpConfig.CertKey = certKey
}
+2 -2
View File
@@ -36,7 +36,7 @@ func Test_LoadMgmtConfig(t *testing.T) {
tmpFile, err := createConfig(exampleConfig)
assert.NoError(t, err)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile, mgmtCmd.Flags())
assert.NoError(t, err)
assert.NotEmpty(t, cfg.Relay)
assert.NotEmpty(t, cfg.Relay.Addresses)
@@ -54,7 +54,7 @@ func Test_LoadMgmtConfig_Empty(t *testing.T) {
}`)
assert.NoError(t, err)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile)
cfg, err := LoadMgmtConfig(context.Background(), tmpFile, mgmtCmd.Flags())
assert.NoError(t, err)
assert.Nil(t, cfg.HighestSupportedSyncMessageVersion)
assert.Nil(t, cfg.PerAccountHighestSupportedSyncMessageVersion)