forked from TrueCloudLab/frostfs-node
[#666] cmd/neofs-adm: Initial app structure with config init
command
Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
24d1725cc8
commit
d189d60925
5 changed files with 269 additions and 0 deletions
124
cmd/neofs-adm/internal/modules/config/config.go
Normal file
124
cmd/neofs-adm/internal/modules/config/config.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type configTemplate struct {
|
||||||
|
Endpoint string
|
||||||
|
AlphabetDir string
|
||||||
|
MaxObjectSize int
|
||||||
|
EpochDuration int
|
||||||
|
Glagolitics []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const configTxtTemplate = `rpc-endpoint: {{ .Endpoint}}
|
||||||
|
alphabet-wallets: {{ .AlphabetDir}}
|
||||||
|
network:
|
||||||
|
max_object_size: {{ .MaxObjectSize}}
|
||||||
|
epoch_duration: {{ .EpochDuration}}
|
||||||
|
# if credentials section is omitted, then neofs-adm will require manual password input
|
||||||
|
credentials:{{ range.Glagolitics}}
|
||||||
|
{{.}}: password{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
func initConfig(cmd *cobra.Command, args []string) error {
|
||||||
|
configPath, err := readConfigPathFromArgs(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathDir := path.Dir(configPath)
|
||||||
|
err = os.MkdirAll(pathDir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create dir %s: %w", pathDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open %s: %w", configPath, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
configText, err := generateConfigExample(pathDir, 7)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.WriteString(configText)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing to %s: %w", configPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Printf("Initial config file saved to %s\n", configPath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfigPathFromArgs(cmd *cobra.Command) (string, error) {
|
||||||
|
configPath, err := cmd.Flags().GetString(configPathFlag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if configPath != "" {
|
||||||
|
return configPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultConfigPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfigPath() (string, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting home dir path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(home, ".neofs", "adm", "config.yml"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateConfigExample builds .yml representation of the config file. It is
|
||||||
|
// easier to build it manually with template instead of using viper, because we
|
||||||
|
// want to order records in specific order in file and, probably, provide
|
||||||
|
// some comments as well.
|
||||||
|
func generateConfigExample(appDir string, credSize int) (string, error) {
|
||||||
|
input := configTemplate{
|
||||||
|
Endpoint: "https://neo.rpc.node:30333",
|
||||||
|
MaxObjectSize: 67108864, // 64 MiB
|
||||||
|
EpochDuration: 240, // 1 hour with 15s per block
|
||||||
|
Glagolitics: make([]string, 0, credSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
appDir, err := filepath.Abs(appDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("making absolute path for %s: %w", appDir, err)
|
||||||
|
}
|
||||||
|
input.AlphabetDir = path.Join(appDir, "alphabet-wallets")
|
||||||
|
|
||||||
|
var i innerring.GlagoliticLetter
|
||||||
|
for i = 0; i < innerring.GlagoliticLetter(credSize); i++ {
|
||||||
|
input.Glagolitics = append(input.Glagolitics, i.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.New("config.yml").Parse(configTxtTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parsing config template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
err = t.Execute(buf, input)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("generating config from tempalte: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
40
cmd/neofs-adm/internal/modules/config/config_test.go
Normal file
40
cmd/neofs-adm/internal/modules/config/config_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateConfigExample(t *testing.T) {
|
||||||
|
const (
|
||||||
|
n = 10
|
||||||
|
appDir = "/home/example/.neofs"
|
||||||
|
)
|
||||||
|
|
||||||
|
configText, err := generateConfigExample(appDir, n)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v := viper.New()
|
||||||
|
v.SetConfigType("yml")
|
||||||
|
|
||||||
|
require.NoError(t, v.ReadConfig(bytes.NewBufferString(configText)))
|
||||||
|
|
||||||
|
require.Equal(t, "https://neo.rpc.node:30333", v.GetString("rpc-endpoint"))
|
||||||
|
require.Equal(t, path.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets"))
|
||||||
|
require.Equal(t, 67108864, v.GetInt("network.max_object_size"))
|
||||||
|
require.Equal(t, 240, v.GetInt("network.epoch_duration"))
|
||||||
|
|
||||||
|
var i innerring.GlagoliticLetter
|
||||||
|
for i = 0; i < innerring.GlagoliticLetter(n); i++ {
|
||||||
|
key := "credentials." + i.String()
|
||||||
|
require.Equal(t, "password", v.GetString(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "credentials." + i.String()
|
||||||
|
require.Equal(t, "", v.GetString(key))
|
||||||
|
}
|
29
cmd/neofs-adm/internal/modules/config/root.go
Normal file
29
cmd/neofs-adm/internal/modules/config/root.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const configPathFlag = "path"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RootCmd is a root command of config section.
|
||||||
|
RootCmd = &cobra.Command{
|
||||||
|
Use: "config",
|
||||||
|
Short: "Section for neofs-adm config related commands.",
|
||||||
|
}
|
||||||
|
|
||||||
|
initCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Initialize basic neofs-adm configuration file.",
|
||||||
|
Example: `neofs-adm config init
|
||||||
|
neofs-adm config init --path .config/neofs-adm.yml`,
|
||||||
|
RunE: initConfig,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(initCmd)
|
||||||
|
|
||||||
|
initCmd.Flags().String(configPathFlag, "", "path to config (default ~/.neofs/adm/config.yml)")
|
||||||
|
}
|
63
cmd/neofs-adm/internal/modules/root.go
Normal file
63
cmd/neofs-adm/internal/modules/root.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/config"
|
||||||
|
"github.com/nspcc-dev/neofs-node/misc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rootCmd = &cobra.Command{
|
||||||
|
Use: "neofs-adm",
|
||||||
|
Short: "NeoFS Administrative Tool",
|
||||||
|
Long: `NeoFS Administrative Tool provides functions to setup and
|
||||||
|
manage NeoFS network deployment.`,
|
||||||
|
RunE: entryPoint,
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
configFlag = "config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cobra.OnInitialize(func() { initConfig(rootCmd) })
|
||||||
|
// we need to init viper config to bind viper and cobra configurations for
|
||||||
|
// rpc endpoint, alphabet wallet dir, key credentials, etc.
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringP(configFlag, "c", "", "config file")
|
||||||
|
rootCmd.Flags().Bool("version", false, "application version")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(config.RootCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() error {
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func entryPoint(cmd *cobra.Command, args []string) error {
|
||||||
|
printVersion, err := cmd.Flags().GetBool("version")
|
||||||
|
if err == nil && printVersion {
|
||||||
|
fmt.Printf("Version: %s \nBuild: %s \nDebug: %s\n",
|
||||||
|
misc.Version,
|
||||||
|
misc.Build,
|
||||||
|
misc.Debug,
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfig(cmd *cobra.Command) {
|
||||||
|
configFile, err := cmd.Flags().GetString(configFlag)
|
||||||
|
if err != nil || configFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetConfigType("yml")
|
||||||
|
viper.SetConfigFile(configFile)
|
||||||
|
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
||||||
|
}
|
13
cmd/neofs-adm/main.go
Normal file
13
cmd/neofs-adm/main.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := modules.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue