Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,29 @@ func configCmd(app *falcon.App) *cobra.Command {
return cmd
}

// configShowCmd returns the commands that prints current configuration
// Command for printing current configuration
func configShowCmd(app *falcon.App) *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Aliases: []string{"s", "list", "l"},
Short: "Display global configuration",
Short: "Prints current configuration",
Args: withUsage(cobra.NoArgs),
Example: strings.TrimSpace(fmt.Sprintf(`
$ %s config show --home %s
$ %s cfg s`, appName, defaultHome, appName)),
$ %s cfg list`, appName, defaultHome, appName)),
RunE: func(cmd *cobra.Command, args []string) error {
_ = app
home, err := cmd.Flags().GetString(flagHome)
if err != nil {
return err
}
out, err := app.GetConfigFile(home)
if err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), out)
return nil
},
}

return cmd
}

Expand All @@ -58,10 +65,13 @@ $ %s cfg i`, appName, defaultHome, appName)),
if err != nil {
return err
}

return app.InitConfigFile(home)
file, err := cmd.Flags().GetString(flagFile)
if err != nil {
return err
}
return app.InitConfigFile(home, file)
},
}

return cmd
return configInitFlags(app.Viper, cmd)
}
19 changes: 19 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

const (
flagHome = "home"
flagFile = "file"
)

func configInitFlags(v *viper.Viper, cmd *cobra.Command) *cobra.Command {
fileFlag(v, cmd)
return cmd
}

func fileFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command {
cmd.Flags().StringP(flagFile, "f", "", "fetch toml data from specified file")
if err := v.BindPFlag(flagFile, cmd.Flags().Lookup(flagFile)); err != nil {
panic(err)
}
return cmd
}
58 changes: 56 additions & 2 deletions falcon/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,39 @@ func (a *App) InitLogger(configLogLevel string) error {
return nil
}

func (a *App) configPath() string {
return path.Join(a.HomePath, "config", "config.toml")
}

// loadConfigFile reads config file into a.Config if file is present.
func (a *App) LoadConfigFile(ctx context.Context) error {
cfgPath := a.configPath()
if _, err := os.Stat(cfgPath); err != nil {
// don't return error if file doesn't exist
return nil
}

// read the config file bytes
file, err := os.ReadFile(cfgPath)
if err != nil {
return fmt.Errorf("error reading file: %w", err)
}

// unmarshall them into the struct
cfg := &Config{}
err = toml.Unmarshal(file, cfg)
if err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}

// save configuration
a.Config = cfg

return nil
}

