mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-25 13:59:08 +01:00
[management,misc] Fix configuration migration checks
The initial migration let an empty legacy Datadir override Management defaults and pulled Viper into WASM builds through the common util package. Normalize the Management data directory, isolate template expansion from the loader dependency, and split source binding to satisfy the quality gate.
This commit is contained in:
@@ -3,11 +3,19 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
configloader "github.com/netbirdio/netbird/util/config"
|
configloader "github.com/netbirdio/netbird/util/config"
|
||||||
|
"github.com/netbirdio/netbird/util/envtemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadManagementConfig(configPath string) (*nbconfig.Config, error) {
|
func loadManagementConfig(configPath string) (*nbconfig.Config, error) {
|
||||||
return configloader.Load(configPath, &nbconfig.Config{Datadir: defaultMgmtDataDir}, configloader.Options{
|
cfg, err := configloader.Load(configPath, &nbconfig.Config{Datadir: defaultMgmtDataDir}, configloader.Options{
|
||||||
TagName: "json",
|
TagName: "json",
|
||||||
Transform: configloader.ExpandEnvTemplate,
|
Transform: envtemplate.Expand,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg.Datadir == "" {
|
||||||
|
cfg.Datadir = defaultMgmtDataDir
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestLoadManagementConfigUsesDefaultDataDirForEmptyValue(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "management.json")
|
||||||
|
require.NoError(t, os.WriteFile(configPath, []byte(`{"Datadir":""}`), 0o600))
|
||||||
|
|
||||||
|
cfg, err := loadManagementConfig(configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, defaultMgmtDataDir, cfg.Datadir, "Empty legacy values should use the default data directory")
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadManagementConfigSources(t *testing.T) {
|
func TestLoadManagementConfigSources(t *testing.T) {
|
||||||
t.Setenv("MANAGEMENT_DATA_DIR", "/template-data")
|
t.Setenv("MANAGEMENT_DATA_DIR", "/template-data")
|
||||||
t.Setenv("MANAGEMENT_ENCRYPTION_KEY", "template-key")
|
t.Setenv("MANAGEMENT_ENCRYPTION_KEY", "template-key")
|
||||||
|
|||||||
+99
-54
@@ -28,8 +28,7 @@
|
|||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// Set [Options.AllowMissing] when the service must start without a configuration
|
// Set [Options.AllowMissing] when the service must start without a configuration
|
||||||
// file. [Options.Transform] can preprocess file contents before decoding, such
|
// file. [Options.Transform] can preprocess file contents before decoding.
|
||||||
// as with [ExpandEnvTemplate].
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -215,66 +214,112 @@ func bindConfigSources(
|
|||||||
defer delete(visiting, configType)
|
defer delete(visiting, configType)
|
||||||
|
|
||||||
for i := range configType.NumField() {
|
for i := range configType.NumField() {
|
||||||
field := configType.Field(i)
|
if err := bindConfigField(
|
||||||
if !field.IsExported() {
|
v,
|
||||||
continue
|
configType.Field(i),
|
||||||
|
prefix,
|
||||||
|
tagName,
|
||||||
|
flagSet,
|
||||||
|
bindEnvironment,
|
||||||
|
bindFlags,
|
||||||
|
visiting,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
key, inline, skip := configFieldKey(field, tagName)
|
func bindConfigField(
|
||||||
if skip {
|
v *viper.Viper,
|
||||||
continue
|
field reflect.StructField,
|
||||||
}
|
prefix string,
|
||||||
if inline {
|
tagName string,
|
||||||
key = prefix
|
flagSet *pflag.FlagSet,
|
||||||
} else if prefix != "" {
|
bindEnvironment bool,
|
||||||
key = prefix + "." + key
|
bindFlags bool,
|
||||||
}
|
visiting map[reflect.Type]bool,
|
||||||
|
) error {
|
||||||
|
if !field.IsExported() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
fieldEnvironment := field.Tag.Get("env")
|
key, inline, skip := configFieldKey(field, tagName)
|
||||||
fieldFlags := field.Tag.Get("flag")
|
if skip {
|
||||||
bindFieldEnvironment := bindEnvironment && fieldEnvironment != "-"
|
return nil
|
||||||
bindFieldFlags := bindFlags && fieldFlags != "-"
|
}
|
||||||
|
if inline {
|
||||||
|
key = prefix
|
||||||
|
} else if prefix != "" {
|
||||||
|
key = prefix + "." + key
|
||||||
|
}
|
||||||
|
|
||||||
fieldType := field.Type
|
fieldEnvironment := field.Tag.Get("env")
|
||||||
for fieldType.Kind() == reflect.Pointer {
|
fieldFlags := field.Tag.Get("flag")
|
||||||
fieldType = fieldType.Elem()
|
bindEnvironment = bindEnvironment && fieldEnvironment != "-"
|
||||||
}
|
bindFlags = bindFlags && fieldFlags != "-"
|
||||||
if fieldType.Kind() == reflect.Struct && !isScalarUnmarshaler(fieldType) {
|
|
||||||
if !visiting[fieldType] {
|
|
||||||
if err := bindConfigSources(
|
|
||||||
v,
|
|
||||||
fieldType,
|
|
||||||
key,
|
|
||||||
tagName,
|
|
||||||
flagSet,
|
|
||||||
bindFieldEnvironment,
|
|
||||||
bindFieldFlags,
|
|
||||||
visiting,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == "" {
|
fieldType := field.Type
|
||||||
return fmt.Errorf("empty config key for field %s", field.Name)
|
for fieldType.Kind() == reflect.Pointer {
|
||||||
|
fieldType = fieldType.Elem()
|
||||||
|
}
|
||||||
|
if fieldType.Kind() == reflect.Struct && !isScalarUnmarshaler(fieldType) {
|
||||||
|
if visiting[fieldType] {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if bindFieldEnvironment {
|
return bindConfigSources(
|
||||||
if err := bindEnvironmentVariable(v, key, fieldEnvironment); err != nil {
|
v,
|
||||||
return err
|
fieldType,
|
||||||
}
|
key,
|
||||||
}
|
tagName,
|
||||||
if bindFieldFlags && flagSet != nil && fieldFlags != "" {
|
flagSet,
|
||||||
flagName, flag, err := selectFlag(flagSet, fieldFlags)
|
bindEnvironment,
|
||||||
if err != nil {
|
bindFlags,
|
||||||
return fmt.Errorf("config field %s: %w", field.Name, err)
|
visiting,
|
||||||
}
|
)
|
||||||
if err := v.BindPFlag(key, flag); err != nil {
|
}
|
||||||
return fmt.Errorf("bind flag %s: %w", flagName, err)
|
|
||||||
}
|
return bindScalarSources(
|
||||||
|
v,
|
||||||
|
field,
|
||||||
|
key,
|
||||||
|
fieldEnvironment,
|
||||||
|
fieldFlags,
|
||||||
|
flagSet,
|
||||||
|
bindEnvironment,
|
||||||
|
bindFlags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindScalarSources(
|
||||||
|
v *viper.Viper,
|
||||||
|
field reflect.StructField,
|
||||||
|
key string,
|
||||||
|
environmentName string,
|
||||||
|
flagNames string,
|
||||||
|
flagSet *pflag.FlagSet,
|
||||||
|
bindEnvironment bool,
|
||||||
|
bindFlags bool,
|
||||||
|
) error {
|
||||||
|
if key == "" {
|
||||||
|
return fmt.Errorf("empty config key for field %s", field.Name)
|
||||||
|
}
|
||||||
|
if bindEnvironment {
|
||||||
|
if err := bindEnvironmentVariable(v, key, environmentName); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !bindFlags || flagSet == nil || flagNames == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flagName, flag, err := selectFlag(flagSet, flagNames)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("config field %s: %w", field.Name, err)
|
||||||
|
}
|
||||||
|
if err := v.BindPFlag(key, flag); err != nil {
|
||||||
|
return fmt.Errorf("bind flag %s: %w", flagName, err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/util/envtemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testConfig struct {
|
type testConfig struct {
|
||||||
@@ -138,7 +140,7 @@ server:
|
|||||||
|
|
||||||
cfg, err := Load(configPath, defaultTestConfig(), Options{
|
cfg, err := Load(configPath, defaultTestConfig(), Options{
|
||||||
TagName: "yaml",
|
TagName: "yaml",
|
||||||
Transform: ExpandEnvTemplate,
|
Transform: envtemplate.Expand,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, ":8443", cfg.Server.Address, "The transform should run before decoding")
|
assert.Equal(t, ":8443", cfg.Server.Address, "The transform should run before decoding")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
package config
|
// Package envtemplate expands Go templates with environment variables.
|
||||||
|
package envtemplate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,27 +9,27 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExpandEnvTemplate substitutes Go-template references with environment values.
|
// Expand substitutes Go-template references with environment values.
|
||||||
func ExpandEnvTemplate(data []byte) ([]byte, error) {
|
func Expand(data []byte) ([]byte, error) {
|
||||||
tmpl, err := template.New("config").Parse(string(data))
|
tmpl, err := template.New("config").Parse(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse environment template: %w", err)
|
return nil, fmt.Errorf("parse environment template: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
if err := tmpl.Execute(&output, environmentMap()); err != nil {
|
if err := tmpl.Execute(&output, environment()); err != nil {
|
||||||
return nil, fmt.Errorf("execute environment template: %w", err)
|
return nil, fmt.Errorf("execute environment template: %w", err)
|
||||||
}
|
}
|
||||||
return output.Bytes(), nil
|
return output.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func environmentMap() map[string]string {
|
func environment() map[string]string {
|
||||||
environment := make(map[string]string)
|
values := make(map[string]string)
|
||||||
for _, entry := range os.Environ() {
|
for _, entry := range os.Environ() {
|
||||||
key, value, ok := strings.Cut(entry, "=")
|
key, value, ok := strings.Cut(entry, "=")
|
||||||
if ok {
|
if ok {
|
||||||
environment[key] = value
|
values[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return environment
|
return values
|
||||||
}
|
}
|
||||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
configloader "github.com/netbirdio/netbird/util/config"
|
"github.com/netbirdio/netbird/util/envtemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteBytesWithRestrictedPermission(ctx context.Context, file string, bs []byte) error {
|
func WriteBytesWithRestrictedPermission(ctx context.Context, file string, bs []byte) error {
|
||||||
@@ -243,7 +243,7 @@ func ReadJsonWithEnvSub(file string, res interface{}) (interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := configloader.ExpandEnvTemplate(bs)
|
output, err := envtemplate.Expand(bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user