243 lines
6.0 KiB
Go
243 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
|
|
"github.com/filecoin-project/lotus/node/config"
|
|
)
|
|
|
|
var configCmd = &cli.Command{
|
|
Name: "config",
|
|
Usage: "Manage node config by layers. The layer 'base' will always be applied. ",
|
|
Subcommands: []*cli.Command{
|
|
configDefaultCmd,
|
|
configSetCmd,
|
|
configGetCmd,
|
|
configListCmd,
|
|
configViewCmd,
|
|
configRmCmd,
|
|
},
|
|
}
|
|
|
|
var configDefaultCmd = &cli.Command{
|
|
Name: "default",
|
|
Aliases: []string{"defaults"},
|
|
Usage: "Print default node config",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "no-comment",
|
|
Usage: "don't comment default values",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
c := config.DefaultLotusProvider()
|
|
|
|
cb, err := config.ConfigUpdate(c, nil, config.Commented(!cctx.Bool("no-comment")), config.DefaultKeepUncommented(), config.NoEnv())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Print(string(cb))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configSetCmd = &cli.Command{
|
|
Name: "set",
|
|
Aliases: []string{"add"},
|
|
Usage: "Set a config layer or the base by providing a filename or stdin.",
|
|
ArgsUsage: "a layer's file name",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "title",
|
|
Usage: "title of the config layer (req'd for stdin)",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
args := cctx.Args()
|
|
|
|
db, err := makeDB(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
name := cctx.String("title")
|
|
var stream io.Reader = os.Stdin
|
|
if args.Len() != 1 {
|
|
if cctx.String("title") == "" {
|
|
return errors.New("must have a title for stdin, or a file name")
|
|
}
|
|
} else {
|
|
stream, err = os.Open(args.First())
|
|
if err != nil {
|
|
return fmt.Errorf("cannot open file %s: %w", args.First(), err)
|
|
}
|
|
if name == "" {
|
|
name = strings.Split(args.First(), ".")[0]
|
|
}
|
|
}
|
|
bytes, err := io.ReadAll(stream)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot read stream/file %w", err)
|
|
}
|
|
|
|
lp := config.DefaultLotusProvider() // ensure it's toml
|
|
_, err = toml.Decode(string(bytes), lp)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot decode file: %w", err)
|
|
}
|
|
_ = lp
|
|
|
|
_, err = db.Exec(context.Background(),
|
|
`INSERT INTO harmony_config (title, config) VALUES ($1, $2)
|
|
ON CONFLICT (title) DO UPDATE SET config = excluded.config`, name, string(bytes))
|
|
if err != nil {
|
|
return fmt.Errorf("unable to save config layer: %w", err)
|
|
}
|
|
|
|
fmt.Println("Layer " + name + " created/updated")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configGetCmd = &cli.Command{
|
|
Name: "get",
|
|
Aliases: []string{"cat", "show"},
|
|
Usage: "Get a config layer by name. You may want to pipe the output to a file, or use 'less'",
|
|
ArgsUsage: "layer name",
|
|
Action: func(cctx *cli.Context) error {
|
|
args := cctx.Args()
|
|
if args.Len() != 1 {
|
|
return fmt.Errorf("want 1 layer arg, got %d", args.Len())
|
|
}
|
|
db, err := makeDB(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var cfg string
|
|
err = db.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title=$1`, args.First()).Scan(&cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(cfg)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configListCmd = &cli.Command{
|
|
Name: "list",
|
|
Aliases: []string{"ls"},
|
|
Usage: "List config layers you can get.",
|
|
Flags: []cli.Flag{},
|
|
Action: func(cctx *cli.Context) error {
|
|
db, err := makeDB(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var res []string
|
|
err = db.Select(context.Background(), &res, `SELECT title FROM harmony_config ORDER BY title`)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read from db: %w", err)
|
|
}
|
|
for _, r := range res {
|
|
fmt.Println(r)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configRmCmd = &cli.Command{
|
|
Name: "remove",
|
|
Aliases: []string{"rm", "del", "delete"},
|
|
Usage: "Remove a named config layer.",
|
|
Flags: []cli.Flag{},
|
|
Action: func(cctx *cli.Context) error {
|
|
args := cctx.Args()
|
|
if args.Len() != 1 {
|
|
return errors.New("must have exactly 1 arg for the layer name")
|
|
}
|
|
db, err := makeDB(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ct, err := db.Exec(context.Background(), `DELETE FROM harmony_config WHERE title=$1`, args.First())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read from db: %w", err)
|
|
}
|
|
if ct == 0 {
|
|
return fmt.Errorf("no layer named %s", args.First())
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
var configViewCmd = &cli.Command{
|
|
Name: "interpret",
|
|
Aliases: []string{"view", "stacked", "stack"},
|
|
Usage: "Interpret stacked config layers by this version of lotus-provider.",
|
|
ArgsUsage: "a list of layers to be interpreted as the final config",
|
|
Flags: []cli.Flag{
|
|
&cli.StringSliceFlag{
|
|
Name: "layers",
|
|
Usage: "comma or space separated list of layers to be interpreted",
|
|
Value: cli.NewStringSlice("base"),
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
db, err := makeDB(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lp, err := getConfig(cctx, db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := toml.NewEncoder(os.Stdout)
|
|
e.Indent = " "
|
|
return e.Encode(lp)
|
|
},
|
|
}
|
|
|
|
func getConfig(cctx *cli.Context, db *harmonydb.DB) (*config.LotusProviderConfig, error) {
|
|
lp := config.DefaultLotusProvider()
|
|
have := []string{}
|
|
for _, layer := range cctx.StringSlice("layers") {
|
|
text := ""
|
|
err := db.QueryRow(cctx.Context, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), sql.ErrNoRows.Error()) {
|
|
return nil, fmt.Errorf("missing layer '%s' ", layer)
|
|
}
|
|
return nil, fmt.Errorf("could not read layer '%s': %w", layer, err)
|
|
}
|
|
meta, err := toml.Decode(text, &lp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read layer, bad toml %s: %w", layer, err)
|
|
}
|
|
for _, k := range meta.Keys() {
|
|
have = append(have, strings.Join(k, " "))
|
|
}
|
|
}
|
|
_ = have // FUTURE: verify that required fields are here.
|
|
// If config includes 3rd-party config, consider JSONSchema as a way that
|
|
// 3rd-parties can dynamically include config requirements and we can
|
|
// validate the config. Because of layering, we must validate @ startup.
|
|
return lp, nil
|
|
}
|