// InitConfigFile initializes the configuration to the given path.
func (a *App) InitConfigFile(homePath string) error {
func (a *App) InitConfigFile(homePath string, customFilePath string) error {
cfgDir := path.Join(homePath, configFolderName)
cfgPath := path.Join(cfgDir, configFileName)

Expand All @@ -96,14 +122,25 @@ func (a *App) InitConfigFile(homePath string) error {
}
}

var cfg Config
var err error
switch {
case customFilePath != "":
cfg, err = LoadConfig(customFilePath) // Initialize with CustomConfig if file is provided
if err != nil {
return fmt.Errorf("LoadConfig file %v error %v", customFilePath, err)
}
default:
cfg = DefaultConfig() // Initialize with DefaultConfig if no file is provided
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • move the logic to below os.Create part, so that the code states clearly that first part is about creating a folder/file
  • prefer if/else over switch case

// Create the file and write the default config to the given location.
f, err := os.Create(cfgPath)
if err != nil {
return err
}
defer f.Close()

cfg := DefaultConfig()
b, err := toml.Marshal(cfg)
if err != nil {
return err
Expand All @@ -116,6 +153,23 @@ func (a *App) InitConfigFile(homePath string) error {
return nil
}

// Get config file from given home path.
func (a *App) GetConfigFile(homePath string) (string, error) {
cfgPath := path.Join(homePath, "config", "config.toml")
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
if _, err := os.Stat(homePath); os.IsNotExist(err) {
return "", fmt.Errorf("home path does not exist: %s", homePath)
}
return "", fmt.Errorf("config does not exist: %s", cfgPath)
}

out, err := toml.Marshal(a.Config)
if err != nil {
return "", err
}
return string(out), nil
}

// Start starts the tunnel relayer program.
func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error {
// initialize band client
Expand Down
165 changes: 162 additions & 3 deletions falcon/app_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package falcon_test

import (
"fmt"
"os"
"path"
"testing"
"time"

"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/require"

"github.com/bandprotocol/falcon/falcon"
"github.com/bandprotocol/falcon/falcon/band"
)

func TestInitConfig(t *testing.T) {
tmpDir := t.TempDir()
customCfgPath := ""

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

err := app.InitConfigFile(tmpDir)
err := app.InitConfigFile(tmpDir, customCfgPath)
require.NoError(t, err)

require.FileExists(t, path.Join(tmpDir, "config", "config.toml"))
Expand All @@ -37,13 +41,168 @@ func TestInitConfig(t *testing.T) {

func TestInitExistingConfig(t *testing.T) {
tmpDir := t.TempDir()
customCfgPath := ""

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

err := app.InitConfigFile(tmpDir)
err := app.InitConfigFile(tmpDir, customCfgPath)
require.NoError(t, err)

// second time should fail
err = app.InitConfigFile(tmpDir)
err = app.InitConfigFile(tmpDir, customCfgPath)
require.ErrorContains(t, err, "config already exists:")
}

func TestShowEmptyConfig(t *testing.T) {
tmpDir := t.TempDir()

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

// should not have the file
require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml"))

_, err := app.GetConfigFile(tmpDir)
require.ErrorContains(t, err, "config does not exist:")
}

func TestShowConfig(t *testing.T) {
tmpDir := t.TempDir()
customCfgPath := ""

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

// create config file
err := app.InitConfigFile(tmpDir, customCfgPath)
require.NoError(t, err)

require.FileExists(t, path.Join(tmpDir, "config", "config.toml"))

// assign config
cfg := falcon.DefaultConfig()
app.Config = &(cfg)

// read config
cfgContent, err := app.GetConfigFile(tmpDir)
require.NoError(t, err)

require.Contains(t, cfgContent, "target_chains = []")
require.Contains(t, cfgContent, "checking_packet_interval = 60000000000")
require.Contains(t, cfgContent, "[bandchain]")
require.Contains(t, cfgContent, "rpc_endpoints = ['http://localhost:26657']")
require.Contains(t, cfgContent, "timeout = 5")
}

func TestLoadConfig(t *testing.T) {
tmpDir := t.TempDir()

// custom config path
customCfgPath := path.Join(tmpDir, "custom.toml")

// create new toml config file
cfg := `
target_chains = []
checking_packet_interval = 60000000000

[bandchain]
rpc_endpoints = ['http://localhost:26657']
timeout = 7
`

// Write the file
err := os.WriteFile(customCfgPath, []byte(cfg), 0o600)
require.NoError(t, err)

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

err = app.InitConfigFile(tmpDir, customCfgPath)
require.NoError(t, err)

// Config file should exist
require.FileExists(t, path.Join(tmpDir, "config", "config.toml"))

// read the file
b, err := os.ReadFile(path.Join(tmpDir, "config", "config.toml"))
require.NoError(t, err)

// unmarshal data
actual := falcon.Config{}
err = toml.Unmarshal(b, &actual)
require.NoError(t, err)

expect := falcon.Config{
BandChainConfig: band.Config{
RpcEndpoints: []string{"http://localhost:26657"},
Timeout: 7,
},
TargetChains: []falcon.TargetChainConfig{},
CheckingPacketInterval: time.Minute,
}

require.Equal(t, expect, actual)
}

func TestInvalidTypeLoadConfig(t *testing.T) {
tmpDir := t.TempDir()

// custom config
customCfgPath := path.Join(tmpDir, "custom.toml")
// invalid type of value
invalidType := "'600000'"

// create new toml config file
cfg := fmt.Sprintf(`
target_chains = []
checking_packet_interval = %v

[bandchain]
rpc_endpoints = ['http://localhost:26657']
timeout = 7
`, invalidType)

// Write the file
err := os.WriteFile(customCfgPath, []byte(cfg), 0o600)
require.NoError(t, err)

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

err = app.InitConfigFile(tmpDir, customCfgPath)

// should fail when try to unmarshal
require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath))

// file should not be created
require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml"))
}

func TestInvalidFieldLoadConfig(t *testing.T) {
tmpDir := t.TempDir()

// custom config
customCfgPath := path.Join(tmpDir, "custom.toml")
// invalid field name
invalidField := "checking_packet_intervalsss"

// create new toml config file
cfg := fmt.Sprintf(`
target_chains = []
%v = 60000000000

[bandchain]
rpc_endpoints = ['http://localhost:26657']
timeout = 7
`, invalidField)
// Write the file
err := os.WriteFile(customCfgPath, []byte(cfg), 0o600)
require.NoError(t, err)

app := falcon.NewApp(nil, nil, tmpDir, false, nil)

err = app.InitConfigFile(tmpDir, customCfgPath)

// should fail when try to unmarshal
require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath))
require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField))

// file should not be created
require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml"))
}
Loading