diff --git a/Makefile b/Makefile index 370d551ed4..79a6e983fb 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ ifndef VERBOSE MAKEFLAGS+=--no-print-directory endif +BUILDPATH=./build + # Provide info from git to the version package using linker flags. ifeq (, $(shell which git)) $(error "No git in $(PATH), version information won't be included") @@ -38,7 +40,7 @@ install: .PHONY: build build: ifeq ($(path),) - @go build $(BUILD_FLAGS) -o build/defradb cmd/defradb/main.go + @go build $(BUILD_FLAGS) -o $(BUILDPATH)/defradb cmd/defradb/main.go else @go build $(BUILD_FLAGS) -o $(path) cmd/defradb/main.go endif @@ -53,15 +55,15 @@ cross-build: .PHONY: start start: @$(MAKE) build - ./build/defradb start + $(BUILDPATH)/defradb start .PHONY: client\:dump client\:dump: - ./build/defradb client dump + $(BUILDPATH)/defradb client dump .PHONY: client\:add-schema client\:add-schema: - ./build/defradb client schema add -f examples/schema/bookauthpub.graphql + $(BUILDPATH)/defradb client schema add -f examples/schema/bookauthpub.graphql .PHONY: deps\:lint deps\:lint: @@ -99,7 +101,7 @@ deps: .PHONY: dev\:start dev\:start: @$(MAKE) build - DEFRA_ENV=dev ./build/defradb start + DEFRA_ENV=dev $(BUILDPATH)/defradb start # Note: In some situations `verify` can modify `go.sum` file, but until a # read-only version is available we have to rely on this. @@ -123,7 +125,7 @@ tidy: .PHONY: clean clean: go clean cmd/defradb/main.go - rm -f build/defradb + rm -f $(BUILDPATH)/defradb .PHONY: clean\:test clean\:test: @@ -142,31 +144,36 @@ endif .PHONY: test test: - gotestsum --format pkgname -- ./... -race -shuffle=on + @PATH=$(BUILDPATH):$(PATH) gotestsum --format pkgname -- ./... -race -shuffle=on + +.PHONY: test\:integrationcli +test\:integrationcli: + @$(MAKE) build + @PATH=$(BUILDPATH):$(PATH) gotestsum --format pkgname -- -race -shuffle=on ./tests/integration/cli/... -tags integrationcli .PHONY: test\:ci test\:ci: - DEFRA_BADGER_MEMORY=true DEFRA_BADGER_FILE=true $(MAKE) test:names + PATH=$(BUILDPATH):$(PATH) DEFRA_BADGER_MEMORY=true DEFRA_BADGER_FILE=true $(MAKE) test:names .PHONY: test\:go test\:go: - go test ./... -race -shuffle=on + PATH=$(BUILDPATH):$(PATH) go test ./... -race -shuffle=on .PHONY: test\:names test\:names: - gotestsum --format testname -- ./... -race -shuffle=on + PATH=$(BUILDPATH):$(PATH) gotestsum --format testname -- ./... -race -shuffle=on .PHONY: test\:verbose test\:verbose: - gotestsum --format standard-verbose -- ./... -race -shuffle=on + PATH=$(BUILDPATH):$(PATH) gotestsum --format standard-verbose -- ./... -race -shuffle=on .PHONY: test\:watch test\:watch: - gotestsum --watch -- ./... + PATH=$(BUILDPATH):$(PATH) gotestsum --watch -- ./... .PHONY: test\:clean test\:clean: - @$(MAKE) clean:test && $(MAKE) test + @$(MAKE) clean:test && PATH=$(BUILDPATH):$(PATH) $(MAKE) test .PHONY: test\:bench test\:bench: @@ -247,13 +254,13 @@ docs\:cli: .PHONY: docs\:manpages docs\:manpages: - go run cmd/genmanpages/main.go -o build/man/ + go run cmd/genmanpages/main.go -o $(BUILDPATH)/man/ detectedOS := $(shell uname) .PHONY: install\:manpages install\:manpages: ifeq ($(detectedOS),Linux) - cp build/man/* /usr/share/man/man1/ + cp $(BUILDPATH)/man/* /usr/share/man/man1/ endif ifneq ($(detectedOS),Linux) @echo "Direct installation of Defradb's man pages is not supported on your system." diff --git a/cli/addreplicator.go b/cli/addreplicator.go index 07dd489837..2004638f78 100644 --- a/cli/addreplicator.go +++ b/cli/addreplicator.go @@ -18,60 +18,60 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" "github.com/sourcenetwork/defradb/logging" netclient "github.com/sourcenetwork/defradb/net/api/client" ) -var addReplicatorCmd = &cobra.Command{ - Use: "addreplicator ", - Short: "Add a new replicator", - Long: `Use this command if you wish to add a new target replicator +func MakeAddReplicatorCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "addreplicator ", + Short: "Add a new replicator", + Long: `Use this command if you wish to add a new target replicator for the p2p data sync system.`, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 2 { - if err := cmd.Usage(); err != nil { - return err + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + if err := cmd.Usage(); err != nil { + return err + } + return errors.New("must specify two arguments: collection and peer") + } + collection := args[0] + peerAddr, err := ma.NewMultiaddr(args[1]) + if err != nil { + return errors.Wrap("could not parse peer address", err) } - return errors.New("must specify two arguments: collection and peer") - } - collection := args[0] - peerAddr, err := ma.NewMultiaddr(args[1]) - if err != nil { - return errors.Wrap("could not parse peer address", err) - } - - log.FeedbackInfo( - cmd.Context(), - "Adding replicator for collection", - logging.NewKV("PeerAddress", peerAddr), - logging.NewKV("Collection", collection), - logging.NewKV("RPCAddress", cfg.Net.RPCAddress), - ) - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return errors.Wrap("failed to create RPC client", err) - } + log.FeedbackInfo( + cmd.Context(), + "Adding replicator for collection", + logging.NewKV("PeerAddress", peerAddr), + logging.NewKV("Collection", collection), + logging.NewKV("RPCAddress", cfg.Net.RPCAddress), + ) - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } + cred := insecure.NewCredentials() + client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) + if err != nil { + return errors.Wrap("failed to create RPC client", err) + } - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() + rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() + if err != nil { + return errors.Wrap("failed to parse RPC timeout duration", err) + } - pid, err := client.AddReplicator(ctx, collection, peerAddr) - if err != nil { - return errors.Wrap("failed to add replicator, request failed", err) - } - log.FeedbackInfo(ctx, "Successfully added replicator", logging.NewKV("PID", pid)) - return nil - }, -} + ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) + defer cancel() -func init() { - rpcCmd.AddCommand(addReplicatorCmd) + pid, err := client.AddReplicator(ctx, collection, peerAddr) + if err != nil { + return errors.Wrap("failed to add replicator, request failed", err) + } + log.FeedbackInfo(ctx, "Successfully added replicator", logging.NewKV("PID", pid)) + return nil + }, + } + return cmd } diff --git a/cli/blocks.go b/cli/blocks.go index 8a4ebc9385..9e55c36d22 100644 --- a/cli/blocks.go +++ b/cli/blocks.go @@ -14,11 +14,11 @@ import ( "github.com/spf13/cobra" ) -var blocksCmd = &cobra.Command{ - Use: "blocks", - Short: "Interact with the database's blockstore", -} +func MakeBlocksCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "blocks", + Short: "Interact with the database's blockstore", + } -func init() { - clientCmd.AddCommand(blocksCmd) + return cmd } diff --git a/cli/blocks_get.go b/cli/blocks_get.go index ddfcc72125..4eb5b40bf9 100644 --- a/cli/blocks_get.go +++ b/cli/blocks_get.go @@ -19,64 +19,64 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var getCmd = &cobra.Command{ - Use: "get [CID]", - Short: "Get a block by its CID from the blockstore.", - RunE: func(cmd *cobra.Command, args []string) (err error) { - if len(args) != 1 { - return errors.New("missing argument: CID") - } - cid := args[0] - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.BlocksPath, cid) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } +func MakeBlocksGetCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "get [CID]", + Short: "Get a block by its CID from the blockstore.", + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("missing argument: CID") + } + cid := args[0] - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed to send get request", err) - } + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.BlocksPath, cid) + if err != nil { + return errors.Wrap("failed to join endpoint", err) + } - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + res, err := http.Get(endpoint.String()) + if err != nil { + return errors.Wrap("failed to send get request", err) } - }() - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) + response, err := io.ReadAll(res.Body) if err != nil { - return errors.Wrap("failed to handle GraphQL errors", err) + return errors.Wrap("failed to read response body", err) } - indentedResult, err := indentJSON(response) + + stdout, err := os.Stdout.Stat() if err != nil { - return errors.Wrap("failed to pretty print response", err) + return errors.Wrap("failed to stat stdout", err) } - if graphlErr { - log.FeedbackError(cmd.Context(), indentedResult) + if isFileInfoPipe(stdout) { + cmd.Println(string(response)) } else { - log.FeedbackInfo(cmd.Context(), indentedResult) + graphlErr, err := hasGraphQLErrors(response) + if err != nil { + return errors.Wrap("failed to handle GraphQL errors", err) + } + indentedResult, err := indentJSON(response) + if err != nil { + return errors.Wrap("failed to pretty print response", err) + } + if graphlErr { + log.FeedbackError(cmd.Context(), indentedResult) + } else { + log.FeedbackInfo(cmd.Context(), indentedResult) + } } - } - return nil - }, -} - -func init() { - blocksCmd.AddCommand(getCmd) + return nil + }, + } + return cmd } diff --git a/cli/cli.go b/cli/cli.go index 2467d1759e..82a28907bc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -30,6 +30,8 @@ import ( "github.com/sourcenetwork/defradb/logging" ) +var log = logging.MustNewLogger("defra.cli") + const badgerDatastoreName = "badger" // List of cobra errors indicating an error occurred in the way the command was invoked. @@ -42,36 +44,90 @@ var usageErrors = []string{ "missing argument", // custom to defradb } -var log = logging.MustNewLogger("defra.cli") +type DefraCommand struct { + RootCmd *cobra.Command + Cfg *config.Config +} -var cfg = config.DefaultConfig() -var RootCmd = rootCmd +// NewDefraCommand returns the root command instanciated with its tree of subcommands. +func NewDefraCommand(cfg *config.Config) DefraCommand { + rootCmd := MakeRootCommand(cfg) + rpcCmd := MakeRPCCommand(cfg) + blocksCmd := MakeBlocksCommand() + schemaCmd := MakeSchemaCommand() + clientCmd := MakeClientCommand() + rpcCmd.AddCommand( + MakeAddReplicatorCommand(cfg), + ) + blocksCmd.AddCommand( + MakeBlocksGetCommand(cfg), + ) + schemaCmd.AddCommand( + MakeSchemaAddCommand(cfg), + ) + clientCmd.AddCommand( + MakeDumpCommand(cfg), + MakePingCommand(cfg), + MakeBlocksCommand(), + MakeQueryCommand(cfg), + MakePeerIDCommand(cfg), + schemaCmd, + rpcCmd, + blocksCmd, + ) + rootCmd.AddCommand( + clientCmd, + MakeStartCommand(cfg), + MakeServerDumpCmd(cfg), + MakeVersionCommand(), + MakeInitCommand(cfg), + ) + return DefraCommand{rootCmd, cfg} +} -func Execute() { - ctx := context.Background() +func (defraCmd *DefraCommand) Execute(ctx context.Context) error { // Silence cobra's default output to control usage and error display. - rootCmd.SilenceUsage = true - rootCmd.SilenceErrors = true - rootCmd.SetOut(os.Stdout) - err := rootCmd.ExecuteContext(ctx) + defraCmd.RootCmd.SilenceUsage = true + defraCmd.RootCmd.SilenceErrors = true + defraCmd.RootCmd.SetOut(os.Stdout) + err := defraCmd.RootCmd.ExecuteContext(ctx) if err != nil { for _, cobraError := range usageErrors { if strings.HasPrefix(err.Error(), cobraError) { log.FeedbackErrorE(ctx, "Usage error", err) - if usageErr := rootCmd.Usage(); usageErr != nil { + if usageErr := defraCmd.RootCmd.Usage(); usageErr != nil { log.FeedbackFatalE(ctx, "error displaying usage help", usageErr) } - os.Exit(1) + return err } } - log.FeedbackFatalE(ctx, "Execution error", err) + log.FeedbackErrorE(ctx, "Execution error", err) + return err + } + return nil +} + +func (defraCmd *DefraCommand) GetConfigAsJSON() []byte { + json, err := defraCmd.Cfg.ToJSON() + if err != nil { + log.FatalE(context.Background(), "Couldn't get loaded config as JSON", err) } + return json } func isFileInfoPipe(fi os.FileInfo) bool { return fi.Mode()&os.ModeNamedPipe != 0 } +func dirExists(rootDir string) bool { + statInfo, err := os.Stat(rootDir) + exists := (err == nil && statInfo.IsDir()) + if errors.Is(err, os.ErrNotExist) { + return false + } + return exists +} + func readStdin() (string, error) { var s strings.Builder scanner := bufio.NewScanner(os.Stdin) diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000000..589bc7bd78 --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,43 @@ +// Copyright 2022 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 ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/config" +) + +// Verify that the top-level commands are registered, and if particular ones have subcommands. +func TestNewDefraCommand(t *testing.T) { + expectedCommandNames := []string{ + "client", + "init", + "server-dump", + "start", + "version", + } + actualCommandNames := []string{} + r := NewDefraCommand(config.NewWithDefaults()) + for _, c := range r.RootCmd.Commands() { + actualCommandNames = append(actualCommandNames, c.Name()) + } + for _, expectedCommandName := range expectedCommandNames { + assert.Contains(t, actualCommandNames, expectedCommandName) + } + for _, c := range r.RootCmd.Commands() { + if c.Name() == "client" { + assert.NotEmpty(t, c.Commands()) + } + } +} diff --git a/cli/client.go b/cli/client.go index c218e22fec..1e6ba43ae5 100644 --- a/cli/client.go +++ b/cli/client.go @@ -14,13 +14,13 @@ import ( "github.com/spf13/cobra" ) -var clientCmd = &cobra.Command{ - Use: "client", - Short: "Interact with a running DefraDB node as a client", - Long: `Interact with a running DefraDB node as a client. +func MakeClientCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "client", + Short: "Interact with a running DefraDB node as a client", + Long: `Interact with a running DefraDB node as a client. Execute queries, add schema types, and run debug routines.`, -} + } -func init() { - rootCmd.AddCommand(clientCmd) + return cmd } diff --git a/cli/dump.go b/cli/dump.go index 071a52669d..a23d160e7e 100644 --- a/cli/dump.go +++ b/cli/dump.go @@ -20,62 +20,62 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var dumpCmd = &cobra.Command{ - Use: "dump", - Short: "Dump the contents of a database node-side", - RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Requesting the database to dump its state, server-side...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.DumpPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } +func MakeDumpCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "dump", + Short: "Dump the contents of a database node-side", + RunE: func(cmd *cobra.Command, _ []string) (err error) { + stdout, err := os.Stdout.Stat() + if err != nil { + return errors.Wrap("failed to stat stdout", err) + } + if !isFileInfoPipe(stdout) { + log.FeedbackInfo(cmd.Context(), "Requesting the database to dump its state, server-side...") + } - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed dump request", err) - } + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.DumpPath) + if err != nil { + return errors.Wrap("failed to join endpoint", err) + } - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + res, err := http.Get(endpoint.String()) + if err != nil { + return errors.Wrap("failed dump request", err) } - }() - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - // dumpResponse follows structure of HTTP API's response - type dumpResponse struct { - Data struct { - Response string `json:"response"` - } `json:"data"` - } - r := dumpResponse{} - err = json.Unmarshal(response, &r) + response, err := io.ReadAll(res.Body) if err != nil { - return errors.Wrap("failed parsing of response", err) + return errors.Wrap("failed to read response body", err) } - log.FeedbackInfo(cmd.Context(), r.Data.Response) - } - return nil - }, -} -func init() { - clientCmd.AddCommand(dumpCmd) + if isFileInfoPipe(stdout) { + cmd.Println(string(response)) + } else { + // dumpResponse follows structure of HTTP API's response + type dumpResponse struct { + Data struct { + Response string `json:"response"` + } `json:"data"` + } + r := dumpResponse{} + err = json.Unmarshal(response, &r) + if err != nil { + return errors.Wrap("failed parsing of response", err) + } + log.FeedbackInfo(cmd.Context(), r.Data.Response) + } + return nil + }, + } + return cmd } diff --git a/cli/init.go b/cli/init.go index 07ccc72723..6f3e7586a0 100644 --- a/cli/init.go +++ b/cli/init.go @@ -20,93 +20,72 @@ import ( "github.com/sourcenetwork/defradb/errors" ) -var reinitialize bool - /* -The `init` command initializes the configuration file and root directory.. +The `init` command initializes the configuration file and root directory. It covers three possible situations: - root dir doesn't exist - root dir exists and doesn't contain a config file - root dir exists and contains a config file */ -var initCmd = &cobra.Command{ - Use: "init [rootdir]", - Short: "Initialize DefraDB's root directory and configuration file", - Long: "Initialize a directory for configuration and data at the given path.", - // Load a default configuration, considering env. variables and CLI flags. - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - err := cfg.LoadWithoutRootDir() - if err != nil { - return errors.Wrap("failed to load configuration", err) - } - - // parse loglevel overrides. - // binding the flags / EnvVars to the struct - return parseAndConfigLog(cmd.Context(), cfg.Log, cmd) - }, - RunE: func(cmd *cobra.Command, args []string) error { - rootDirPath := "" - if len(args) == 1 { - rootDirPath = args[0] - } else if len(args) > 1 { - if err := cmd.Usage(); err != nil { - return err +func MakeInitCommand(cfg *config.Config) *cobra.Command { + var reinitialize bool + var cmd = &cobra.Command{ + Use: "init", + Short: "Initialize DefraDB's root directory and configuration file", + Long: "Initialize a directory for configuration and data at the given path.", + // Load a default configuration, considering env. variables and CLI flags. + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + withoutConfigFile := false + if err := cfg.Load(withoutConfigFile); err != nil { + return errors.Wrap("failed to load configuration", err) } - return errors.New("init command requires one rootdir argument, or no argument") - } - rootDir, rootDirExists, err := config.GetRootDir(rootDirPath) - if err != nil { - return errors.Wrap("failed to get root dir", err) - } - if rootDirExists { - // we assume the config file is using its default path in the rootdir - configFilePath := fmt.Sprintf("%v/%v", rootDir, config.DefaultDefraDBConfigFileName) - info, err := os.Stat(configFilePath) - configFileExists := (err == nil && !info.IsDir()) - if configFileExists { + + // parse loglevel overrides. + // binding the flags / EnvVars to the struct + return parseAndConfigLog(cmd.Context(), cfg.Log, cmd) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + if dirExists(cfg.Rootdir) { if reinitialize { - err = os.Remove(configFilePath) - if err != nil { - return errors.Wrap("failed to remove configuration file", err) + if cfg.ConfigFileExists() { + if err := os.Remove(cfg.ConfigFilePath()); err != nil { + return errors.Wrap("failed to remove configuration file", err) + } + if err := cfg.WriteConfigFile(); err != nil { + return errors.Wrap("failed to create configuration file", err) + } + log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Reinitialized configuration file at %v", cfg.ConfigFilePath())) + } else { + if err := cfg.WriteConfigFile(); err != nil { + return errors.Wrap("failed to create configuration file", err) + } + log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Initialized configuration file at %v", cfg.ConfigFilePath())) } - err = cfg.WriteConfigFileToRootDir(rootDir) - if err != nil { - return errors.Wrap("failed to create configuration file", err) - } - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Reinitialized configuration file at %v", configFilePath)) } else { - log.FeedbackError( - cmd.Context(), - fmt.Sprintf( + if cfg.ConfigFileExists() { + log.FeedbackError(cmd.Context(), fmt.Sprintf( "Configuration file already exists at %v. Consider using --reinitialize", - configFilePath, + cfg.ConfigFilePath(), ), - ) + ) + } } } else { - err = cfg.WriteConfigFileToRootDir(rootDir) - if err != nil { - return errors.Wrap("failed to create configuration file", err) + if err := cfg.CreateRootDir(); err != nil { + return errors.Wrap("failed to create root directory", err) } - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Initialized configuration file at %v", configFilePath)) - } - } else { - err = config.CreateRootDirWithDefaultConfig(rootDir) - if err != nil { - return errors.Wrap("failed to create root dir", err) + if err := cfg.WriteConfigFile(); err != nil { + return errors.Wrap("failed to create config file", err) + } + log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Initialized configuration file at %v", cfg.ConfigFilePath())) } - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Created DefraDB root directory at %v", rootDir)) - } - return nil - }, -} - -func init() { - rootCmd.AddCommand(initCmd) - - initCmd.Flags().BoolVar( + return nil + }, + } + cmd.Flags().BoolVar( &reinitialize, "reinitialize", false, "Reinitialize the configuration file", ) + return cmd } diff --git a/cli/peerid.go b/cli/peerid.go index 23075ffa7c..091f5fd0cf 100644 --- a/cli/peerid.go +++ b/cli/peerid.go @@ -20,82 +20,82 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var peerIDCmd = &cobra.Command{ - Use: "peerid", - Short: "Get the peer ID of the DefraDB node", - RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Requesting peer ID...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PeerIDPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } +func MakePeerIDCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "peerid", + Short: "Get the peer ID of the DefraDB node", + RunE: func(cmd *cobra.Command, _ []string) (err error) { + stdout, err := os.Stdout.Stat() + if err != nil { + return errors.Wrap("failed to stat stdout", err) + } + if !isFileInfoPipe(stdout) { + log.FeedbackInfo(cmd.Context(), "Requesting peer ID...") + } - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed to request peer ID", err) - } + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PeerIDPath) + if err != nil { + return errors.Wrap("failed to join endpoint", err) + } - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + res, err := http.Get(endpoint.String()) + if err != nil { + return errors.Wrap("failed to request peer ID", err) } - }() - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() - if res.StatusCode == http.StatusNotFound { - r := httpapi.ErrorResponse{} - err = json.Unmarshal(response, &r) + response, err := io.ReadAll(res.Body) if err != nil { - return errors.Wrap("parsing of response failed", err) + return errors.Wrap("failed to read response body", err) } - if len(r.Errors) > 0 { - if isFileInfoPipe(stdout) { - b, err := json.Marshal(r.Errors[0]) - if err != nil { - return errors.Wrap("mashalling error response failed", err) + + if res.StatusCode == http.StatusNotFound { + r := httpapi.ErrorResponse{} + err = json.Unmarshal(response, &r) + if err != nil { + return errors.Wrap("parsing of response failed", err) + } + if len(r.Errors) > 0 { + if isFileInfoPipe(stdout) { + b, err := json.Marshal(r.Errors[0]) + if err != nil { + return errors.Wrap("mashalling error response failed", err) + } + cmd.Println(string(b)) + } else { + log.FeedbackInfo(cmd.Context(), r.Errors[0].Message) } - cmd.Println(string(b)) - } else { - log.FeedbackInfo(cmd.Context(), r.Errors[0].Message) + return nil } - return nil + return errors.New("no peer ID available. P2P might be disabled") } - return errors.New("no peer ID available. P2P might be disabled") - } - r := httpapi.DataResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("parsing of response failed", err) - } - if isFileInfoPipe(stdout) { - b, err := json.Marshal(r.Data) + r := httpapi.DataResponse{} + err = json.Unmarshal(response, &r) if err != nil { - return errors.Wrap("mashalling data response failed", err) + return errors.Wrap("parsing of response failed", err) + } + if isFileInfoPipe(stdout) { + b, err := json.Marshal(r.Data) + if err != nil { + return errors.Wrap("mashalling data response failed", err) + } + cmd.Println(string(b)) + } else if data, ok := r.Data.(map[string]any); ok { + log.FeedbackInfo(cmd.Context(), data["peerID"].(string)) } - cmd.Println(string(b)) - } else if data, ok := r.Data.(map[string]any); ok { - log.FeedbackInfo(cmd.Context(), data["peerID"].(string)) - } - - return nil - }, -} -func init() { - clientCmd.AddCommand(peerIDCmd) + return nil + }, + } + return cmd } diff --git a/cli/peerid_test.go b/cli/peerid_test.go index 8095f9f8b7..fb9cd00e8f 100644 --- a/cli/peerid_test.go +++ b/cli/peerid_test.go @@ -34,6 +34,8 @@ func setTestingAddresses(cfg *config.Config) { } func TestGetPeerIDCmd(t *testing.T) { + cfg := config.NewWithDefaults() + peerIDCmd := MakePeerIDCommand(cfg) dir := t.TempDir() ctx := context.Background() cfg.Datastore.Store = "memory" @@ -41,7 +43,7 @@ func TestGetPeerIDCmd(t *testing.T) { cfg.Net.P2PDisabled = false setTestingAddresses(cfg) - di, err := start(ctx) + di, err := start(ctx, cfg) if err != nil { t.Fatal(err) } @@ -70,6 +72,8 @@ func TestGetPeerIDCmd(t *testing.T) { } func TestGetPeerIDCmdWithNoP2P(t *testing.T) { + cfg := config.NewWithDefaults() + peerIDCmd := MakePeerIDCommand(cfg) dir := t.TempDir() ctx := context.Background() cfg.Datastore.Store = "memory" @@ -77,7 +81,7 @@ func TestGetPeerIDCmdWithNoP2P(t *testing.T) { cfg.Net.P2PDisabled = true setTestingAddresses(cfg) - di, err := start(ctx) + di, err := start(ctx, cfg) if err != nil { t.Fatal(err) } diff --git a/cli/ping.go b/cli/ping.go index 6dbd16aa37..11ca129850 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -20,61 +20,61 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var pingCmd = &cobra.Command{ - Use: "ping", - Short: "Ping to test connection to a node", - RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Sending ping...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PingPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } +func MakePingCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "ping", + Short: "Ping to test connection to a node", + RunE: func(cmd *cobra.Command, _ []string) (err error) { + stdout, err := os.Stdout.Stat() + if err != nil { + return errors.Wrap("failed to stat stdout", err) + } + if !isFileInfoPipe(stdout) { + log.FeedbackInfo(cmd.Context(), "Sending ping...") + } - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed to send ping", err) - } + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PingPath) + if err != nil { + return errors.Wrap("failed to join endpoint", err) + } - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + res, err := http.Get(endpoint.String()) + if err != nil { + return errors.Wrap("failed to send ping", err) } - }() - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - type pingResponse struct { - Data struct { - Response string `json:"response"` - } `json:"data"` - } - r := pingResponse{} - err = json.Unmarshal(response, &r) + response, err := io.ReadAll(res.Body) if err != nil { - return errors.Wrap("parsing of response failed", err) + return errors.Wrap("failed to read response body", err) } - log.FeedbackInfo(cmd.Context(), r.Data.Response) - } - return nil - }, -} -func init() { - clientCmd.AddCommand(pingCmd) + if isFileInfoPipe(stdout) { + cmd.Println(string(response)) + } else { + type pingResponse struct { + Data struct { + Response string `json:"response"` + } `json:"data"` + } + r := pingResponse{} + err = json.Unmarshal(response, &r) + if err != nil { + return errors.Wrap("parsing of response failed", err) + } + log.FeedbackInfo(cmd.Context(), r.Data.Response) + } + return nil + }, + } + return cmd } diff --git a/cli/query.go b/cli/query.go index 7975ea1b36..6afc12b3ce 100644 --- a/cli/query.go +++ b/cli/query.go @@ -20,13 +20,15 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var queryCmd = &cobra.Command{ - Use: "query [query]", - Short: "Send a DefraDB GraphQL query", - Long: `Send a DefraDB GraphQL query to the database. +func MakeQueryCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "query [query]", + Short: "Send a DefraDB GraphQL query", + Long: `Send a DefraDB GraphQL query to the database. A query can be sent as a single argument. Example command: defradb client query 'query { ... }' @@ -38,102 +40,100 @@ A GraphQL client such as GraphiQL (https://github.com/graphql/graphiql) can be u with the database more conveniently. To learn more about the DefraDB GraphQL Query Language, refer to https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - var query string + RunE: func(cmd *cobra.Command, args []string) (err error) { + var query string - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if len(args) > 1 { - if err = cmd.Usage(); err != nil { - return err - } - return errors.New("too many arguments") - } - - if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client query -' to read from stdin. Example: 'cat my.graphql | defradb client query -').", - ) - return nil - } else if len(args) == 0 { - err := cmd.Help() + fi, err := os.Stdin.Stat() if err != nil { - return errors.Wrap("failed to print help", err) + return err } - return nil - } else if args[0] == "-" { - stdin, err := readStdin() - if err != nil { - return errors.Wrap("failed to read stdin", err) + + if len(args) > 1 { + if err = cmd.Usage(); err != nil { + return err + } + return errors.New("too many arguments") } - if len(stdin) == 0 { - return errors.New("no query in stdin provided") + + if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { + log.FeedbackInfo( + cmd.Context(), + "Run 'defradb client query -' to read from stdin. Example: 'cat my.graphql | defradb client query -').", + ) + return nil + } else if len(args) == 0 { + err := cmd.Help() + if err != nil { + return errors.Wrap("failed to print help", err) + } + return nil + } else if args[0] == "-" { + stdin, err := readStdin() + if err != nil { + return errors.Wrap("failed to read stdin", err) + } + if len(stdin) == 0 { + return errors.New("no query in stdin provided") + } else { + query = stdin + } } else { - query = stdin + query = args[0] + } + + if query == "" { + return errors.New("query cannot be empty") + } + + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.GraphQLPath) + if err != nil { + return errors.Wrap("joining paths failed", err) } - } else { - query = args[0] - } - - if query == "" { - return errors.New("query cannot be empty") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.GraphQLPath) - if err != nil { - return errors.Wrap("joining paths failed", err) - } - - p := url.Values{} - p.Add("query", query) - endpoint.RawQuery = p.Encode() - - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed query", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + + p := url.Values{} + p.Add("query", query) + endpoint.RawQuery = p.Encode() + + res, err := http.Get(endpoint.String()) + if err != nil { + return errors.Wrap("failed query", err) } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - fi, err = os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - - if isFileInfoPipe(fi) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) + + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() + + response, err := io.ReadAll(res.Body) if err != nil { - return errors.Wrap("failed to handle GraphQL errors", err) + return errors.Wrap("failed to read response body", err) } - indentedResult, err := indentJSON(response) + + fi, err = os.Stdout.Stat() if err != nil { - return errors.Wrap("failed to pretty print result", err) + return errors.Wrap("failed to stat stdout", err) } - if graphlErr { - log.FeedbackError(cmd.Context(), indentedResult) + + if isFileInfoPipe(fi) { + cmd.Println(string(response)) } else { - log.FeedbackInfo(cmd.Context(), indentedResult) + graphlErr, err := hasGraphQLErrors(response) + if err != nil { + return errors.Wrap("failed to handle GraphQL errors", err) + } + indentedResult, err := indentJSON(response) + if err != nil { + return errors.Wrap("failed to pretty print result", err) + } + if graphlErr { + log.FeedbackError(cmd.Context(), indentedResult) + } else { + log.FeedbackInfo(cmd.Context(), indentedResult) + } } - } - return nil - }, -} - -func init() { - clientCmd.AddCommand(queryCmd) + return nil + }, + } + return cmd } diff --git a/cli/root.go b/cli/root.go index d2ca7a3707..ad52c5148d 100644 --- a/cli/root.go +++ b/cli/root.go @@ -12,127 +12,111 @@ package cli import ( "context" - "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var rootDirParam string - -var rootCmd = &cobra.Command{ - Use: "defradb", - Short: "DefraDB Edge Database", - Long: `DefraDB is the edge database to power the user-centric future. +func MakeRootCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "defradb", + Short: "DefraDB Edge Database", + Long: `DefraDB is the edge database to power the user-centric future. Start a database node, query a local or remote node, and much more. DefraDB is released under the BSL license, (c) 2022 Democratized Data Foundation. See https://docs.source.network/BSL.txt for more information. `, - // Runs on subcommands before their Run function, to handle configuration and top-level flags. - // Loads the rootDir containing the configuration file, otherwise warn about it and load a default configuration. - // This allows some subcommands (`init`, `start`) to override the PreRun to create a rootDir by default. - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - rootDir, exists, err := config.GetRootDir(rootDirParam) - if err != nil { - return errors.Wrap("failed to get root dir", err) - } - defaultConfig := false - if exists { - err := cfg.Load(rootDir) - if err != nil { + // Runs on subcommands before their Run function, to handle configuration and top-level flags. + // Loads the rootDir containing the configuration file, otherwise warn about it and load a default configuration. + // This allows some subcommands (`init`, `start`) to override the PreRun to create a rootDir by default. + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + withConfigFile := cfg.ConfigFileExists() + if err := cfg.Load(withConfigFile); err != nil { return errors.Wrap("failed to load config", err) } - } else { - err := cfg.LoadWithoutRootDir() - if err != nil { - return errors.Wrap("failed to load config", err) + + // parse loglevel overrides + // we use `cfg.Logging.Level` as an argument since the viper.Bind already handles + // binding the flags / EnvVars to the struct + if err := parseAndConfigLog(cmd.Context(), cfg.Log, cmd); err != nil { + return err } - defaultConfig = true - } - - // parse loglevel overrides - // we use `cfg.Logging.Level` as an argument since the viper.Bind already handles - // binding the flags / EnvVars to the struct - if err := parseAndConfigLog(cmd.Context(), cfg.Log, cmd); err != nil { - return err - } - - if defaultConfig { - log.FeedbackInfo(cmd.Context(), "Using default configuration") - } else { - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Configuration loaded from DefraDB directory %v", rootDir)) - } - return nil - }, -} + return nil + }, + } + + var err error -func init() { - rootCmd.PersistentFlags().StringVar( - &rootDirParam, "rootdir", "", + cmd.PersistentFlags().String( + "rootdir", cfg.Rootdir, "Directory for data and configuration to use (default \"$HOME/.defradb\")", ) + err = cfg.BindFlag("rootdircli", cmd.PersistentFlags().Lookup("rootdir")) + if err != nil { + log.FeedbackFatalE(context.Background(), "Could not bind rootdir", err) + } - rootCmd.PersistentFlags().String( + cmd.PersistentFlags().String( "loglevel", cfg.Log.Level, "Log level to use. Options are debug, info, error, fatal", ) - err := viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("loglevel")) + err = cfg.BindFlag("log.level", cmd.PersistentFlags().Lookup("loglevel")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind logging.loglevel", err) } - rootCmd.PersistentFlags().StringArray( + cmd.PersistentFlags().StringArray( "logger", []string{}, "Override logger parameters. Usage: --logger ,level=,output=,...", ) - rootCmd.PersistentFlags().String( + cmd.PersistentFlags().String( "logoutput", cfg.Log.Output, "Log output path", ) - err = viper.BindPFlag("log.output", rootCmd.PersistentFlags().Lookup("logoutput")) + err = cfg.BindFlag("log.output", cmd.PersistentFlags().Lookup("logoutput")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind log.output", err) } - rootCmd.PersistentFlags().String( + cmd.PersistentFlags().String( "logformat", cfg.Log.Format, "Log format to use. Options are csv, json", ) - err = viper.BindPFlag("log.format", rootCmd.PersistentFlags().Lookup("logformat")) + err = cfg.BindFlag("log.format", cmd.PersistentFlags().Lookup("logformat")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind log.format", err) } - rootCmd.PersistentFlags().Bool( + cmd.PersistentFlags().Bool( "logtrace", cfg.Log.Stacktrace, "Include stacktrace in error and fatal logs", ) - err = viper.BindPFlag("log.stacktrace", rootCmd.PersistentFlags().Lookup("logtrace")) + err = cfg.BindFlag("log.stacktrace", cmd.PersistentFlags().Lookup("logtrace")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind log.stacktrace", err) } - rootCmd.PersistentFlags().Bool( + cmd.PersistentFlags().Bool( "lognocolor", cfg.Log.NoColor, "Disable colored log output", ) - err = viper.BindPFlag("log.nocolor", rootCmd.PersistentFlags().Lookup("lognocolor")) + err = cfg.BindFlag("log.nocolor", cmd.PersistentFlags().Lookup("lognocolor")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind log.nocolor", err) } - rootCmd.PersistentFlags().String( + cmd.PersistentFlags().String( "url", cfg.API.Address, "URL of HTTP endpoint to listen on or connect to", ) - err = viper.BindPFlag("api.address", rootCmd.PersistentFlags().Lookup("url")) + err = cfg.BindFlag("api.address", cmd.PersistentFlags().Lookup("url")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind api.address", err) } + return cmd } diff --git a/cli/rpc.go b/cli/rpc.go index 08bd4d7d3d..6bac1651f1 100644 --- a/cli/rpc.go +++ b/cli/rpc.go @@ -14,23 +14,23 @@ import ( "context" "github.com/spf13/cobra" - "github.com/spf13/viper" -) -var rpcCmd = &cobra.Command{ - Use: "rpc", - Short: "Interact with a DefraDB gRPC server", - Long: "Interact with a DefraDB gRPC server.", -} + "github.com/sourcenetwork/defradb/config" +) -func init() { - rpcCmd.PersistentFlags().String( +func MakeRPCCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "rpc", + Short: "Interact with a DefraDB gRPC server", + Long: "Interact with a DefraDB gRPC server.", + } + cmd.PersistentFlags().String( "addr", cfg.Net.RPCAddress, "gRPC endpoint address", ) - err := viper.BindPFlag("net.rpcaddress", rpcCmd.PersistentFlags().Lookup("addr")) + err := cfg.BindFlag("net.rpcaddress", cmd.PersistentFlags().Lookup("addr")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind net.rpcaddress", err) } - clientCmd.AddCommand(rpcCmd) + return cmd } diff --git a/cli/schema.go b/cli/schema.go index f64e38ce97..dc96539c71 100644 --- a/cli/schema.go +++ b/cli/schema.go @@ -14,12 +14,12 @@ import ( "github.com/spf13/cobra" ) -var schemaCmd = &cobra.Command{ - Use: "schema", - Short: "Interact with the schema system of a running DefraDB instance", - Long: "Make changes, updates, or look for existing schema types to a DefraDB node.", -} +func MakeSchemaCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "schema", + Short: "Interact with the schema system of a running DefraDB instance", + Long: `Make changes, updates, or look for existing schema types to a DefraDB node.`, + } -func init() { - clientCmd.AddCommand(schemaCmd) + return cmd } diff --git a/cli/schema_add.go b/cli/schema_add.go index 585637f486..813a4a13c3 100644 --- a/cli/schema_add.go +++ b/cli/schema_add.go @@ -21,15 +21,16 @@ import ( "github.com/spf13/cobra" httpapi "github.com/sourcenetwork/defradb/api/http" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -var schemaFile string - -var addCmd = &cobra.Command{ - Use: "add [schema]", - Short: "Add a new schema type to DefraDB", - Long: `Add a new schema type to DefraDB. +func MakeSchemaAddCommand(cfg *config.Config) *cobra.Command { + var schemaFile string + var cmd = &cobra.Command{ + Use: "add [schema]", + Short: "Add a new schema type to DefraDB", + Long: `Add a new schema type to DefraDB. Example: add from an argument string: defradb client schema add 'type Foo { ... }' @@ -40,115 +41,113 @@ Example: add from file: Example: add from stdin: cat schema.graphql | defradb client schema add - -To learn more about the DefraDB GraphQL Schema Language, refer to https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - var schema string - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if len(args) > 1 { - if err = cmd.Usage(); err != nil { +Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + var schema string + fi, err := os.Stdin.Stat() + if err != nil { return err } - return errors.New("too many arguments") - } - if schemaFile != "" { - buf, err := os.ReadFile(schemaFile) - if err != nil { - return errors.Wrap("failed to read schema file", err) + if len(args) > 1 { + if err = cmd.Usage(); err != nil { + return err + } + return errors.New("too many arguments") } - schema = string(buf) - } else if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client schema add -' to read from stdin."+ - " Example: 'cat schema.graphql | defradb client schema add -').", - ) - return nil - } else if len(args) == 0 { - err := cmd.Help() - if err != nil { - return errors.Wrap("failed to print help", err) + + if schemaFile != "" { + buf, err := os.ReadFile(schemaFile) + if err != nil { + return errors.Wrap("failed to read schema file", err) + } + schema = string(buf) + } else if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { + log.FeedbackInfo( + cmd.Context(), + "Run 'defradb client schema add -' to read from stdin."+ + " Example: 'cat schema.graphql | defradb client schema add -').", + ) + return nil + } else if len(args) == 0 { + err := cmd.Help() + if err != nil { + return errors.Wrap("failed to print help", err) + } + return nil + } else if args[0] == "-" { + stdin, err := readStdin() + if err != nil { + return errors.Wrap("failed to read stdin", err) + } + if len(stdin) == 0 { + return errors.New("no schema in stdin provided") + } else { + schema = stdin + } + } else { + schema = args[0] } - return nil - } else if args[0] == "-" { - stdin, err := readStdin() + + if schema == "" { + return errors.New("empty schema provided") + } + + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaLoadPath) if err != nil { - return errors.Wrap("failed to read stdin", err) + return errors.Wrap("join paths failed", err) } - if len(stdin) == 0 { - return errors.New("no schema in stdin provided") - } else { - schema = stdin + + res, err := http.Post(endpoint.String(), "text", strings.NewReader(schema)) + if err != nil { + return errors.Wrap("failed to post schema", err) } - } else { - schema = args[0] - } - - if schema == "" { - return errors.New("empty schema provided") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaLoadPath) - if err != nil { - return errors.Wrap("join paths failed", err) - } - - res, err := http.Post(endpoint.String(), "text", strings.NewReader(schema)) - if err != nil { - return errors.Wrap("failed to post schema", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + + defer func() { + if e := res.Body.Close(); e != nil { + err = errors.Wrap(fmt.Sprintf("failed to read response body: %v", e.Error()), err) + } + }() + + response, err := io.ReadAll(res.Body) + if err != nil { + return errors.Wrap("failed to read response body", err) } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) + + stdout, err := os.Stdout.Stat() if err != nil { - return errors.Wrap("failed to handle GraphQL errors", err) + return errors.Wrap("failed to stat stdout", err) } - if graphlErr { - indentedResult, err := indentJSON(response) - if err != nil { - return errors.Wrap("failed to pretty print result", err) - } - log.FeedbackError(cmd.Context(), indentedResult) + if isFileInfoPipe(stdout) { + cmd.Println(string(response)) } else { - type schemaResponse struct { - Data struct { - Result string `json:"result"` - } `json:"data"` - } - r := schemaResponse{} - err = json.Unmarshal(response, &r) + graphlErr, err := hasGraphQLErrors(response) if err != nil { - return errors.Wrap("failed to unmarshal response", err) + return errors.Wrap("failed to handle GraphQL errors", err) + } + if graphlErr { + indentedResult, err := indentJSON(response) + if err != nil { + return errors.Wrap("failed to pretty print result", err) + } + log.FeedbackError(cmd.Context(), indentedResult) + } else { + type schemaResponse struct { + Data struct { + Result string `json:"result"` + } `json:"data"` + } + r := schemaResponse{} + err = json.Unmarshal(response, &r) + if err != nil { + return errors.Wrap("failed to unmarshal response", err) + } + log.FeedbackInfo(cmd.Context(), r.Data.Result) } - log.FeedbackInfo(cmd.Context(), r.Data.Result) } - } - return nil - }, -} - -func init() { - schemaCmd.AddCommand(addCmd) - addCmd.Flags().StringVarP(&schemaFile, "file", "f", "", "File to load a schema from") + return nil + }, + } + cmd.Flags().StringVarP(&schemaFile, "file", "f", "", "File to load a schema from") + return cmd } diff --git a/cli/serverdump.go b/cli/serverdump.go index 108e24d6e3..f339e3a15e 100644 --- a/cli/serverdump.go +++ b/cli/serverdump.go @@ -18,58 +18,57 @@ import ( ds "github.com/ipfs/go-datastore" "github.com/spf13/cobra" + "github.com/sourcenetwork/defradb/config" badgerds "github.com/sourcenetwork/defradb/datastore/badger/v3" "github.com/sourcenetwork/defradb/db" "github.com/sourcenetwork/defradb/errors" "github.com/sourcenetwork/defradb/logging" ) -var datastore string +func MakeServerDumpCmd(cfg *config.Config) *cobra.Command { + var datastore string + cmd := &cobra.Command{ + Use: "server-dump", + Short: "Dumps the state of the entire database", + RunE: func(cmd *cobra.Command, _ []string) error { + log.FeedbackInfo(cmd.Context(), "Starting DefraDB process...") -var serverDumpCmd = &cobra.Command{ - Use: "server-dump", - Short: "Dumps the state of the entire database", - RunE: func(cmd *cobra.Command, _ []string) error { - log.FeedbackInfo(cmd.Context(), "Starting DefraDB process...") + // setup signal handlers + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, os.Interrupt) - // setup signal handlers - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, os.Interrupt) - - var rootstore ds.Batching - var err error - if datastore == badgerDatastoreName { - info, err := os.Stat(cfg.Datastore.Badger.Path) - exists := (err == nil && info.IsDir()) - if !exists { - return errors.New(fmt.Sprintf( - "badger store does not exist at %s. Try with an existing directory", - cfg.Datastore.Badger.Path, - )) + var rootstore ds.Batching + var err error + if datastore == badgerDatastoreName { + info, err := os.Stat(cfg.Datastore.Badger.Path) + exists := (err == nil && info.IsDir()) + if !exists { + return errors.New(fmt.Sprintf( + "badger store does not exist at %s. Try with an existing directory", + cfg.Datastore.Badger.Path, + )) + } + log.FeedbackInfo(cmd.Context(), "Opening badger store", logging.NewKV("Path", cfg.Datastore.Badger.Path)) + rootstore, err = badgerds.NewDatastore(cfg.Datastore.Badger.Path, cfg.Datastore.Badger.Options) + if err != nil { + return errors.Wrap("could not open badger datastore", err) + } + } else { + return errors.New("server-side dump is only supported for the Badger datastore") } - log.FeedbackInfo(cmd.Context(), "Opening badger store", logging.NewKV("Path", cfg.Datastore.Badger.Path)) - rootstore, err = badgerds.NewDatastore(cfg.Datastore.Badger.Path, cfg.Datastore.Badger.Options) + + db, err := db.NewDB(cmd.Context(), rootstore) if err != nil { - return errors.Wrap("could not open badger datastore", err) + return errors.Wrap("failed to initialize database", err) } - } else { - return errors.New("server-side dump is only supported for the Badger datastore") - } - - db, err := db.NewDB(cmd.Context(), rootstore) - if err != nil { - return errors.Wrap("failed to initialize database", err) - } - - log.FeedbackInfo(cmd.Context(), "Dumping DB state...") - return db.PrintDump(cmd.Context()) - }, -} -func init() { - rootCmd.AddCommand(serverDumpCmd) - serverDumpCmd.Flags().StringVar( + log.FeedbackInfo(cmd.Context(), "Dumping DB state...") + return db.PrintDump(cmd.Context()) + }, + } + cmd.Flags().StringVar( &datastore, "store", cfg.Datastore.Store, "Datastore to use. Options are badger, memory", ) + return cmd } diff --git a/cli/start.go b/cli/start.go index cefc8b924b..9a42e04156 100644 --- a/cli/start.go +++ b/cli/start.go @@ -23,7 +23,6 @@ import ( ds "github.com/ipfs/go-datastore" ma "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" - "github.com/spf13/viper" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" @@ -40,147 +39,127 @@ import ( "github.com/sourcenetwork/defradb/node" ) -var startCmd = &cobra.Command{ - Use: "start", - Short: "Start a DefraDB node", - Long: "Start a new instance of DefraDB node.", - // Load the root config if it exists, otherwise create it. - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - rootDir, exists, err := config.GetRootDir(rootDirParam) - if err != nil { - return errors.Wrap("failed to get root dir", err) - } - if !exists { - err = config.CreateRootDirWithDefaultConfig(rootDir) - if err != nil { - return errors.Wrap("failed to create root dir", err) +func MakeStartCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "start", + Short: "Start a DefraDB node", + Long: "Start a new instance of DefraDB node.", + // Load the root config if it exists, otherwise create it. + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + withConfigFile := cfg.ConfigFileExists() + if err := cfg.Load(withConfigFile); err != nil { + return errors.Wrap("failed to load config", err) } - } - err = cfg.Load(rootDir) - if err != nil { - return errors.Wrap("failed to load config", err) - } - // parse loglevel overrides - if err := parseAndConfigLog(cmd.Context(), cfg.Log, cmd); err != nil { - return err - } - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Configuration loaded from DefraDB directory %v", rootDir)) - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - di, err := start(cmd.Context()) - if err != nil { - return err - } - - wait(cmd.Context(), di) + // parse loglevel overrides + if err := parseAndConfigLog(cmd.Context(), cfg.Log, cmd); err != nil { + return err + } + // log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Configuration loaded from DefraDB directory %v", rootDir)) + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + di, err := start(cmd.Context(), cfg) + if err != nil { + return err + } - return nil - }, -} + wait(cmd.Context(), di) -func init() { - startCmd.Flags().String( - "address", cfg.API.Address, - "Specify the HTTP API endpoint address", - ) - err := viper.BindPFlag("api.address", startCmd.Flags().Lookup("address")) - if err != nil { - log.FeedbackFatalE(context.Background(), "Could not bind api.address", err) + return nil + }, } - startCmd.Flags().String( + cmd.Flags().String( "peers", cfg.Net.Peers, "List of peers to connect to", ) - err = viper.BindPFlag("net.peers", startCmd.Flags().Lookup("peers")) + err := cfg.BindFlag("net.peers", cmd.Flags().Lookup("peers")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind net.peers", err) } - startCmd.Flags().String( + cmd.Flags().String( "store", cfg.Datastore.Store, "Specify the datastore to use (supported: badger, memory)", ) - err = viper.BindPFlag("datastore.store", startCmd.Flags().Lookup("store")) + err = cfg.BindFlag("datastore.store", cmd.Flags().Lookup("store")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind datastore.store", err) } - startCmd.Flags().Var( + cmd.Flags().Var( &cfg.Datastore.Badger.ValueLogFileSize, "valuelogfilesize", "Specify the datastore value log file size (in bytes). In memory size will be 2*valuelogfilesize", ) - err = viper.BindPFlag("datastore.badger.valuelogfilesize", startCmd.Flags().Lookup("valuelogfilesize")) + err = cfg.BindFlag("datastore.badger.valuelogfilesize", cmd.Flags().Lookup("valuelogfilesize")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind datastore.badger.valuelogfilesize", err) } - startCmd.Flags().String( + cmd.Flags().String( "p2paddr", cfg.Net.P2PAddress, "Listener address for the p2p network (formatted as a libp2p MultiAddr)", ) - err = viper.BindPFlag("net.p2paddress", startCmd.Flags().Lookup("p2paddr")) + err = cfg.BindFlag("net.p2paddress", cmd.Flags().Lookup("p2paddr")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind net.p2paddress", err) } - startCmd.Flags().String( + cmd.Flags().String( "tcpaddr", cfg.Net.TCPAddress, "Listener address for the tcp gRPC server (formatted as a libp2p MultiAddr)", ) - err = viper.BindPFlag("net.tcpaddress", startCmd.Flags().Lookup("tcpaddr")) + err = cfg.BindFlag("net.tcpaddress", cmd.Flags().Lookup("tcpaddr")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind net.tcpaddress", err) } - startCmd.Flags().Bool( + cmd.Flags().Bool( "no-p2p", cfg.Net.P2PDisabled, "Disable the peer-to-peer network synchronization system", ) - err = viper.BindPFlag("net.p2pdisabled", startCmd.Flags().Lookup("no-p2p")) + err = cfg.BindFlag("net.p2pdisabled", cmd.Flags().Lookup("no-p2p")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind net.p2pdisabled", err) } - startCmd.Flags().Bool( + cmd.Flags().Bool( "tls", cfg.API.TLS, "Enable serving the API over https", ) - err = viper.BindPFlag("api.tls", startCmd.Flags().Lookup("tls")) + err = cfg.BindFlag("api.tls", cmd.Flags().Lookup("tls")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind api.tls", err) } - startCmd.Flags().String( + cmd.Flags().String( "pubkeypath", cfg.API.PubKeyPath, "Path to the public key for tls", ) - err = viper.BindPFlag("api.pubkeypath", startCmd.Flags().Lookup("pubkeypath")) + err = cfg.BindFlag("api.pubkeypath", cmd.Flags().Lookup("pubkeypath")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind api.pubkeypath", err) } - startCmd.Flags().String( + cmd.Flags().String( "privkeypath", cfg.API.PrivKeyPath, "Path to the private key for tls", ) - err = viper.BindPFlag("api.privkeypath", startCmd.Flags().Lookup("privkeypath")) + err = cfg.BindFlag("api.privkeypath", cmd.Flags().Lookup("privkeypath")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind api.privkeypath", err) } - startCmd.Flags().String( + cmd.Flags().String( "email", cfg.API.Email, "Email address used by the CA for notifications", ) - err = viper.BindPFlag("api.email", startCmd.Flags().Lookup("email")) + err = cfg.BindFlag("api.email", cmd.Flags().Lookup("email")) if err != nil { log.FeedbackFatalE(context.Background(), "Could not bind api.email", err) } - - rootCmd.AddCommand(startCmd) + return cmd } type defraInstance struct { @@ -209,7 +188,7 @@ func (di *defraInstance) close(ctx context.Context) { } } -func start(ctx context.Context) (*defraInstance, error) { +func start(ctx context.Context, cfg *config.Config) (*defraInstance, error) { log.FeedbackInfo(ctx, "Starting DefraDB service...") var rootstore ds.Batching @@ -310,14 +289,9 @@ func start(ctx context.Context) (*defraInstance, error) { }() } - rootDir, _, err := config.GetRootDir(rootDirParam) - if err != nil { - return nil, errors.Wrap("failed to get root dir", err) - } - sOpt := []func(*httpapi.Server){ httpapi.WithAddress(cfg.API.Address), - httpapi.WithRootDir(rootDir), + httpapi.WithRootDir(cfg.Rootdir), } if n != nil { @@ -374,8 +348,13 @@ func wait(ctx context.Context, di *defraInstance) { signalCh := make(chan os.Signal, 1) signal.Notify(signalCh, os.Interrupt) - <-signalCh - log.FeedbackInfo(ctx, "Received interrupt; closing database...") - di.close(ctx) - os.Exit(0) + select { + case <-ctx.Done(): + log.FeedbackInfo(ctx, "Stopped process. Received context cancellation.") + case <-signalCh: + log.FeedbackInfo(ctx, "Received interrupt; closing database...") + di.close(ctx) + os.Exit(0) + return + } } diff --git a/cli/version.go b/cli/version.go index 96a9ff1b0f..8842697699 100644 --- a/cli/version.go +++ b/cli/version.go @@ -20,42 +20,40 @@ import ( "github.com/sourcenetwork/defradb/version" ) -var format string -var full bool - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Display the version information of DefraDB and its components", - RunE: func(cmd *cobra.Command, _ []string) error { - dv, err := version.NewDefraVersion() - if err != nil { - return err - } - switch format { - case "json": - var buf bytes.Buffer - dvj, err := json.Marshal(dv) - if err != nil { - return err - } - err = json.Indent(&buf, dvj, "", " ") +func MakeVersionCommand() *cobra.Command { + var format string + var full bool + var cmd = &cobra.Command{ + Use: "version", + Short: "Display the version information of DefraDB and its components", + RunE: func(cmd *cobra.Command, _ []string) error { + dv, err := version.NewDefraVersion() if err != nil { return err } - cmd.Println(buf.String()) - default: - if full { - cmd.Println(dv.StringFull()) - } else { - cmd.Println(dv.String()) + switch format { + case "json": + var buf bytes.Buffer + dvj, err := json.Marshal(dv) + if err != nil { + return err + } + err = json.Indent(&buf, dvj, "", " ") + if err != nil { + return err + } + cmd.Println(buf.String()) + default: + if full { + cmd.Println(dv.StringFull()) + } else { + cmd.Println(dv.String()) + } } - } - return nil - }, -} - -func init() { - versionCmd.Flags().StringVarP(&format, "format", "f", "", "Version output format. Options are text, json") - versionCmd.Flags().BoolVarP(&full, "full", "", false, "Display the full version information") - rootCmd.AddCommand(versionCmd) + return nil + }, + } + cmd.Flags().StringVarP(&format, "format", "f", "", "Version output format. Options are text, json") + cmd.Flags().BoolVarP(&full, "full", "", false, "Display the full version information") + return cmd } diff --git a/cli/version_test.go b/cli/version_test.go new file mode 100644 index 0000000000..0d4bb6025d --- /dev/null +++ b/cli/version_test.go @@ -0,0 +1,89 @@ +// Copyright 2022 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 ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +// The version information comes from the build process which is not [easily] accessible from tests. +// Therefore we test that the command outputs the expected formats *without the version info*. + +// case: no args, meaning `--format text` +func TestVersionNoArg(t *testing.T) { + cmd := MakeVersionCommand() + buf := new(bytes.Buffer) + cmd.SetOut(buf) + err := cmd.Execute() + assert.NoError(t, err) + t.Log(buf.String()) + assert.Contains(t, buf.String(), "defradb") + assert.Contains(t, buf.String(), "built with Go") +} + +// case: `--full`, meaning `--format text --full` +func TestVersionFull(t *testing.T) { + cmd := MakeVersionCommand() + buf := new(bytes.Buffer) + cmd.SetOut(buf) + cmd.SetArgs([]string{"--full"}) + err := cmd.Execute() + assert.NoError(t, err) + t.Log(buf.String()) + assert.Contains(t, buf.String(), "* HTTP API") + assert.Contains(t, buf.String(), "* DocKey versions") + assert.Contains(t, buf.String(), "* P2P multicodec") +} + +// case: `--format json` +func TestVersionJSON(t *testing.T) { + cmd := MakeVersionCommand() + buf := new(bytes.Buffer) + cmd.SetOut(buf) + cmd.SetArgs([]string{"--format", "json"}) + err := cmd.Execute() + assert.NoError(t, err) + t.Log(buf.String()) + assert.JSONEq(t, buf.String(), ` + { + "release": "", + "commit": "", + "commitdate": "", + "go": "", + "httpapi": "v0", + "dockeyversions": "1", + "netprotocol": "/defra/0.0.1" + }`) +} + +// case: `--format json --full` (is equivalent to previous one) +func TestVersionJSONFull(t *testing.T) { + cmd := MakeVersionCommand() + buf := new(bytes.Buffer) + cmd.SetOut(buf) + cmd.SetArgs([]string{"--format", "json", "--full"}) + err := cmd.Execute() + assert.NoError(t, err) + t.Log(buf.String()) + assert.JSONEq(t, buf.String(), ` + { + "release": "", + "commit": "", + "commitdate": "", + "go": "", + "httpapi": "v0", + "dockeyversions": "1", + "netprotocol": "/defra/0.0.1" + }`) +} diff --git a/cmd/defradb/main.go b/cmd/defradb/main.go index 2f8208f781..509c9768c4 100644 --- a/cmd/defradb/main.go +++ b/cmd/defradb/main.go @@ -11,9 +11,20 @@ // defradb is a decentralized peer-to-peer, user-centric, privacy-focused document database. package main -import "github.com/sourcenetwork/defradb/cli" +import ( + "context" + "os" + + "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" +) // Execute adds all child commands to the root command and sets flags appropriately. func main() { - cli.Execute() + cfg := config.NewWithDefaults() + ctx := context.Background() + defraCmd := cli.NewDefraCommand(cfg) + if defraCmd.Execute(ctx) != nil { + os.Exit(-1) + } } diff --git a/cmd/genclidocs/genclidocs.go b/cmd/genclidocs/genclidocs.go index f644a63f6b..8a93c79d52 100644 --- a/cmd/genclidocs/genclidocs.go +++ b/cmd/genclidocs/genclidocs.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra/doc" "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/logging" ) @@ -33,8 +34,9 @@ func main() { if err != nil { log.FatalE(context.Background(), "Creating the filesystem path failed", err) } - cli.RootCmd.DisableAutoGenTag = true - err = doc.GenMarkdownTree(cli.RootCmd, *path) + defraCmd := cli.NewDefraCommand(config.NewWithDefaults()) + defraCmd.RootCmd.DisableAutoGenTag = true + err = doc.GenMarkdownTree(defraCmd.RootCmd, *path) if err != nil { log.FatalE(context.Background(), "Generating cmd docs failed", err) } diff --git a/cmd/genmanpages/main.go b/cmd/genmanpages/main.go index fe3f36d324..026ef4cc74 100644 --- a/cmd/genmanpages/main.go +++ b/cmd/genmanpages/main.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra/doc" "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/logging" ) @@ -45,7 +46,8 @@ func genRootManPages(dir string) { if err != nil { log.FatalE(ctx, "Failed to create directory", err, logging.NewKV("dir", dir)) } - err = doc.GenManTree(cli.RootCmd, header, dir) + defraCmd := cli.NewDefraCommand(config.NewWithDefaults()) + err = doc.GenManTree(defraCmd.RootCmd, header, dir) if err != nil { log.FatalE(ctx, "Failed generation of man pages", err) } diff --git a/config/config.go b/config/config.go index 82d8d0ec43..3d8964a72f 100644 --- a/config/config.go +++ b/config/config.go @@ -16,7 +16,7 @@ default options, a method providing test configurations, a method for validation (e.g. with warnings). This is extensible. The 'root directory' is where the configuration file and data of a DefraDB instance exists. It is specified as a global -flag `defradb --rootdir path/to/somewhere`, or with the DEFRA_ROOT environment variable. +flag `defradb --rootdir path/to/somewhere`, or with the DEFRA_ROOTDIR environment variable. Some packages of DefraDB provide their own configuration approach (logging, node). For each, a way to go from top-level configuration to package-specific configuration is provided. @@ -36,7 +36,7 @@ How to use, e.g. without using a rootdir: cfg := config.DefaultConfig() cfg.NetConfig.P2PDisabled = true // as example - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) if err != nil { ... @@ -49,6 +49,7 @@ import ( "encoding/json" "fmt" "net" + "os" "path/filepath" "strconv" "strings" @@ -58,6 +59,7 @@ import ( "github.com/mitchellh/mapstructure" ma "github.com/multiformats/go-multiaddr" + "github.com/spf13/pflag" "github.com/spf13/viper" badgerds "github.com/sourcenetwork/defradb/datastore/badger/v3" @@ -69,12 +71,12 @@ import ( var log = logging.MustNewLogger("defra.config") const ( - DefraEnvPrefix = "DEFRA" - defaultDefraDBRootDir = ".defradb" - logLevelDebug = "debug" - logLevelInfo = "info" - logLevelError = "error" - logLevelFatal = "fatal" + defraEnvPrefix = "DEFRA" + defaultRootDirName = ".defradb" + logLevelDebug = "debug" + logLevelInfo = "info" + logLevelError = "error" + logLevelFatal = "fatal" ) // Config is DefraDB's main configuration struct, embedding component-specific config structs. @@ -83,81 +85,102 @@ type Config struct { API *APIConfig Net *NetConfig Log *LoggingConfig + Rootdir string + v *viper.Viper } -// Load Config and handles parameters from config file, environment variables. +// Loads Config from defaults, config file (conditionally), environment variables, and CLI flags (last has priority). // To use on a Config struct already loaded with default values from DefaultConfig(). -func (cfg *Config) Load(rootDirPath string) error { - viper.SetConfigName(DefaultDefraDBConfigFileName) - viper.SetConfigType(configType) - viper.AddConfigPath(rootDirPath) - if err := viper.ReadInConfig(); err != nil { - return err - } - - viper.SetEnvPrefix(DefraEnvPrefix) - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() +func (cfg *Config) Load(withConfigFile bool) error { + var err error - err := viper.Unmarshal(cfg, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc())) - if err != nil { + // start with the default logging configuration + if err = cfg.applyLoggingConfig(); err != nil { return err } - cfg.handleParams(rootDirPath) - err = cfg.validate() - if err != nil { + if err = cfg.v.BindEnv("rootdir"); err != nil { return err } - return nil -} + // WIP explain + if r := cfg.v.GetString("rootdircli"); r != "" { + // WIP validate ? + cfg.v.Set("rootdir", r) + cfg.Rootdir = r + } -// LoadWithoutRootDir loads Config and handles parameters from defaults, environment variables, and CLI flags - -// not from config file. -// To use on a Config struct already loaded with default values from DefaultConfig(). -func (cfg *Config) LoadWithoutRootDir() error { - // With Viper, we use a config file to provide a basic structure and set defaults, for env. variables to load. - viper.SetConfigType(configType) - configbytes, err := cfg.toBytes() - if err != nil { - return err + if withConfigFile { + cfg.v.AddConfigPath(cfg.Rootdir) + if err := cfg.v.ReadInConfig(); err != nil { + return err + } + } else { + // from default configuration + var configbytes []byte + if configbytes, err = cfg.toBytes(); err != nil { + return err + } + if err = cfg.v.ReadConfig(bytes.NewReader(configbytes)); err != nil { + return err + } } - err = viper.ReadConfig(bytes.NewReader(configbytes)) - if err != nil { + + cfg.v.AutomaticEnv() + + // unmarshal the viper structure into the config struct + if err = cfg.v.Unmarshal(cfg, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc())); err != nil { return err } - viper.SetEnvPrefix(DefraEnvPrefix) - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() + cfg.paramsPostprocessing() - err = viper.Unmarshal(cfg, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc())) - if err != nil { + if err = cfg.validate(); err != nil { return err } - rootDir, err := DefaultRootDir() - if err != nil { - log.FatalE(context.Background(), "Could not get home directory", err) - } - cfg.handleParams(rootDir) - err = cfg.validate() - if err != nil { + // apply the logging configuration from obtained configuration + if err = cfg.applyLoggingConfig(); err != nil { return err } + return nil } -// DefaultConfig returns the default configuration. -func DefaultConfig() *Config { +// NewWithDefaults returns the default configuration. +func NewWithDefaults() *Config { return &Config{ Datastore: defaultDatastoreConfig(), API: defaultAPIConfig(), Net: defaultNetConfig(), Log: defaultLogConfig(), + Rootdir: defaultRootDir(), + v: newDefaultViper(), } } +func newDefaultViper() *viper.Viper { + options := []viper.Option{ + viper.EnvKeyReplacer(strings.NewReplacer(".", "_")), + } + v := viper.NewWithOptions(options...) + // for our special DEFRA_ env vars + v.SetEnvPrefix(defraEnvPrefix) + // for now, only one type supported + v.SetConfigType(configType) + // only this specific config file name supported + v.SetConfigName(DefaultConfigFileName) + return v +} + +func (cfg *Config) applyLoggingConfig() error { + loggingConfig, err := cfg.Log.ToLoggerConfig() + if err != nil { + return errors.Wrap("could not get logging config", err) + } + logging.SetConfig(loggingConfig) + return nil +} + func (cfg *Config) validate() error { if err := cfg.Datastore.validate(); err != nil { return errors.Wrap("failed to validate Datastore config", err) @@ -174,10 +197,11 @@ func (cfg *Config) validate() error { return nil } -func (cfg *Config) handleParams(rootDir string) { +// some params require post-processing +func (cfg *Config) paramsPostprocessing() { // We prefer using absolute paths. if !filepath.IsAbs(cfg.Datastore.Badger.Path) { - cfg.Datastore.Badger.Path = filepath.Join(rootDir, cfg.Datastore.Badger.Path) + cfg.Datastore.Badger.Path = filepath.Join(cfg.Rootdir, cfg.Datastore.Badger.Path) } cfg.setBadgerVLogMaxSize() } @@ -328,6 +352,8 @@ func (apicfg *APIConfig) validate() error { if err != nil { return errors.Wrap("invalid database URL", err) } + } else if ip == nil { + return errors.New("invalid database URL provided") } return nil } @@ -542,6 +568,14 @@ func (cfg *Config) GetLoggingConfig() (logging.Config, error) { return cfg.Log.ToLoggerConfig() } +func defaultRootDir() string { + home, err := os.UserHomeDir() + if err != nil { + log.FatalE(context.Background(), "error determining user directory", err) + } + return filepath.Join(home, defaultRootDirName) +} + // ToJSON serializes the config to a JSON string. func (c *Config) ToJSON() ([]byte, error) { jsonbytes, err := json.Marshal(c) @@ -563,3 +597,10 @@ func (c *Config) toBytes() ([]byte, error) { } return buffer.Bytes(), nil } + +func (cfg *Config) BindFlag(key string, flag *pflag.Flag) error { + if err := cfg.v.BindPFlag(key, flag); err != nil { + return err + } + return nil +} diff --git a/config/config_template_yaml.txt b/config/config_template_yaml.txt new file mode 100644 index 0000000000..e5394f76e1 --- /dev/null +++ b/config/config_template_yaml.txt @@ -0,0 +1,64 @@ +# DefraDB configuration (YAML) + +# NOTE: Paths below are relative to the DefraDB directory. +# By default, the DefraDB directory is "$HOME/.defradb", but +# can be changed via the $DEFRA_ROOTDIR env variable or --rootdir CLI flag. + +datastore: + # Store can be badger | memory + # badger: fast pure Go key-value store optimized for SSDs (https://github.com/dgraph-io/badger) + # memory: in-memory version of badger + store: {{ .Datastore.Store }} + badger: + path: {{ .Datastore.Badger.Path }} + # Maximum file size of the value log files. The in-memory file size will be 2*valuelogfilesize. + # Human friendly units can be used (ex: 500MB). + valuelogfilesize: {{ .Datastore.Badger.ValueLogFileSize }} + # memory: + # size: {{ .Datastore.Memory.Size }} + +api: + # Address of the HTTP API to listen on or connect to + address: {{ .API.Address }} + # Whether the API server should listen over HTTPS + tls: {{ .API.TLS }} + # The path to the public key file. Ignored if domains is set. + pubkeypath: {{ .API.PubKeyPath }} + # The path to the private key file. Ignored if domains is set. + privkeypath: {{ .API.PrivKeyPath }} + # Email address to let the CA (Let's Encrypt) send notifications via email when there are issues (optional). + # email: {{ .API.Email }} + +net: + # Whether the P2P is disabled + p2pdisabled: {{ .Net.P2PDisabled }} + # Listening address of the P2P network + p2paddress: {{ .Net.P2PAddress }} + # Listening address of the RPC endpoint + rpcaddress: {{ .Net.RPCAddress }} + # gRPC server address + tcpaddress: {{ .Net.TCPAddress }} + # Time duration after which a RPC connection to a peer times out + rpctimeout: {{ .Net.RPCTimeout }} + # Whether the node has pubsub enabled or not + pubsub: {{ .Net.PubSubEnabled }} + # Enable libp2p's Circuit relay transport protocol https://docs.libp2p.io/concepts/circuit-relay/ + relay: {{ .Net.RelayEnabled }} + # List of peers to boostrap with, specified as multiaddresses (https://docs.libp2p.io/concepts/addressing/) + peers: {{ .Net.Peers }} + # Amount of time after which an idle RPC connection would be closed + RPCMaxConnectionIdle: {{ .Net.RPCMaxConnectionIdle }} + +log: + # Log level. Options are debug, info, error, fatal + level: {{ .Log.Level }} + # Include stacktrace in error and fatal logs + stacktrace: {{ .Log.Stacktrace }} + # Supported log formats are json, csv + format: {{ .Log.Format }} + # Where the log output is written to + output: {{ .Log.Output }} + # Disable colored log output + nocolor: {{ .Log.NoColor }} + # Caller location in log output + caller: {{ .Log.Caller }} \ No newline at end of file diff --git a/config/config_test.go b/config/config_test.go index a1bb7d1f94..7367c79f93 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -24,7 +24,7 @@ import ( "github.com/sourcenetwork/defradb/node" ) -var envVarsDifferentThanDefault = map[string]string{ +var envVarsDifferent = map[string]string{ "DEFRA_DATASTORE_STORE": "memory", "DEFRA_DATASTORE_BADGER_PATH": "defra_data", "DEFRA_API_ADDRESS": "localhost:9999", @@ -69,14 +69,36 @@ func FixtureEnvVarsUnset(envVars map[string]string) { // Gives a path to a temporary directory containing a default config file func FixtureDefaultConfigFile(t *testing.T) string { dir := t.TempDir() - cfg := DefaultConfig() - - cfg.writeConfigFile(dir) + cfg := NewWithDefaults() + assert.NoError(t, cfg.WriteConfigFile()) return dir } +// // not sure how this behaves in parallel +// func envSet(t *testing.T, envs map[string]string) (cleanup func()) { +// originalEnvs := map[string]string{} + +// for k, v := range envs { +// if orig, ok := os.LookupEnv(k); ok { +// originalEnvs[k] = orig +// } +// t.Setenv(k, v) +// } + +// return func() { +// for k := range envs { +// orig, has := originalEnvs[k] +// if has { +// t.Setenv(k, orig) +// } else { +// _ = os.Unsetenv(k) +// } +// } +// } +// } + func TestConfigValidateBasic(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() assert.NoError(t, cfg.validate()) // Borked configuration gives out error cfg.API.Address = "localhost" @@ -87,7 +109,7 @@ func TestConfigValidateBasic(t *testing.T) { } func TestJSONSerialization(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() var m map[string]any b, errSerialize := cfg.ToJSON() @@ -102,183 +124,193 @@ func TestJSONSerialization(t *testing.T) { // 2022-06-20T14:03:14.284-0500, WARN, defra.cli, WTF initConfig, {"cfg": "eyJEYXRhc3RvcmUiOnsiU3RvcmUiOiJiYWRnZXIiLCJNZW1vcnkiOnsiU2l6ZSI6MH0sIkJhZGdlciI6eyJQYXRoIjoiZGF0YSJ9fSwiQVBJIjp7IkFkZHJlc3MiOiJsb2NhbGhvc3Q6OTE4MSJ9LCJOZXQiOnsiUDJQQWRkcmVzcyI6Ii9pcDQvMC4wLjAuMC90Y3AvOTE3MSIsIlAyUERpc2FibGVkIjpmYWxzZSwiUGVlcnMiOiIiLCJQdWJTdWJFbmFibGVkIjp0cnVlLCJSZWxheUVuYWJsZWQiOnRydWUsIlJQQ0FkZHJlc3MiOiIwLjAuMC4wOjkxNjEiLCJSUENNYXhDb25uZWN0aW9uSWRsZSI6IjVtIiwiUlBDVGltZW91dCI6IjEwcyIsIlRDUEFkZHJlc3MiOiIvaXA0LzAuMC4wLjAvdGNwLzkxNjEifSwiTG9nZ2luZyI6eyJMZXZlbCI6ImRlYnVnIiwiU3RhY2t0cmFjZSI6ZmFsc2UsIkZvcm1hdCI6ImNzdiIsIk91dHB1dFBhdGgiOiJzdGRvdXQiLCJDb2xvciI6dHJ1ZX19"} func TestLoadDefaultsConfigFileEnv(t *testing.T) { - dir := t.TempDir() - cfg := DefaultConfig() - errWriteConfig := cfg.WriteConfigFileToRootDir(dir) - FixtureEnvVars(envVarsDifferentThanDefault) - defer FixtureEnvVarsUnset(envVarsDifferentThanDefault) - - errLoad := cfg.Load(dir) + tmpdir := t.TempDir() + cfg := NewWithDefaults() + cfg.Rootdir = tmpdir + FixtureEnvVars(envVarsDifferent) + defer FixtureEnvVarsUnset(envVarsDifferent) + assert.NoError(t, cfg.Load(false)) + errWriteConfig := cfg.WriteConfigFile() + assert.NoError(t, errWriteConfig) + errLoad := cfg.Load(true) assert.NoError(t, errLoad) - assert.NoError(t, errWriteConfig) - assert.Equal(t, "localhost:9999", cfg.API.Address) - assert.Equal(t, filepath.Join(dir, "defra_data"), cfg.Datastore.Badger.Path) + + assert.Equal(t, envVarsDifferent["DEFRA_API_ADDRESS"], cfg.API.Address) + assert.Equal(t, + filepath.Join(tmpdir, envVarsDifferent["DEFRA_DATASTORE_BADGER_PATH"]), + cfg.Datastore.Badger.Path, + ) } func TestLoadDefaultsEnv(t *testing.T) { - cfg := DefaultConfig() - FixtureEnvVars(envVarsDifferentThanDefault) - defer FixtureEnvVarsUnset(envVarsDifferentThanDefault) + cfg := NewWithDefaults() + FixtureEnvVars(envVarsDifferent) + defer FixtureEnvVarsUnset(envVarsDifferent) - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.NoError(t, err) - assert.Equal(t, "localhost:9999", cfg.API.Address) - defaultRootDir, _ := DefaultRootDir() - assert.Equal(t, filepath.Join(defaultRootDir, "defra_data"), cfg.Datastore.Badger.Path) + assert.Equal(t, envVarsDifferent["DEFRA_API_ADDRESS"], cfg.API.Address) + assert.Equal(t, filepath.Join(cfg.Rootdir, envVarsDifferent["DEFRA_DATASTORE_BADGER_PATH"]), cfg.Datastore.Badger.Path) +} + +func stringtoBool(t *testing.T, s string) bool { + t.Helper() + if s == "true" { + return true + } else if s == "false" { + return false + } + t.Fail() + return false } func TestEnvVariablesAllConsidered(t *testing.T) { - cfg := DefaultConfig() - FixtureEnvVars(envVarsDifferentThanDefault) - defer FixtureEnvVarsUnset(envVarsDifferentThanDefault) + cfg := NewWithDefaults() + FixtureEnvVars(envVarsDifferent) + defer FixtureEnvVarsUnset(envVarsDifferent) - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.NoError(t, err) - assert.Equal(t, "localhost:9999", cfg.API.Address) - defaultRootDir, _ := DefaultRootDir() - assert.Equal(t, filepath.Join(defaultRootDir, "defra_data"), cfg.Datastore.Badger.Path) - assert.Equal(t, "memory", cfg.Datastore.Store) - assert.Equal(t, true, cfg.Net.P2PDisabled) - assert.Equal(t, "/ip4/0.0.0.0/tcp/9876", cfg.Net.P2PAddress) - assert.Equal(t, "localhost:7777", cfg.Net.RPCAddress) - assert.Equal(t, "90s", cfg.Net.RPCTimeout) - assert.Equal(t, false, cfg.Net.PubSubEnabled) - assert.Equal(t, false, cfg.Net.RelayEnabled) - assert.Equal(t, "error", cfg.Log.Level) - assert.Equal(t, true, cfg.Log.Stacktrace) - assert.Equal(t, "json", cfg.Log.Format) + assert.Equal(t, envVarsDifferent["DEFRA_API_ADDRESS"], cfg.API.Address) + assert.Equal(t, filepath.Join(cfg.Rootdir, envVarsDifferent["DEFRA_DATASTORE_BADGER_PATH"]), cfg.Datastore.Badger.Path) + assert.Equal(t, envVarsDifferent["DEFRA_DATASTORE_STORE"], cfg.Datastore.Store) + assert.Equal(t, stringtoBool(t, envVarsDifferent["DEFRA_NET_P2PDISABLED"]), cfg.Net.P2PDisabled) + assert.Equal(t, envVarsDifferent["DEFRA_NET_P2PADDRESS"], cfg.Net.P2PAddress) + assert.Equal(t, envVarsDifferent["DEFRA_NET_RPCADDRESS"], cfg.Net.RPCAddress) + assert.Equal(t, envVarsDifferent["DEFRA_NET_RPCTIMEOUT"], cfg.Net.RPCTimeout) + assert.Equal(t, stringtoBool(t, envVarsDifferent["DEFRA_NET_PUBSUB"]), cfg.Net.PubSubEnabled) + assert.Equal(t, stringtoBool(t, envVarsDifferent["DEFRA_NET_RELAY"]), cfg.Net.RelayEnabled) + assert.Equal(t, envVarsDifferent["DEFRA_LOG_LEVEL"], cfg.Log.Level) + assert.Equal(t, stringtoBool(t, envVarsDifferent["DEFRA_LOG_STACKTRACE"]), cfg.Log.Stacktrace) + assert.Equal(t, envVarsDifferent["DEFRA_LOG_FORMAT"], cfg.Log.Format) } -func TestGetRootDirExists(t *testing.T) { - dir, exists, err := GetRootDir("/tmp/defra_cli/") +// func TestGetRootDirExists(t *testing.T) { +// dir, exists, err := GetRootDir("/tmp/defra_cli/") - assert.NoError(t, err) - assert.Equal(t, "/tmp/defra_cli", dir) - assert.Equal(t, false, exists) -} +// assert.NoError(t, err) +// assert.Equal(t, "/tmp/defra_cli", dir) +// assert.Equal(t, false, exists) +// } -func TestGetRootDir(t *testing.T) { - os.Setenv("DEFRA_ROOT", "/tmp/defra_env/") - defer os.Unsetenv("DEFRA_ROOT") +// func TestGetRootDir(t *testing.T) { +// os.Setenv("DEFRA_ROOTDIR", "/tmp/defra_env/") +// defer os.Unsetenv("DEFRA_ROOTDIR") - dir, exists, err := GetRootDir("") +// dir, exists, err := GetRootDir("") - assert.NoError(t, err) - assert.Equal(t, "/tmp/defra_env", dir) - assert.Equal(t, false, exists) -} +// assert.NoError(t, err) +// assert.Equal(t, "/tmp/defra_env", dir) +// assert.Equal(t, false, exists) +// } func TestLoadNonExistingConfigFile(t *testing.T) { - cfg := DefaultConfig() - dir := t.TempDir() - - err := cfg.Load(dir) - - assert.Error(t, err) + cfg := NewWithDefaults() + cfg.Rootdir = t.TempDir() + assert.Error(t, cfg.Load(true)) } func TestLoadInvalidConfigFile(t *testing.T) { - cfg := DefaultConfig() - dir := t.TempDir() + cfg := NewWithDefaults() + tmpdir := t.TempDir() errWrite := os.WriteFile( - filepath.Join(dir, DefaultDefraDBConfigFileName), + filepath.Join(tmpdir, DefaultConfigFileName), []byte("{"), 0644, ) assert.NoError(t, errWrite) - errLoad := cfg.Load(dir) + cfg.Rootdir = tmpdir + errLoad := cfg.Load(true) assert.Error(t, errLoad) } func TestInvalidEnvVars(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() FixtureEnvVars(envVarsInvalid) defer FixtureEnvVarsUnset(envVarsInvalid) - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.Error(t, err) } func TestValidNetConfigPeers(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.Peers = "/ip4/127.0.0.1/udp/1234,/ip4/7.7.7.7/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N" - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.NoError(t, err) } func TestInvalidNetConfigPeers(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.Peers = "&(*^(*&^(*&^(*&^))), mmmmh,123123" - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.Error(t, err) } func TestInvalidRPCMaxConnectionIdle(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCMaxConnectionIdle = "123123" - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.Error(t, err) } func TestInvalidRPCTimeout(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCTimeout = "123123" - err := cfg.LoadWithoutRootDir() + err := cfg.Load(false) assert.Error(t, err) } func TestValidRPCTimeoutDuration(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCTimeout = "1s" - cfg.LoadWithoutRootDir() + assert.NoError(t, cfg.Load(false)) _, err := cfg.Net.RPCTimeoutDuration() - assert.NoError(t, err) } func TestInvalidRPCTimeoutDuration(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCTimeout = "123123" - cfg.LoadWithoutRootDir() - _, err := cfg.Net.RPCTimeoutDuration() + err := cfg.Load(false) + assert.Error(t, err) + _, err = cfg.Net.RPCTimeoutDuration() assert.Error(t, err) } func TestValidRPCMaxConnectionIdleDuration(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCMaxConnectionIdle = "1s" - cfg.LoadWithoutRootDir() + assert.NoError(t, cfg.Load(false)) _, err := cfg.Net.RPCMaxConnectionIdleDuration() assert.NoError(t, err) } func TestInvalidMaxConnectionIdleDuration(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.RPCMaxConnectionIdle = "*ˆ&%*&%" - - cfg.LoadWithoutRootDir() + assert.Error(t, cfg.Load(false)) _, err := cfg.Net.RPCMaxConnectionIdleDuration() - assert.Error(t, err) } func TestGetLoggingConfig(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Log.Level = "debug" cfg.Log.Format = "json" cfg.Log.Stacktrace = true @@ -294,18 +326,16 @@ func TestGetLoggingConfig(t *testing.T) { } func TestInvalidGetLoggingConfig(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Log.Level = "546578" cfg.Log.Format = "*&)*&" - - cfg.LoadWithoutRootDir() + assert.Error(t, cfg.Load(false)) _, err := cfg.GetLoggingConfig() - assert.Error(t, err) } func TestNodeConfig(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() cfg.Net.P2PAddress = "/ip4/0.0.0.0/tcp/9179" cfg.Net.TCPAddress = "/ip4/0.0.0.0/tcp/9169" cfg.Net.RPCTimeout = "100s" @@ -313,7 +343,7 @@ func TestNodeConfig(t *testing.T) { cfg.Net.RelayEnabled = true cfg.Net.PubSubEnabled = true cfg.Datastore.Badger.Path = "/tmp/defra_cli/badger" - cfg.LoadWithoutRootDir() + assert.NoError(t, cfg.Load(false)) nodeConfig := cfg.NodeConfig() options, errOptionsMerge := node.NewMergedOptions(nodeConfig) @@ -461,3 +491,44 @@ func TestByteSizeToString(t *testing.T) { mb := 10 * MiB assert.Equal(t, "10MiB", mb.String()) } + +func TestCreateRootDirWithDefaultConfig(t *testing.T) { + tmpdir := t.TempDir() + cfg := NewWithDefaults() + cfg.Rootdir = tmpdir + err := cfg.CreateRootDir() + + _, errStat := os.Stat(tmpdir) + notExists := os.IsNotExist(errStat) + assert.Equal(t, false, notExists) + assert.NoError(t, errStat) + assert.NoError(t, err) +} + +// func TestRootdirEnvVarIsDetected(t *testing.T) { +// tmpdir := t.TempDir() +// t.Cleanup(envSet(t, map[string]string{ +// "DEFRA_ROOTDIR": tmpdir, +// })) +// cfg := DefaultConfig() +// err := cfg.Load(false) +// assert.NoError(t, err) +// assert.Equal(t, tmpdir, cfg.Rootdir) +// } + +func TestLoadMultipleIsIdempotent(t *testing.T) { + cfg := NewWithDefaults() + err := cfg.Load(false) + assert.NoError(t, err) + + b, err := cfg.toBytes() + assert.NoError(t, err) + + err = cfg.Load(false) + assert.NoError(t, err) + + b2, err := cfg.toBytes() + assert.NoError(t, err) + + assert.Equal(t, b, b2) +} diff --git a/config/configfile.go b/config/configfile.go index bdd2dad5b9..cb308a1cde 100644 --- a/config/configfile.go +++ b/config/configfile.go @@ -11,20 +11,38 @@ package config import ( + "context" + _ "embed" "fmt" "os" + "path" "github.com/sourcenetwork/defradb/errors" ) const ( - DefaultDefraDBConfigFileName = "config.yaml" - configType = "yaml" - defaultDirPerm = 0o700 - defaultConfigFilePerm = 0o644 + DefaultConfigFileName = "config.yaml" + configType = "yaml" + defaultDirPerm = 0o700 + defaultConfigFilePerm = 0o644 ) -func (cfg *Config) writeConfigFile(path string) error { +// defaultConfigTemplate must reflect Config in content and configuration. +// All parameters must be represented here, to support Viper's automatic environment variable handling. + +//go:embed config_template_yaml.txt +var defaultConfigTemplate string + +func (cfg *Config) ConfigFileExists() bool { + return fileExists(cfg.ConfigFilePath()) +} + +func (cfg *Config) ConfigFilePath() string { + return path.Join(cfg.Rootdir, DefaultConfigFileName) +} + +func (cfg *Config) WriteConfigFile() error { + path := cfg.ConfigFilePath() buffer, err := cfg.toBytes() if err != nil { return err @@ -35,76 +53,17 @@ func (cfg *Config) writeConfigFile(path string) error { return nil } -// WriteConfigFile writes a config file in a given root directory. -func (cfg *Config) WriteConfigFileToRootDir(rootDir string) error { - path := fmt.Sprintf("%v/%v", rootDir, DefaultDefraDBConfigFileName) - return cfg.writeConfigFile(path) +func (cfg *Config) CreateRootDir() error { + err := os.MkdirAll(cfg.Rootdir, defaultDirPerm) + if err != nil { + return err + } + log.FeedbackInfo(context.Background(), fmt.Sprintf("Created DefraDB root directory at %v", cfg.Rootdir)) + return nil } -// defaultConfigTemplate must reflect Config in content and configuration. -// All parameters must be represented here, to support Viper's automatic environment variable handling. -const defaultConfigTemplate = `# DefraDB configuration (YAML) - -# NOTE: Paths below are relative to the DefraDB directory. -# By default, the DefraDB directory is "$HOME/.defradb", but -# can be changed via the $DEFRA_ROOT env variable or --rootdir CLI flag. - -datastore: - # Store can be badger | memory - # badger: fast pure Go key-value store optimized for SSDs (https://github.com/dgraph-io/badger) - # memory: in-memory version of badger - store: {{ .Datastore.Store }} - badger: - path: {{ .Datastore.Badger.Path }} - # Maximum file size of the value log files. The in-memory file size will be 2*valuelogfilesize. - # Human friendly units can be used (ex: 500MB). - valuelogfilesize: {{ .Datastore.Badger.ValueLogFileSize }} - # memory: - # size: {{ .Datastore.Memory.Size }} - -api: - # Address of the HTTP API to listen on or connect to - address: {{ .API.Address }} - # Whether the API server should listen over HTTPS - tls: {{ .API.TLS }} - # The path to the public key file. Ignored if domains is set. - pubkeypath: {{ .API.PubKeyPath }} - # The path to the private key file. Ignored if domains is set. - privkeypath: {{ .API.PrivKeyPath }} - # Email address to let the CA (Let's Encrypt) send notifications via email when there are issues (optional). - # email: {{ .API.Email }} - -net: - # Whether the P2P is disabled - p2pdisabled: {{ .Net.P2PDisabled }} - # Listening address of the P2P network - p2paddress: {{ .Net.P2PAddress }} - # Listening address of the RPC endpoint - rpcaddress: {{ .Net.RPCAddress }} - # gRPC server address - tcpaddress: {{ .Net.TCPAddress }} - # Time duration after which a RPC connection to a peer times out - rpctimeout: {{ .Net.RPCTimeout }} - # Whether the node has pubsub enabled or not - pubsub: {{ .Net.PubSubEnabled }} - # Enable libp2p's Circuit relay transport protocol https://docs.libp2p.io/concepts/circuit-relay/ - relay: {{ .Net.RelayEnabled }} - # List of peers to boostrap with, specified as multiaddresses (https://docs.libp2p.io/concepts/addressing/) - peers: {{ .Net.Peers }} - # Amount of time after which an idle RPC connection would be closed - RPCMaxConnectionIdle: {{ .Net.RPCMaxConnectionIdle }} - -log: - # Log level. Options are debug, info, error, fatal - level: {{ .Log.Level }} - # Include stacktrace in error and fatal logs - stacktrace: {{ .Log.Stacktrace }} - # Supported log formats are json, csv - format: {{ .Log.Format }} - # Where the log output is written to - output: {{ .Log.Output }} - # Disable colored log output - nocolor: {{ .Log.NoColor }} - # Caller location in log output - caller: {{ .Log.Caller }} -` +func fileExists(fpath string) bool { + statInfo, err := os.Stat(fpath) + existsAsFile := (err == nil && !statInfo.IsDir()) + return existsAsFile +} diff --git a/config/configfile_test.go b/config/configfile_test.go index 5080964966..ab2b22ec42 100644 --- a/config/configfile_test.go +++ b/config/configfile_test.go @@ -13,6 +13,7 @@ package config import ( "bytes" "os" + "path" "testing" "text/template" @@ -21,7 +22,7 @@ import ( func TestConfigTemplateSerialize(t *testing.T) { var buffer bytes.Buffer - cfg := DefaultConfig() + cfg := NewWithDefaults() tmpl := template.New("configTemplate") configTemplate, err := tmpl.Parse(defaultConfigTemplate) if err != nil { @@ -36,7 +37,7 @@ func TestConfigTemplateSerialize(t *testing.T) { } func TestConfigTemplateExecutes(t *testing.T) { - cfg := DefaultConfig() + cfg := NewWithDefaults() var buffer bytes.Buffer tmpl := template.New("configTemplate") configTemplate, err := tmpl.Parse(defaultConfigTemplate) @@ -49,48 +50,45 @@ func TestConfigTemplateExecutes(t *testing.T) { } func TestWritesConfigFile(t *testing.T) { - cfg := DefaultConfig() - dir := t.TempDir() - err := cfg.WriteConfigFileToRootDir(dir) + cfg := NewWithDefaults() + tmpdir := t.TempDir() + cfg.Rootdir = tmpdir + err := cfg.WriteConfigFile() assert.NoError(t, err) - path := dir + "/" + DefaultDefraDBConfigFileName + path := tmpdir + "/" + DefaultConfigFileName _, err = os.Stat(path) assert.Nil(t, err) } func TestWritesConfigFileErroneousPath(t *testing.T) { - cfg := DefaultConfig() - dir := t.TempDir() - err := cfg.WriteConfigFileToRootDir(dir + "////*&^^(*8769876////bar") + cfg := NewWithDefaults() + cfg.Rootdir = t.TempDir() + "////*&^^(*8769876////bar" + err := cfg.WriteConfigFile() assert.Error(t, err) } +// func TestReadConfigFileForLogger(t *testing.T) { - dir := t.TempDir() - - cfg := DefaultConfig() + cfg := NewWithDefaults() + tmpdir := t.TempDir() + cfg.Rootdir = tmpdir cfg.Log.Caller = true cfg.Log.Format = "json" cfg.Log.Level = logLevelDebug cfg.Log.NoColor = true - cfg.Log.Output = dir + "/log.txt" + cfg.Log.Output = tmpdir + "/log.txt" cfg.Log.Stacktrace = true - err := cfg.WriteConfigFileToRootDir(dir) - if err != nil { - t.Fatal(err) - } - - path := dir + "/" + DefaultDefraDBConfigFileName - - _, err = os.Stat(path) + err := cfg.WriteConfigFile() if err != nil { t.Fatal(err) } - cfgFromFile := DefaultConfig() + assert.True(t, cfg.ConfigFileExists()) - err = cfgFromFile.Load(dir) + cfgFromFile := NewWithDefaults() + cfgFromFile.Rootdir = tmpdir + err = cfgFromFile.Load(true) if err != nil { t.Fatal(err) } @@ -104,32 +102,54 @@ func TestReadConfigFileForLogger(t *testing.T) { } func TestReadConfigFileForDatastore(t *testing.T) { - dir := t.TempDir() + // WIP we have a conflict + // we a value from TestRootdirEnvVarIsDetect?? + tmpdir := t.TempDir() - cfg := DefaultConfig() + cfg := NewWithDefaults() + cfg.Rootdir = tmpdir cfg.Datastore.Store = "badger" cfg.Datastore.Badger.Path = "dataPath" cfg.Datastore.Badger.ValueLogFileSize = 512 * MiB - err := cfg.WriteConfigFileToRootDir(dir) + err := cfg.WriteConfigFile() if err != nil { t.Fatal(err) } - path := dir + "/" + DefaultDefraDBConfigFileName + path := tmpdir + "/" + DefaultConfigFileName _, err = os.Stat(path) if err != nil { t.Fatal(err) } - cfgFromFile := DefaultConfig() - - err = cfgFromFile.Load(dir) + cfgFromFile := NewWithDefaults() + cfgFromFile.Rootdir = tmpdir + err = cfgFromFile.Load(true) if err != nil { t.Fatal(err) } assert.Equal(t, cfg.Datastore.Store, cfgFromFile.Datastore.Store) - assert.Equal(t, dir+"/"+cfg.Datastore.Badger.Path, cfgFromFile.Datastore.Badger.Path) + assert.Equal(t, tmpdir+"/"+cfg.Datastore.Badger.Path, cfgFromFile.Datastore.Badger.Path) assert.Equal(t, cfg.Datastore.Badger.ValueLogFileSize, cfgFromFile.Datastore.Badger.ValueLogFileSize) } + +func TestFileExists(t *testing.T) { + tmpdir := t.TempDir() + // Verify that a file that doesn't exist returns false. + assert.False(t, fileExists(tmpdir+"/nonexistentfile")) + + // Verify that a file that does exist returns true. + fpath := path.Join(tmpdir, "file") + f, err := os.Create(fpath) + f.Close() + assert.NoError(t, err) + assert.True(t, fileExists(fpath)) + + // Test that a directory is not considered a file. + dpath := path.Join(tmpdir, "dir") + err = os.Mkdir(dpath, 0755) + assert.NoError(t, err) + assert.False(t, fileExists(dpath)) +} diff --git a/config/rootdir.go b/config/rootdir.go deleted file mode 100644 index 5394b7c4ef..0000000000 --- a/config/rootdir.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 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 config - -import ( - "os" - "path/filepath" -) - -// DefaultRootDir returns the default rootdir path, which is at the user's home directory. -func DefaultRootDir() (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(home, defaultDefraDBRootDir), nil -} - -// GetRootDir returns rootdir path and whether it exists as directory, considering the env. variable and CLI flag. -func GetRootDir(rootDir string) (string, bool, error) { - var err error - var path string - rootDirEnv := os.Getenv(DefraEnvPrefix + "_ROOT") - if rootDirEnv == "" && rootDir == "" { - path, err = DefaultRootDir() - if err != nil { - return "", false, err - } - } else if rootDirEnv != "" && rootDir == "" { - path = rootDirEnv - } else { - path = rootDir - } - path, err = filepath.Abs(path) - if err != nil { - return "", false, err - } - info, err := os.Stat(path) - exists := (err == nil && info.IsDir()) - return path, exists, nil -} - -// CreateRootDirWithDefaultConfig creates a rootdir with default configuration. -func CreateRootDirWithDefaultConfig(rootDir string) error { - err := os.MkdirAll(rootDir, defaultDirPerm) - if err != nil { - return err - } - cfg := DefaultConfig() - err = cfg.WriteConfigFileToRootDir(rootDir) - if err != nil { - return err - } - return nil -} diff --git a/config/rootdir_test.go b/config/rootdir_test.go deleted file mode 100644 index c8f65973e5..0000000000 --- a/config/rootdir_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 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 config - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateRootDirWithDefaultConfig(t *testing.T) { - tempdir := t.TempDir() - rootdir := filepath.Join(tempdir, "defra_rootdir") - - err := CreateRootDirWithDefaultConfig(rootdir) - - _, errStat := os.Stat(rootdir) - notExists := os.IsNotExist(errStat) - assert.Equal(t, false, notExists) - assert.NoError(t, errStat) - assert.NoError(t, err) -} - -func TestGetRootDirDefault(t *testing.T) { - rootdir := "" - obtainedRootDir, _, err := GetRootDir(rootdir) - defaultDir, errDefaultRootDir := DefaultRootDir() - - assert.NoError(t, err) - assert.Equal(t, defaultDir, obtainedRootDir) - assert.NoError(t, errDefaultRootDir) -} diff --git a/go.mod b/go.mod index cf12aae3a9..05a6ff646f 100644 --- a/go.mod +++ b/go.mod @@ -39,13 +39,14 @@ require ( github.com/pkg/errors v0.9.1 github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.4.0 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.11.0 github.com/stretchr/testify v1.8.0 github.com/textileio/go-libp2p-pubsub-rpc v0.0.9 github.com/ugorji/go/codec v1.1.7 github.com/valyala/fastjson v1.6.3 go.uber.org/zap v1.23.0 - golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b + golang.org/x/crypto v0.1.0 google.golang.org/grpc v1.46.2 ) @@ -161,7 +162,6 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/textileio/go-log/v2 v2.1.3-gke-2 // indirect @@ -175,12 +175,12 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/net v0.1.0 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect + golang.org/x/tools v0.2.0 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index bca9742317..46a0d4747c 100644 --- a/go.sum +++ b/go.sum @@ -1398,8 +1398,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1437,8 +1437,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1497,8 +1497,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 h1:KafLifaRFIuSJ5C+7CyFJOF9haxKNC1CEIDk8GX6X0k= -golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1612,8 +1612,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1625,8 +1626,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1695,8 +1697,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tests/integration/net/utils.go b/tests/integration/net/utils.go index 0e68580957..86ae830ac2 100644 --- a/tests/integration/net/utils.go +++ b/tests/integration/net/utils.go @@ -295,7 +295,7 @@ func executeTestCase(t *testing.T, test P2PTestCase) { const randomMultiaddr = "/ip4/0.0.0.0/tcp/0" func randomNetworkingConfig() *config.Config { - cfg := config.DefaultConfig() + cfg := config.NewWithDefaults() cfg.Net.P2PAddress = randomMultiaddr cfg.Net.RPCAddress = "0.0.0.0:0" cfg.Net.TCPAddress = randomMultiaddr