Jelajahi Sumber

Node: Specify config file (#4404)

* Node: Specify config file

* Doc: Update operations MD
bruce-riley 4 bulan lalu
induk
melakukan
7bde17b5b5

+ 2 - 0
devnet/node.yaml

@@ -80,6 +80,8 @@ spec:
           command:
             - /guardiand
             - node
+            - --config
+            - node/config/guardiand.yaml
             # - --ethRPC
             # - ws://eth-devnet:8545
             # - --wormchainURL

+ 3 - 3
docs/operations.md

@@ -603,13 +603,13 @@ docker run \
 
 <!-- cspell:enable -->
 
-## Guardian Configurations
+## Guardian Configuration
 
-Configuration files, environment variables and flags are all supported.
+Configuration files, environment variables and command line arguments are all supported.
 
 ### Config File
 
-**Location/Naming**: By default, the config file is expected to be in the `node/config` directory. The standard name for the config file is `guardiand.yaml`. Currently there's no support for custom directory or filename yet.
+**Specifying the File**: To load config parameters from an arbitrary file, specify the `--config <fileName>` command line argument. The file name should be complete, including any extension. It may include a relative or absolute path.
 
 **Format**: We support any format that is supported by [Viper](https://pkg.go.dev/github.com/dvln/viper#section-readme). But YAML format is generally preferred.
 

+ 7 - 7
node/cmd/guardiand/node.go

@@ -45,6 +45,7 @@ import (
 	libp2p_crypto "github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
 	"github.com/wormhole-foundation/wormhole/sdk"
 	"github.com/wormhole-foundation/wormhole/sdk/vaa"
 	"go.uber.org/zap"
@@ -546,11 +547,7 @@ var (
 	rootCtxCancel context.CancelFunc
 )
 
-var (
-	configFilename = "guardiand"
-	configPath     = "node/config"
-	envPrefix      = "GUARDIAND"
-)
+const envPrefix = "GUARDIAND"
 
 // "Why would anyone do this?" are famous last words.
 //
@@ -582,8 +579,7 @@ var Build = "prod"
 // initConfig initializes the file configuration.
 func initConfig(cmd *cobra.Command, args []string) error {
 	return node.InitFileConfig(cmd, node.ConfigOptions{
-		FilePath:  configPath,
-		FileName:  configFilename,
+		FilePath:  viper.ConfigFileUsed(),
 		EnvPrefix: envPrefix,
 	})
 }
@@ -660,6 +656,10 @@ func runNode(cmd *cobra.Command, args []string) {
 	// Override the default go-log config, which uses a magic environment variable.
 	ipfslog.SetAllLoggers(lvl)
 
+	if viper.ConfigFileUsed() != "" {
+		logger.Info("loaded config file", zap.String("filePath", viper.ConfigFileUsed()))
+	}
+
 	// In devnet mode, we automatically set a number of flags that rely on deterministic keys.
 	if env == common.UnsafeDevNet {
 		g0key, err := peer.IDFromPrivateKey(devnet.DeterministicP2PPrivKeyByIndex(0))

+ 3 - 2
node/cmd/root.go

@@ -34,8 +34,7 @@ var versionCmd = &cobra.Command{
 	},
 }
 
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
+// Execute adds all child commands to the root command and sets flags appropriately. This is called by main.main(). It only needs to happen once to the rootCmd.
 func Execute() {
 	if err := rootCmd.Execute(); err != nil {
 		fmt.Println(err)
@@ -59,6 +58,8 @@ func init() {
 }
 
 // initConfig reads in config file and ENV variables if set.
+// NOTE: This set up is used for guardiand commands other than node.
+// for that, see `initConfig` in `node.go`.
 func initConfig() {
 	if cfgFile != "" {
 		// Use config file from the flag.

+ 13 - 9
node/pkg/node/config_file_reader.go

@@ -1,7 +1,6 @@
 package node
 
 import (
-	"errors"
 	"fmt"
 	"log"
 
@@ -10,9 +9,17 @@ import (
 	"github.com/spf13/viper"
 )
 
+// ConfigOptions is used to configure the loading of config parameters by "guardiand node".
 type ConfigOptions struct {
-	FilePath  string
-	FileName  string
+	// FilePath is the path to the config file to be loaded, including the file name and extension.
+	// If this is specified (either as fully qualified or relative), config parameters will be loaded
+	// from that file, in addition to from environment variables and command line arguments.
+	// The file may be any of the types supported by Viper (such as .yaml or .json).
+	FilePath string
+
+	// EnvPrefix is the prefix to be added to environment variables to load variables that
+	// override config file settings. For instance, setting it to "GUARDIAND" will cause it
+	// to look for variables like "GUARDIAND_ETHRPC".
 	EnvPrefix string
 }
 
@@ -24,12 +31,9 @@ type ConfigOptions struct {
 func InitFileConfig(cmd *cobra.Command, options ConfigOptions) error {
 	v := viper.New()
 
-	v.SetConfigName(options.FileName)
-	v.AddConfigPath(options.FilePath)
-
-	if err := v.ReadInConfig(); err != nil {
-		var configFileNotFoundError viper.ConfigFileNotFoundError
-		if !errors.As(err, &configFileNotFoundError) {
+	if options.FilePath != "" {
+		v.SetConfigFile(options.FilePath)
+		if err := v.ReadInConfig(); err != nil {
 			return err
 		}
 	}

+ 30 - 6
node/pkg/node/config_file_reader_test.go

@@ -10,14 +10,15 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func NewTestRootCommand() *cobra.Command {
+const TestConfigFile = "testdata/test.yaml"
+
+func NewTestRootCommand(configFile string) *cobra.Command {
 	var ethRPC *string
 	var solRPC *string
 
 	// Define test configuration
 	testConfig := ConfigOptions{
-		FilePath:  "testdata",
-		FileName:  "test",
+		FilePath:  configFile,
 		EnvPrefix: "TEST_GUARDIAND",
 	}
 
@@ -48,7 +49,7 @@ func NewTestRootCommand() *cobra.Command {
 // Tests that the config file is read and the default value is set
 func TestInitFileConfig(t *testing.T) {
 
-	cmd := NewTestRootCommand()
+	cmd := NewTestRootCommand(TestConfigFile)
 	output := &bytes.Buffer{}
 	cmd.SetOut(output)
 	_ = cmd.Execute()
@@ -66,7 +67,7 @@ func TestEnvVarPrecedence(t *testing.T) {
 	os.Setenv("TEST_GUARDIAND_ETHRPC", "ws://eth-env-var:8545")
 	defer os.Unsetenv("TEST_GUARDIAND_ETHRPC")
 
-	cmd := NewTestRootCommand()
+	cmd := NewTestRootCommand(TestConfigFile)
 	output := &bytes.Buffer{}
 	cmd.SetOut(output)
 	_ = cmd.Execute()
@@ -85,7 +86,30 @@ func TestFlagPrecedence(t *testing.T) {
 	os.Setenv("TEST_GUARDIAND_ETHRPC", "ws://eth-env-var:8545")
 	defer os.Unsetenv("TEST_GUARDIAND_ETHRPC")
 
-	cmd := NewTestRootCommand()
+	cmd := NewTestRootCommand(TestConfigFile)
+	output := &bytes.Buffer{}
+	cmd.SetOut(output)
+	cmd.SetArgs([]string{
+		"--ethRPC",
+		"ws://eth-flag:8545",
+		"--solRPC",
+		"ws://sol-flag:8545",
+	})
+	_ = cmd.Execute()
+
+	gotOutput := output.String()
+	wantOutput := `ethRPC: ws://eth-flag:8545
+solRPC: ws://sol-flag:8545
+`
+
+	assert.Equal(t, wantOutput, gotOutput, "expected the ethRPC to use the flag value and solRPC to use the flag value")
+}
+
+func TestNoConfigFileWorks(t *testing.T) {
+	os.Setenv("TEST_GUARDIAND_ETHRPC", "ws://eth-env-var:8545")
+	defer os.Unsetenv("TEST_GUARDIAND_ETHRPC")
+
+	cmd := NewTestRootCommand("")
 	output := &bytes.Buffer{}
 	cmd.SetOut(output)
 	cmd.SetArgs([]string{