Skip to content

Commit

Permalink
refactor: Make config internal to CLI (sourcenetwork#2310)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves sourcenetwork#2257

## Description

This PR refactors the config into a simpler implementation that exists
within the `cli` package.

Notable changes:

- ~~`datastore.store` config key has been replaced with
`datastore.badger.inMemory`~~
- ~~`--store` cli flag has been replaced with `--in-memory`~~
- `log.overrides` config map has been added. (contains same keys as
`log` but for individual loggers)
- `--logger` cli flag has been temporarily removed (will be replaced in
upcoming log library)
- `init` command has been removed. (can be replaced with `rm -rf
~/.defradb` or similar)

## Tasks

- [x] I made sure the code is well commented, particularly
hard-to-understand areas.
- [x] I made sure the repository-held documentation is changed
accordingly.
- [x] I made sure the pull request title adheres to the conventional
commit style (the subset used in the project can be found in
[tools/configs/chglog/config.yml](tools/configs/chglog/config.yml)).
- [x] I made sure to discuss its limitations such as threats to
validity, vulnerability to mistake and misuse, robustness to
invalidation of assumptions, resource requirements, ...

## How has this been tested?

`make test`

Specify the platform(s) on which this was tested:
- MacOS
  • Loading branch information
nasdf authored Feb 16, 2024
1 parent 21e1f79 commit 022b0a0
Show file tree
Hide file tree
Showing 65 changed files with 596 additions and 2,385 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ In this document, we use the default configuration, which has the following beha

The GraphQL endpoint can be used with a GraphQL client (e.g., Altair) to conveniently perform requests (`query`, `mutation`) and obtain schema introspection.

Read more about the configuration [here](./docs/config.md).

## External port binding

By default the HTTP API and P2P network will use localhost. If you want to expose the ports externally you need to specify the addresses in the config or command line parameters.
Expand Down
2 changes: 1 addition & 1 deletion cli/backup_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Example: export data for the 'Users' collection:
defradb client export --collection Users user_data.json`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
store := mustGetStoreContext(cmd)
store := mustGetContextStore(cmd)

if !isValidExportFormat(format) {
return ErrInvalidExportFormat
Expand Down
2 changes: 1 addition & 1 deletion cli/backup_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Example: import data to the database:
defradb client import user_data.json`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
store := mustGetStoreContext(cmd)
store := mustGetContextStore(cmd)
return store.BasicImport(cmd.Context(), args[0])
},
}
Expand Down
20 changes: 9 additions & 11 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ package cli
import (
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/config"
"github.com/sourcenetwork/defradb/logging"
)

var log = logging.MustNewLogger("cli")

// NewDefraCommand returns the root command instanciated with its tree of subcommands.
func NewDefraCommand(cfg *config.Config) *cobra.Command {
func NewDefraCommand() *cobra.Command {
p2p_collection := MakeP2PCollectionCommand()
p2p_collection.AddCommand(
MakeP2PCollectionAddCommand(),
Expand Down Expand Up @@ -83,12 +82,12 @@ func NewDefraCommand(cfg *config.Config) *cobra.Command {

tx := MakeTxCommand()
tx.AddCommand(
MakeTxCreateCommand(cfg),
MakeTxCommitCommand(cfg),
MakeTxDiscardCommand(cfg),
MakeTxCreateCommand(),
MakeTxCommitCommand(),
MakeTxDiscardCommand(),
)

collection := MakeCollectionCommand(cfg)
collection := MakeCollectionCommand()
collection.AddCommand(
MakeCollectionGetCommand(),
MakeCollectionListDocIDsCommand(),
Expand All @@ -98,7 +97,7 @@ func NewDefraCommand(cfg *config.Config) *cobra.Command {
MakeCollectionDescribeCommand(),
)

client := MakeClientCommand(cfg)
client := MakeClientCommand()
client.AddCommand(
MakeDumpCommand(),
MakeRequestCommand(),
Expand All @@ -111,13 +110,12 @@ func NewDefraCommand(cfg *config.Config) *cobra.Command {
collection,
)

root := MakeRootCommand(cfg)
root := MakeRootCommand()
root.AddCommand(
client,
MakeStartCommand(cfg),
MakeServerDumpCmd(cfg),
MakeStartCommand(),
MakeServerDumpCmd(),
MakeVersionCommand(),
MakeInitCommand(cfg),
)

return root
Expand Down
13 changes: 7 additions & 6 deletions cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,26 @@ package cli

import (
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/config"
)

func MakeClientCommand(cfg *config.Config) *cobra.Command {
func MakeClientCommand() *cobra.Command {
var txID uint64
var cmd = &cobra.Command{
Use: "client",
Short: "Interact with a DefraDB node",
Long: `Interact with a DefraDB node.
Execute queries, add schema types, obtain node info, etc.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := loadConfig(cfg); err != nil {
if err := setContextRootDir(cmd); err != nil {
return err
}
if err := setContextConfig(cmd); err != nil {
return err
}
if err := setTransactionContext(cmd, cfg, txID); err != nil {
if err := setContextTransaction(cmd, txID); err != nil {
return err
}
return setStoreContext(cmd, cfg)
return setContextStore(cmd)
},
}
cmd.PersistentFlags().Uint64Var(&txID, "tx", 0, "Transaction ID")
Expand Down
14 changes: 8 additions & 6 deletions cli/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import (
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/config"
"github.com/sourcenetwork/defradb/datastore"
)

func MakeCollectionCommand(cfg *config.Config) *cobra.Command {
func MakeCollectionCommand() *cobra.Command {
var txID uint64
var name string
var schemaRoot string
Expand All @@ -31,16 +30,19 @@ func MakeCollectionCommand(cfg *config.Config) *cobra.Command {
Long: `Create, read, update, and delete documents within a collection.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
// cobra does not chain pre run calls so we have to run them again here
if err := loadConfig(cfg); err != nil {
if err := setContextRootDir(cmd); err != nil {
return err
}
if err := setTransactionContext(cmd, cfg, txID); err != nil {
if err := setContextConfig(cmd); err != nil {
return err
}
if err := setStoreContext(cmd, cfg); err != nil {
if err := setContextTransaction(cmd, txID); err != nil {
return err
}
store := mustGetStoreContext(cmd)
if err := setContextStore(cmd); err != nil {
return err
}
store := mustGetContextStore(cmd)

var col client.Collection
var cols []client.Collection
Expand Down
2 changes: 1 addition & 1 deletion cli/collection_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Example: create from stdin
`,
Args: cobra.RangeArgs(0, 1),
RunE: func(cmd *cobra.Command, args []string) error {
col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if !ok {
return cmd.Usage()
}
Expand Down
2 changes: 1 addition & 1 deletion cli/collection_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Example: delete by filter
defradb client collection delete --name User --filter '{ "_gte": { "points": 100 } }'
`,
RunE: func(cmd *cobra.Command, args []string) error {
col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if !ok {
return cmd.Usage()
}
Expand Down
4 changes: 2 additions & 2 deletions cli/collection_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Example: view collection by version id
defradb client collection describe --version bae123
`,
RunE: func(cmd *cobra.Command, args []string) error {
store := mustGetStoreContext(cmd)
store := mustGetContextStore(cmd)

col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if ok {
return writeJSON(cmd, col.Definition())
}
Expand Down
2 changes: 1 addition & 1 deletion cli/collection_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Example:
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if !ok {
return cmd.Usage()
}
Expand Down
2 changes: 1 addition & 1 deletion cli/collection_list_doc_ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Example:
defradb client collection docIDs --name User
`,
RunE: func(cmd *cobra.Command, args []string) error {
col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if !ok {
return cmd.Usage()
}
Expand Down
2 changes: 1 addition & 1 deletion cli/collection_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Example: update by docIDs
`,
Args: cobra.RangeArgs(0, 1),
RunE: func(cmd *cobra.Command, args []string) error {
col, ok := tryGetCollectionContext(cmd)
col, ok := tryGetContextCollection(cmd)
if !ok {
return cmd.Usage()
}
Expand Down
185 changes: 185 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2024 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package cli

import (
"os"
"path/filepath"
"strings"

"github.com/spf13/pflag"
"github.com/spf13/viper"

"github.com/sourcenetwork/defradb/logging"
)

const (
configStoreBadger = "badger"
configStoreMemory = "memory"
configLogFormatJSON = "json"
configLogFormatCSV = "csv"
configLogLevelInfo = "info"
configLogLevelDebug = "debug"
configLogLevelError = "error"
configLogLevelFatal = "fatal"
)

// configPaths are config keys that will be made relative to the rootdir
var configPaths = []string{
"datastore.badger.path",
"api.pubkeypath",
"api.privkeypath",
}

// configFlags is a mapping of config keys to cli flags to bind to.
var configFlags = map[string]string{
"log.level": "loglevel",
"log.output": "logoutput",
"log.format": "logformat",
"log.stacktrace": "logtrace",
"log.nocolor": "lognocolor",
"api.address": "url",
"datastore.maxtxnretries": "max-txn-retries",
"datastore.store": "store",
"datastore.badger.valuelogfilesize": "valuelogfilesize",
"net.peers": "peers",
"net.p2paddresses": "p2paddr",
"net.p2pdisabled": "no-p2p",
"api.allowed-origins": "allowed-origins",
"api.pubkeypath": "pubkeypath",
"api.privkeypath": "privkeypath",
}

// defaultConfig returns a new config with default values.
func defaultConfig() *viper.Viper {
cfg := viper.New()

cfg.AutomaticEnv()
cfg.SetEnvPrefix("DEFRA")
cfg.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))

cfg.SetConfigName("config")
cfg.SetConfigType("yaml")

cfg.SetDefault("datastore.badger.path", "data")
cfg.SetDefault("net.pubSubEnabled", true)
cfg.SetDefault("net.relay", false)
cfg.SetDefault("log.caller", false)

return cfg
}

// createConfig writes the default config file if one does not exist.
func createConfig(rootdir string, flags *pflag.FlagSet) error {
cfg := defaultConfig()
cfg.AddConfigPath(rootdir)

if err := bindConfigFlags(cfg, flags); err != nil {
return err
}
// make sure rootdir exists
if err := os.MkdirAll(rootdir, 0755); err != nil {
return err
}
err := cfg.SafeWriteConfig()
// error type is known and shouldn't be wrapped
//
//nolint:errorlint
if _, ok := err.(viper.ConfigFileAlreadyExistsError); ok {
return nil
}
return err
}

// loadConfig returns a new config with values from the config in the given rootdir.
func loadConfig(rootdir string, flags *pflag.FlagSet) (*viper.Viper, error) {
cfg := defaultConfig()
cfg.AddConfigPath(rootdir)

// attempt to read the existing config
err := cfg.ReadInConfig()
// error type is known and shouldn't be wrapped
//
//nolint:errorlint
if _, ok := err.(viper.ConfigFileNotFoundError); err != nil && !ok {
return nil, err
}
// bind cli flags to config keys
if err := bindConfigFlags(cfg, flags); err != nil {
return nil, err
}

// make paths relative to the rootdir
for _, key := range configPaths {
path := cfg.GetString(key)
if path != "" && !filepath.IsAbs(path) {
cfg.Set(key, filepath.Join(rootdir, path))
}
}

logCfg := loggingConfig(cfg.Sub("log"))
logCfg.OverridesByLoggerName = make(map[string]logging.Config)

// apply named logging overrides
for key := range cfg.GetStringMap("log.overrides") {
logCfg.OverridesByLoggerName[key] = loggingConfig(cfg.Sub("log.overrides." + key))
}
logging.SetConfig(logCfg)

return cfg, nil
}

// bindConfigFlags binds the set of cli flags to config values.
func bindConfigFlags(cfg *viper.Viper, flags *pflag.FlagSet) error {
for key, flag := range configFlags {
err := cfg.BindPFlag(key, flags.Lookup(flag))
if err != nil {
return err
}
}
return nil
}

// loggingConfig returns a new logging config from the given config.
func loggingConfig(cfg *viper.Viper) logging.Config {
var level int8
switch value := cfg.GetString("level"); value {
case configLogLevelDebug:
level = logging.Debug
case configLogLevelInfo:
level = logging.Info
case configLogLevelError:
level = logging.Error
case configLogLevelFatal:
level = logging.Fatal
default:
level = logging.Info
}

var format logging.EncoderFormat
switch value := cfg.GetString("format"); value {
case configLogFormatJSON:
format = logging.JSON
case configLogFormatCSV:
format = logging.CSV
default:
format = logging.CSV
}

return logging.Config{
Level: logging.NewLogLevelOption(level),
EnableStackTrace: logging.NewEnableStackTraceOption(cfg.GetBool("stacktrace")),
DisableColor: logging.NewDisableColorOption(cfg.GetBool("nocolor")),
EncoderFormat: logging.NewEncoderFormatOption(format),
OutputPaths: []string{cfg.GetString("output")},
EnableCaller: logging.NewEnableCallerOption(cfg.GetBool("caller")),
}
}
Loading

0 comments on commit 022b0a0

Please sign in to comment.