lotus/cmd/lotus-provider/config.go

260 lines
6.7 KiB
Go
Raw Normal View History

2023-08-28 16:37:36 +00:00
package main
import (
2023-09-20 03:48:39 +00:00
"context"
2023-09-27 03:06:00 +00:00
"database/sql"
2023-09-20 03:48:39 +00:00
"errors"
2023-08-28 16:37:36 +00:00
"fmt"
2023-10-02 22:08:42 +00:00
"io"
2023-09-20 03:48:39 +00:00
"os"
2023-11-10 02:45:47 +00:00
"path"
2023-09-20 03:48:39 +00:00
"strings"
2023-08-28 16:37:36 +00:00
2023-09-20 03:48:39 +00:00
"github.com/BurntSushi/toml"
2023-08-28 16:37:36 +00:00
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
2023-09-20 17:58:56 +00:00
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
"github.com/filecoin-project/lotus/node/config"
2023-08-28 16:37:36 +00:00
)
var configCmd = &cli.Command{
Name: "config",
2023-09-20 03:48:39 +00:00
Usage: "Manage node config by layers. The layer 'base' will always be applied. ",
2023-08-28 16:37:36 +00:00
Subcommands: []*cli.Command{
configDefaultCmd,
configSetCmd,
configGetCmd,
2023-09-20 03:48:39 +00:00
configListCmd,
configViewCmd,
2023-09-29 16:56:10 +00:00
configRmCmd,
2023-11-09 00:01:44 +00:00
configMigrateCmd,
2023-08-28 16:37:36 +00:00
},
}
var configDefaultCmd = &cli.Command{
2023-09-29 18:05:51 +00:00
Name: "default",
Aliases: []string{"defaults"},
Usage: "Print default node config",
2023-08-28 16:37:36 +00:00
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "no-comment",
Usage: "don't comment default values",
},
},
Action: func(cctx *cli.Context) error {
comment := !cctx.Bool("no-comment")
cfg, err := getDefaultConfig(comment)
2023-09-20 03:48:39 +00:00
if err != nil {
return err
}
fmt.Print(cfg)
2023-09-20 03:48:39 +00:00
2023-08-28 16:37:36 +00:00
return nil
},
}
func getDefaultConfig(comment bool) (string, error) {
c := config.DefaultLotusProvider()
cb, err := config.ConfigUpdate(c, nil, config.Commented(comment), config.DefaultKeepUncommented(), config.NoEnv())
if err != nil {
return "", err
}
return string(cb), nil
}
2023-08-28 16:37:36 +00:00
var configSetCmd = &cli.Command{
2023-09-20 03:48:39 +00:00
Name: "set",
2023-09-29 18:05:51 +00:00
Aliases: []string{"add"},
2023-10-02 22:08:42 +00:00
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)",
},
},
2023-08-28 16:37:36 +00:00
Action: func(cctx *cli.Context) error {
2023-09-20 03:48:39 +00:00
args := cctx.Args()
2023-10-02 22:08:42 +00:00
2023-09-20 03:48:39 +00:00
db, err := makeDB(cctx)
if err != nil {
return err
}
2023-10-02 22:08:42 +00:00
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 == "" {
2023-11-10 02:45:47 +00:00
name = strings.Split(path.Base(args.First()), ".")[0]
2023-10-02 22:08:42 +00:00
}
}
bytes, err := io.ReadAll(stream)
2023-09-20 03:48:39 +00:00
if err != nil {
2023-10-02 22:08:42 +00:00
return fmt.Errorf("cannot read stream/file %w", err)
2023-09-20 03:48:39 +00:00
}
lp := config.DefaultLotusProvider() // ensure it's toml
2023-09-20 17:58:56 +00:00
_, err = toml.Decode(string(bytes), lp)
if err != nil {
return fmt.Errorf("cannot decode file: %w", err)
}
2023-09-20 03:48:39 +00:00
_ = lp
_, err = db.Exec(context.Background(),
2023-09-27 03:06:00 +00:00
`INSERT INTO harmony_config (title, config) VALUES ($1, $2)
2023-09-20 03:48:39 +00:00
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")
2023-08-28 16:37:36 +00:00
return nil
},
}
var configGetCmd = &cli.Command{
2023-09-20 03:48:39 +00:00
Name: "get",
2023-09-29 18:05:51 +00:00
Aliases: []string{"cat", "show"},
2023-09-20 03:48:39 +00:00
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 {
2023-09-20 17:58:56 +00:00
return fmt.Errorf("want 1 layer arg, got %d", args.Len())
2023-09-20 03:48:39 +00:00
}
db, err := makeDB(cctx)
if err != nil {
return err
}
var cfg string
2023-09-27 03:06:00 +00:00
err = db.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title=$1`, args.First()).Scan(&cfg)
2023-09-20 03:48:39 +00:00
if err != nil {
return err
}
fmt.Println(cfg)
return nil
2023-08-28 16:37:36 +00:00
},
2023-09-20 03:48:39 +00:00
}
var configListCmd = &cli.Command{
2023-09-29 18:05:51 +00:00
Name: "list",
Aliases: []string{"ls"},
Usage: "List config layers you can get.",
Flags: []cli.Flag{},
2023-08-28 16:37:36 +00:00
Action: func(cctx *cli.Context) error {
2023-09-20 03:48:39 +00:00
db, err := makeDB(cctx)
if err != nil {
return err
}
var res []string
2023-09-27 03:06:00 +00:00
err = db.Select(context.Background(), &res, `SELECT title FROM harmony_config ORDER BY title`)
2023-09-20 17:58:56 +00:00
if err != nil {
return fmt.Errorf("unable to read from db: %w", err)
}
2023-09-20 03:48:39 +00:00
for _, r := range res {
fmt.Println(r)
}
2023-08-28 16:37:36 +00:00
return nil
},
}
2023-09-20 03:48:39 +00:00
2023-09-29 16:56:10 +00:00
var configRmCmd = &cli.Command{
2023-09-29 18:05:51 +00:00
Name: "remove",
Aliases: []string{"rm", "del", "delete"},
Usage: "Remove a named config layer.",
Flags: []cli.Flag{},
2023-09-29 16:56:10 +00:00
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
},
}
2023-09-20 03:48:39 +00:00
var configViewCmd = &cli.Command{
2023-09-29 18:05:51 +00:00
Name: "interpret",
Aliases: []string{"view", "stacked", "stack"},
Usage: "Interpret stacked config layers by this version of lotus-provider, with system-generated comments.",
2023-09-20 03:48:39 +00:00
ArgsUsage: "a list of layers to be interpreted as the final config",
2023-09-29 18:05:51 +00:00
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "layers",
Usage: "comma or space separated list of layers to be interpreted",
Value: cli.NewStringSlice("base"),
Required: true,
},
},
2023-09-20 03:48:39 +00:00
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
}
cb, err := config.ConfigUpdate(lp, config.DefaultLotusProvider(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv())
if err != nil {
return xerrors.Errorf("cannot interpret config: %w", err)
}
fmt.Println(string(cb))
return nil
2023-09-20 03:48:39 +00:00
},
}
func getConfig(cctx *cli.Context, db *harmonydb.DB) (*config.LotusProviderConfig, error) {
lp := config.DefaultLotusProvider()
have := []string{}
2023-10-18 21:47:00 +00:00
layers := cctx.StringSlice("layers")
for _, layer := range layers {
2023-09-20 03:48:39 +00:00
text := ""
2023-09-27 03:06:00 +00:00
err := db.QueryRow(cctx.Context, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text)
2023-09-20 03:48:39 +00:00
if err != nil {
2023-09-27 03:06:00 +00:00
if strings.Contains(err.Error(), sql.ErrNoRows.Error()) {
return nil, fmt.Errorf("missing layer '%s' ", layer)
}
2023-11-09 00:01:44 +00:00
if layer == "base" {
return nil, errors.New(`lotus-provider defaults to a layer named 'base'.
Either use 'migrate' command or edit a base.toml and upload it with: lotus-provider config set base.toml`)
}
2023-09-27 03:06:00 +00:00
return nil, fmt.Errorf("could not read layer '%s': %w", layer, err)
2023-09-20 03:48:39 +00:00
}
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, " "))
}
}
2023-09-29 18:05:51 +00:00
_ = have // FUTURE: verify that required fields are here.
2023-10-02 22:08:42 +00:00
// 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.
2023-09-20 03:48:39 +00:00
return lp, nil
}