config: create helper for dumping config

With this commit we add a new helper function that recursively turns the
runtime configuration into a flat key/value map that is human-readable,
using the dot notation for nested values that is also used in the config
file or command line flags.
This commit is contained in:
Oliver Gugger 2023-11-15 21:16:15 -06:00
parent 2b54774721
commit becbe7085d
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
2 changed files with 141 additions and 0 deletions

View File

@ -2111,3 +2111,96 @@ func checkEstimateMode(estimateMode string) error {
return fmt.Errorf("estimatemode must be one of the following: %v",
bitcoindEstimateModes[:])
}
// configToFlatMap converts the given config struct into a flat map of key/value
// pairs using the dot notation we are used to from the config file or command
// line flags.
func configToFlatMap(cfg Config) (map[string]string, error) {
result := make(map[string]string)
// redact is the helper function that redacts sensitive values like
// passwords.
redact := func(key, value string) string {
sensitiveKeySuffixes := []string{
"pass",
"password",
"dsn",
}
for _, suffix := range sensitiveKeySuffixes {
if strings.HasSuffix(key, suffix) {
return "[redacted]"
}
}
return value
}
// printConfig is the helper function that goes into nested structs
// recursively. Because we call it recursively, we need to declare it
// before we define it.
var printConfig func(reflect.Value, string)
printConfig = func(obj reflect.Value, prefix string) {
// Turn struct pointers into the actual struct, so we can
// iterate over the fields as we would with a struct value.
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
// Abort on nil values.
if !obj.IsValid() {
return
}
// Loop over all fields of the struct and inspect the type.
for i := 0; i < obj.NumField(); i++ {
field := obj.Field(i)
fieldType := obj.Type().Field(i)
longName := fieldType.Tag.Get("long")
namespace := fieldType.Tag.Get("namespace")
group := fieldType.Tag.Get("group")
switch {
// We have a long name defined, this is a config value.
case longName != "":
key := longName
if prefix != "" {
key = prefix + "." + key
}
// Add the value directly to the flattened map.
result[key] = redact(key, fmt.Sprintf(
"%v", field.Interface(),
))
// We have no long name but a namespace, this is a
// nested struct.
case longName == "" && namespace != "":
key := namespace
if prefix != "" {
key = prefix + "." + key
}
printConfig(field, key)
// Just a group means this is a dummy struct to house
// multiple config values, the group name doesn't go
// into the final field name.
case longName == "" && group != "":
printConfig(field, prefix)
// Anonymous means embedded struct. We need to recurse
// into it but without adding anything to the prefix.
case fieldType.Anonymous:
printConfig(field, prefix)
default:
continue
}
}
}
// Turn the whole config struct into a flat map.
printConfig(reflect.ValueOf(cfg), "")
return result, nil
}

48
config_test.go Normal file
View File

@ -0,0 +1,48 @@
package lnd
import (
"fmt"
"testing"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/routing"
"github.com/stretchr/testify/require"
)
var (
testPassword = "testpassword"
redactedPassword = "[redacted]"
)
// TestConfigToFlatMap tests that the configToFlatMap function works as
// expected on the default configuration.
func TestConfigToFlatMap(t *testing.T) {
cfg := DefaultConfig()
cfg.BitcoindMode.RPCPass = testPassword
cfg.BtcdMode.RPCPass = testPassword
cfg.Tor.Password = testPassword
cfg.DB.Etcd.Pass = testPassword
cfg.DB.Postgres.Dsn = testPassword
result, err := configToFlatMap(cfg)
require.NoError(t, err)
// Pick a couple of random values to check.
require.Equal(t, DefaultLndDir, result["lnddir"])
require.Equal(
t, fmt.Sprintf("%v", chainreg.DefaultBitcoinTimeLockDelta),
result["bitcoin.timelockdelta"],
)
require.Equal(
t, fmt.Sprintf("%v", routing.DefaultAprioriWeight),
result["routerrpc.apriori.weight"],
)
require.Contains(t, result, "routerrpc.routermacaroonpath")
// Check that sensitive values are not included.
require.Equal(t, redactedPassword, result["bitcoind.rpcpass"])
require.Equal(t, redactedPassword, result["btcd.rpcpass"])
require.Equal(t, redactedPassword, result["tor.password"])
require.Equal(t, redactedPassword, result["db.etcd.pass"])
require.Equal(t, redactedPassword, result["db.postgres.dsn"])
}