diff --git a/README.rst b/README.rst index 3fe7064f80..715d56cbad 100644 --- a/README.rst +++ b/README.rst @@ -133,25 +133,23 @@ Key Default Description ============================ ============== ================================================================================================================================================================================= **** address localhost:1323 Address and port the server will be listening to -configfile Nuts config file +configfile nuts.yaml Nuts config file +datadir ./data Directory where the node stores its files. strictmode false When set, insecure settings are forbidden. verbosity info Log level (trace, debug, info, warn, error) **Crypto** -crypto.fspath ./ When file system is used as storage, this configures the path where key material and the truststore are persisted, default: ./ crypto.storage fs Storage to use, 'fs' for file system, default: fs **Network** network.advertHashesInterval 2000 Interval (in milliseconds) that specifies how often the node should broadcast its last hashes to other nodes. network.bootstrapNodes Space-separated list of bootstrap nodes (`:`) which the node initially connect to. network.certFile PEM file containing the server certificate for the gRPC server. Required when `enableTLS` is `true`. network.certKeyFile PEM file containing the private key of the server certificate. Required when `network.enableTLS` is `true`. -network.databaseFile network.db File path to the network database. network.enableTLS true Whether to enable TLS for inbound gRPC connections. If set to `true` (which is default) `certFile` and `certKeyFile` MUST be configured. network.grpcAddr \:5555 Local address for gRPC to listen on. If empty the gRPC server won't be started and other nodes will not be able to connect to this node (outbound connections can still be made). network.publicAddr Public address (of this node) other nodes can use to connect to it. If set, it is registered on the nodelist. network.trustStoreFile PEM file containing the trusted CA certificates for authenticating remote gRPC servers. **Verifiable Data Registry** vdr.clientTimeout 10 Time-out for the client in seconds (e.g. when using the CLI), default: 10 -vdr.datadir ./data Location of data files, default: ./data ============================ ============== ================================================================================================================================================================================= This table is automatically generated using the configuration flags in the core and engines. When they're changed diff --git a/cmd/network.db b/cmd/network.db deleted file mode 100644 index e449c28987..0000000000 Binary files a/cmd/network.db and /dev/null differ diff --git a/cmd/root_test.go b/cmd/root_test.go index f4eeb8d4bf..28ad59aeb3 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -7,7 +7,6 @@ import ( "github.com/nuts-foundation/nuts-node/test/io" "github.com/stretchr/testify/assert" "os" - "path" "testing" ) @@ -50,22 +49,13 @@ func Test_rootCmd(t *testing.T) { return echoServer } - expectedAddress := "some-other-address:1323" - testDirectory := io.TestDirectory(t) - os.Setenv("NUTS_ADDRESS", expectedAddress) - defer os.Unsetenv("NUTS_ADDRESS") - os.Setenv("NUTS_NETWORK_DATABASEFILE", path.Join(testDirectory, "network.db")) - defer os.Unsetenv("NUTS_NETWORK_DATABASEFILE") - os.Setenv("NUTS_VDR_DATADIR", path.Join(testDirectory, "vdr")) - defer os.Unsetenv("NUTS_VDR_DATADIR") - os.Setenv("NUTS_CRYPTO_FSPATH", path.Join(testDirectory, "crypto")) - defer os.Unsetenv("NUTS_CRYPTO_FSPATH") - + os.Setenv("NUTS_DATADIR", testDirectory) + defer os.Unsetenv("NUTS_DATADIR") os.Args = []string{"nuts", "server"} type Cfg struct { - Address string + Datadir string } var engineCfg = &Cfg{} engine := &core.Engine{ @@ -78,9 +68,9 @@ func Test_rootCmd(t *testing.T) { Execute(system) // Assert global config contains overridden property - assert.Equal(t, expectedAddress, system.Config.Address) + assert.Equal(t, testDirectory, system.Config.Datadir) // Assert engine config is injected - assert.Equal(t, expectedAddress, engineCfg.Address) + assert.Equal(t, testDirectory, engineCfg.Datadir) }) } diff --git a/core/config.go b/core/config.go index f6c408ee09..9916727829 100644 --- a/core/config.go +++ b/core/config.go @@ -40,31 +40,35 @@ const defaultConfigFile = "nuts.yaml" const configFileFlag = "configfile" const loggerLevelFlag = "verbosity" const addressFlag = "address" +const datadirFlag = "datadir" const defaultLogLevel = "info" const defaultAddress = "localhost:1323" const strictModeFlag = "strictmode" const defaultStrictMode = false +const defaultDatadir = "./data" -// NutsGlobalConfig has global settings. -type NutsGlobalConfig struct { +// NutsConfig has global settings. +type NutsConfig struct { Address string `koanf:"address"` Verbosity string `koanf:"verbosity"` Strictmode bool `koanf:"strictmode"` + Datadir string `koanf:"datadir"` configMap *koanf.Koanf } // NewNutsConfig creates a new config with some defaults -func NewNutsConfig() *NutsGlobalConfig { - return &NutsGlobalConfig{ +func NewNutsConfig() *NutsConfig { + return &NutsConfig{ configMap: koanf.New(defaultDelimiter), Address: defaultAddress, Verbosity: defaultLogLevel, Strictmode: defaultStrictMode, + Datadir: defaultDatadir, } } // Load follows the load order of configfile, env vars and then commandline param -func (ngc *NutsGlobalConfig) Load(cmd *cobra.Command) (err error) { +func (ngc *NutsConfig) Load(cmd *cobra.Command) (err error) { cmd.PersistentFlags().AddFlagSet(ngc.flagSet()) ngc.configMap = koanf.New(defaultDelimiter) @@ -111,7 +115,7 @@ func (ngc *NutsGlobalConfig) Load(cmd *cobra.Command) (err error) { } // getConfigfile returns the configfile path in the following order: commandline param, env variable, default path -func (ngc *NutsGlobalConfig) getConfigfile(cmd *cobra.Command) string { +func (ngc *NutsConfig) getConfigfile(cmd *cobra.Command) string { k := koanf.New(defaultDelimiter) // load env flags @@ -133,22 +137,23 @@ func (ngc *NutsGlobalConfig) getConfigfile(cmd *cobra.Command) string { return k.String(configFileFlag) } -func (ngc *NutsGlobalConfig) flagSet() *pflag.FlagSet { +func (ngc *NutsConfig) flagSet() *pflag.FlagSet { flagSet := pflag.NewFlagSet("config", pflag.ContinueOnError) flagSet.String(configFileFlag, defaultConfigFile, "Nuts config file") flagSet.String(loggerLevelFlag, defaultLogLevel, "Log level (trace, debug, info, warn, error)") flagSet.String(addressFlag, defaultAddress, "Address and port the server will be listening to") flagSet.Bool(strictModeFlag, defaultStrictMode, "When set, insecure settings are forbidden.") + flagSet.String(datadirFlag, defaultDatadir, "Directory where the node stores its files.") return flagSet } // PrintConfig return the current config in string form -func (ngc *NutsGlobalConfig) PrintConfig() string { +func (ngc *NutsConfig) PrintConfig() string { return ngc.configMap.Sprint() } // InjectIntoEngine takes the loaded config and sets the engine's config struct -func (ngc *NutsGlobalConfig) InjectIntoEngine(e *Engine) error { +func (ngc *NutsConfig) InjectIntoEngine(e *Engine) error { // ignore if no target for injection if e.Config != nil { return ngc.configMap.Unmarshal(e.ConfigKey, e.Config) @@ -159,7 +164,7 @@ func (ngc *NutsGlobalConfig) InjectIntoEngine(e *Engine) error { // RegisterFlags adds the flagSet of an engine to the commandline, flag names are prefixed if needed // The passed command must be the root command not the engine.Cmd (unless they are the same) -func (ngc *NutsGlobalConfig) RegisterFlags(cmd *cobra.Command, e *Engine) { +func (ngc *NutsConfig) RegisterFlags(cmd *cobra.Command, e *Engine) { if e.FlagSet != nil { cmd.PersistentFlags().AddFlagSet(e.FlagSet) } diff --git a/core/engine.go b/core/engine.go index 3b1b2468de..2e27ba2e07 100644 --- a/core/engine.go +++ b/core/engine.go @@ -20,7 +20,9 @@ package core import ( + "fmt" "net/url" + "os" "github.com/labstack/echo/v4" "github.com/spf13/cobra" @@ -40,7 +42,7 @@ type System struct { // engines is the slice of all registered engines engines []*Engine // Config holds the global and raw config - Config *NutsGlobalConfig + Config *NutsConfig } // Load loads the config and injects config values into engines @@ -94,10 +96,13 @@ func (system *System) Shutdown() error { // Configure configures all engines in the system. func (system *System) Configure() error { var err error + if err = os.MkdirAll(system.Config.Datadir, os.ModePerm); err != nil { + return fmt.Errorf("unable to create datadir (dir=%s): %w", system.Config.Datadir, err) + } return system.VisitEnginesE(func(engine *Engine) error { // only if Engine is dynamically configurable if engine.Configurable != nil { - err = engine.Configure() + err = engine.Configure(*system.Config) } return err }) @@ -160,7 +165,7 @@ type Runnable interface { // When an engine implements the Configurable interface, it will be called before startup. // Configure should only be called once per engine instance type Configurable interface { - Configure() error + Configure(config NutsConfig) error } // Diagnosable allows the implementer, mostly engines, to return diagnostics. diff --git a/core/engine_test.go b/core/engine_test.go index 98eafd4623..8df5556f20 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -66,16 +66,23 @@ func TestSystem_Shutdown(t *testing.T) { } func TestSystem_Configure(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() + t.Run("ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() - r := NewMockConfigurable(ctrl) - r.EXPECT().Configure() + r := NewMockConfigurable(ctrl) + r.EXPECT().Configure(gomock.Any()) - system := NewSystem() - system.RegisterEngine(&Engine{}) - system.RegisterEngine(&Engine{Configurable: r}) - assert.Nil(t, system.Configure()) + system := NewSystem() + system.RegisterEngine(&Engine{}) + system.RegisterEngine(&Engine{Configurable: r}) + assert.Nil(t, system.Configure()) + }) + t.Run("unable to create datadir", func(t *testing.T) { + system := NewSystem() + system.Config = &NutsConfig{Datadir: "engine_test.go"} + assert.Error(t, system.Configure()) + }) } func TestSystem_Diagnostics(t *testing.T) { diff --git a/core/metrics.go b/core/metrics.go index 0ad8c592d3..476019a740 100644 --- a/core/metrics.go +++ b/core/metrics.go @@ -41,7 +41,7 @@ type metricsEngine struct{} // Configure configures the MetricsEngine. // It configures and registers the prometheus collector -func (metricsEngine) Configure() error { +func (metricsEngine) Configure(_ NutsConfig) error { collectors := []prometheus.Collector{ prometheus.NewGoCollector(), prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}), diff --git a/core/metrics_test.go b/core/metrics_test.go index 92e273b9b3..4256b81b1a 100644 --- a/core/metrics_test.go +++ b/core/metrics_test.go @@ -33,7 +33,7 @@ import ( func TestNewMetricsEngine(t *testing.T) { mEngine := NewMetricsEngine() - _ = mEngine.Configure() + _ = mEngine.Configure(NutsConfig{}) e := echo.New() mEngine.Routes(e) @@ -56,7 +56,7 @@ func TestNewMetricsEngine(t *testing.T) { }) t.Run("calling configure twice is ok", func(t *testing.T) { - err := mEngine.Configure() + err := mEngine.Configure(NutsConfig{}) assert.NoError(t, err) }) diff --git a/core/mock.go b/core/mock.go index a162f4c14e..f73efa3197 100644 --- a/core/mock.go +++ b/core/mock.go @@ -487,17 +487,17 @@ func (m *MockConfigurable) EXPECT() *MockConfigurableMockRecorder { } // Configure mocks base method -func (m *MockConfigurable) Configure() error { +func (m *MockConfigurable) Configure(config NutsConfig) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Configure") + ret := m.ctrl.Call(m, "Configure", config) ret0, _ := ret[0].(error) return ret0 } // Configure indicates an expected call of Configure -func (mr *MockConfigurableMockRecorder) Configure() *gomock.Call { +func (mr *MockConfigurableMockRecorder) Configure(config interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configure", reflect.TypeOf((*MockConfigurable)(nil).Configure)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configure", reflect.TypeOf((*MockConfigurable)(nil).Configure), config) } // MockDiagnosable is a mock of Diagnosable interface diff --git a/crypto/crypto.go b/crypto/crypto.go index 31ca03eff2..5c0950cd98 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -28,6 +28,7 @@ import ( "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/crypto/storage" "io" + "path" "sync" "time" ) @@ -35,22 +36,12 @@ import ( // Config holds the values for the crypto engine type Config struct { Storage string - Fspath string -} - -func (cc Config) getFSPath() string { - if cc.Fspath == "" { - return DefaultCryptoConfig().Fspath - } - - return cc.Fspath } // DefaultCryptoConfig returns a Config with sane defaults func DefaultCryptoConfig() Config { return Config{ Storage: "fs", - Fspath: "./", } } @@ -106,22 +97,23 @@ func Instance() *Crypto { } // Configure loads the given configurations in the engine. Any wrong combination will return an error -func (client *Crypto) Configure() error { +func (client *Crypto) Configure(config core.NutsConfig) error { var err error client.configOnce.Do(func() { - if err = client.doConfigure(); err == nil { + if err = client.doConfigure(config); err == nil { client.configDone = true } }) return err } -func (client *Crypto) doConfigure() error { +func (client *Crypto) doConfigure(config core.NutsConfig) error { if client.Config.Storage != "fs" && client.Config.Storage != "" { return errors.New("only fs backend available for now") } var err error - if client.Storage, err = storage.NewFileSystemBackend(client.Config.getFSPath()); err != nil { + fsPath := path.Join(config.Datadir, "crypto") + if client.Storage, err = storage.NewFileSystemBackend(fsPath); err != nil { return err } return nil diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index b7a6945657..8494eb6d3f 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -119,14 +119,16 @@ func TestCrypto_New(t *testing.T) { } func TestCrypto_doConfigure(t *testing.T) { + directory := io.TestDirectory(t) + cfg := core.NutsConfig{Datadir: directory} t.Run("ok", func(t *testing.T) { e := createCrypto(t) - err := e.doConfigure() + err := e.doConfigure(cfg) assert.NoError(t, err) }) t.Run("ok - default = fs backend", func(t *testing.T) { client := createCrypto(t) - err := client.doConfigure() + err := client.doConfigure(cfg) if !assert.NoError(t, err) { return } @@ -136,7 +138,7 @@ func TestCrypto_doConfigure(t *testing.T) { t.Run("error - unknown backend", func(t *testing.T) { client := createCrypto(t) client.Config.Storage = "unknown" - err := client.doConfigure() + err := client.doConfigure(cfg) assert.EqualErrorf(t, err, "only fs backend available for now", "expected error") }) } @@ -145,14 +147,15 @@ func TestCrypto_Configure(t *testing.T) { createCrypto(t) t.Run("ok - configOnce", func(t *testing.T) { + directory := io.TestDirectory(t) e := createCrypto(t) assert.False(t, e.configDone) - err := e.Configure() + err := e.Configure(core.NutsConfig{Datadir: directory}) if !assert.NoError(t, err) { return } assert.True(t, e.configDone) - err = e.Configure() + err = e.Configure(core.NutsConfig{Datadir: directory}) if !assert.NoError(t, err) { return } @@ -160,21 +163,11 @@ func TestCrypto_Configure(t *testing.T) { }) } -func TestCryptoConfig_getFsPath(t *testing.T) { - t.Run("no path configured returns defaultPath", func(t *testing.T) { - c := Config{ - Fspath: "", - } - assert.Equal(t, "./", c.getFSPath()) - }) -} - func createCrypto(t *testing.T) *Crypto { dir := io.TestDirectory(t) backend, _ := storage.NewFileSystemBackend(dir) crypto := Crypto{ Storage: backend, - Config: TestCryptoConfig(dir), } return &crypto diff --git a/crypto/engine/engine.go b/crypto/engine/engine.go index c2ded6371d..9b024766ce 100644 --- a/crypto/engine/engine.go +++ b/crypto/engine/engine.go @@ -35,9 +35,6 @@ import ( // ConfigStorage is used as --crypto.storage config flag const ConfigStorage string = "crypto.storage" -// ConfigFSPath is used as --crypto.fspath config flagclient.getStoragePath() -const ConfigFSPath string = "crypto.fspath" - // NewCryptoEngine the engine configuration for nuts-go. func NewCryptoEngine() (*core.Engine, crypto2.KeyStore) { cb := crypto2.Instance() @@ -62,7 +59,6 @@ func flagSet() *pflag.FlagSet { defs := crypto2.DefaultCryptoConfig() flags.String(ConfigStorage, defs.Storage, fmt.Sprintf("Storage to use, 'fs' for file system, default: %s", defs.Storage)) - flags.String(ConfigFSPath, defs.Fspath, fmt.Sprintf("When file system is used as storage, this configures the path where key material and the truststore are persisted, default: %v", defs.Fspath)) return flags } diff --git a/crypto/engine/engine_test.go b/crypto/engine/engine_test.go index 55634ae52e..826cbcf044 100644 --- a/crypto/engine/engine_test.go +++ b/crypto/engine/engine_test.go @@ -171,10 +171,6 @@ func TestNewCryptoEngine_FlagSet(t *testing.T) { t.Errorf("Expected --storage to be command line flag") } - if !strings.Contains(result, "--crypto.fspath") { - t.Errorf("Expected --fspath to be command line flag") - } - }) } diff --git a/crypto/test.go b/crypto/test.go index 8ee6156186..049a7ec110 100644 --- a/crypto/test.go +++ b/crypto/test.go @@ -5,7 +5,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "path" + "github.com/nuts-foundation/nuts-node/core" "time" "github.com/sirupsen/logrus" @@ -14,24 +14,16 @@ import ( // NewTestCryptoInstance returns a new Crypto instance to be used for integration tests. Any data is stored in the // specified test directory. func NewTestCryptoInstance(testDirectory string) *Crypto { - config := TestCryptoConfig(testDirectory) newInstance := &Crypto{ - Config: config, + Config: DefaultCryptoConfig(), } - if err := newInstance.Configure(); err != nil { + if err := newInstance.Configure(core.NutsConfig{Datadir: testDirectory}); err != nil { logrus.Fatal(err) } instance = newInstance return newInstance } -// TestCryptoConfig returns Config to be used in integration/unit tests. -func TestCryptoConfig(testDirectory string) Config { - config := DefaultCryptoConfig() - config.Fspath = path.Join(testDirectory, "crypto") - return config -} - // StringNamingFunc can be used to give a key a simple string name func StringNamingFunc(name string) KIDNamingFunc { return func(key crypto.PublicKey) (string, error) { diff --git a/docs/index.rst b/docs/index.rst index 2ce2f5fba6..928932a9dd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,6 +30,7 @@ Nuts documentation pages/monitoring.rst pages/running-a-node.rst pages/administering-your-node.rst + pages/production-configuration.rst .. toctree:: :maxdepth: 1 diff --git a/docs/pages/configuration/configuration.rst b/docs/pages/configuration/configuration.rst index c95381e286..d78e5b9a50 100644 --- a/docs/pages/configuration/configuration.rst +++ b/docs/pages/configuration/configuration.rst @@ -5,7 +5,7 @@ Nuts node config .. marker-for-readme -The Nuts library contains some configuration logic which allows for usage of configFiles, Environment variables and commandLine params transparently. +The Nuts node can be configured using a YAML configuration file, environment variables and commandline params. The parameters follow the following convention: ``$ nuts --parameter X`` is equal to ``$ NUTS_PARAMETER=X nuts`` is equal to ``parameter: X`` in a yaml file. diff --git a/docs/pages/configuration/options.rst b/docs/pages/configuration/options.rst index 681f712f76..4494673395 100755 --- a/docs/pages/configuration/options.rst +++ b/docs/pages/configuration/options.rst @@ -3,23 +3,21 @@ Key Default Description ============================ ============== ================================================================================================================================================================================= **** address localhost:1323 Address and port the server will be listening to -configfile Nuts config file +configfile nuts.yaml Nuts config file +datadir ./data Directory where the node stores its files. strictmode false When set, insecure settings are forbidden. verbosity info Log level (trace, debug, info, warn, error) **Crypto** -crypto.fspath ./ When file system is used as storage, this configures the path where key material and the truststore are persisted, default: ./ crypto.storage fs Storage to use, 'fs' for file system, default: fs **Network** network.advertHashesInterval 2000 Interval (in milliseconds) that specifies how often the node should broadcast its last hashes to other nodes. network.bootstrapNodes Space-separated list of bootstrap nodes (`:`) which the node initially connect to. network.certFile PEM file containing the server certificate for the gRPC server. Required when `enableTLS` is `true`. network.certKeyFile PEM file containing the private key of the server certificate. Required when `network.enableTLS` is `true`. -network.databaseFile network.db File path to the network database. network.enableTLS true Whether to enable TLS for inbound gRPC connections. If set to `true` (which is default) `certFile` and `certKeyFile` MUST be configured. network.grpcAddr \:5555 Local address for gRPC to listen on. If empty the gRPC server won't be started and other nodes will not be able to connect to this node (outbound connections can still be made). network.publicAddr Public address (of this node) other nodes can use to connect to it. If set, it is registered on the nodelist. network.trustStoreFile PEM file containing the trusted CA certificates for authenticating remote gRPC servers. **Verifiable Data Registry** vdr.clientTimeout 10 Time-out for the client in seconds (e.g. when using the CLI), default: 10 -vdr.datadir ./data Location of data files, default: ./data ============================ ============== ================================================================================================================================================================================= diff --git a/docs/pages/production-configuration.rst b/docs/pages/production-configuration.rst new file mode 100644 index 0000000000..0b19da8ffe --- /dev/null +++ b/docs/pages/production-configuration.rst @@ -0,0 +1,23 @@ +.. _prodruction-configuration: + +Configuring for Production +########################## + +Running a Nuts node in a production environment has additional requirements regarding security and data integrity +compared to development or test environments. This page instructs how to :ref:`configure ` +your node for running in a production environment and what to consider. + +Persistence +*********** + +All data the node produces is stored on disk in the configured data directory (`datadir`). It is recommended to backup +everything in that directory. However, there are certain directories that absolutely should be part of the backup: + +* `crypto`, because it contains your node's private keys + +Strict mode +*********** + +By default the node runs in a mode which allows the operator run configure the node in such a way that it is less secure. +For production it is recommended to enable `strictmode` which blocks some of the unsafe configuration options +(e.g. using the IRMA demo scheme). \ No newline at end of file diff --git a/network/config.go b/network/config.go index 5c30b51783..c1f90489f8 100644 --- a/network/config.go +++ b/network/config.go @@ -10,8 +10,7 @@ import ( // Config holds the config for Network type Config struct { // Socket address for gRPC to listen on - GrpcAddr string `koanf:"grpcAddr"` - DatabaseFile string `koanf:"databaseFile"` + GrpcAddr string `koanf:"grpcAddr"` // EnableTLS specifies whether to enable TLS for incoming connections. EnableTLS bool `koanf:"enableTLS"` // Public address of this nodes other nodes can use to connect to this node. @@ -30,7 +29,6 @@ type Config struct { func DefaultConfig() Config { return Config{ GrpcAddr: ":5555", - DatabaseFile: "network.db", EnableTLS: true, AdvertHashesInterval: 2000, } diff --git a/network/config_test.go b/network/config_test.go index 73ee588ddb..e0ad4c9048 100644 --- a/network/config_test.go +++ b/network/config_test.go @@ -8,7 +8,6 @@ import ( func TestDefaultConfig(t *testing.T) { defs := DefaultConfig() assert.True(t, defs.EnableTLS) - assert.Equal(t, "network.db", defs.DatabaseFile) assert.Equal(t, 2000, defs.AdvertHashesInterval) assert.Equal(t, ":5555", defs.GrpcAddr) } diff --git a/network/dag/mock.go b/network/dag/mock.go index b34e4e888d..8913678f60 100644 --- a/network/dag/mock.go +++ b/network/dag/mock.go @@ -251,17 +251,17 @@ func (m *MockWalkerAlgorithm) EXPECT() *MockWalkerAlgorithmMockRecorder { } // walk mocks base method -func (m *MockWalkerAlgorithm) walk(visitor Visitor, startAt hash.SHA256Hash, getFn func(hash.SHA256Hash) (Document, error), nextsFn func(hash.SHA256Hash) ([]hash.SHA256Hash, error), numberOfNodes int) error { +func (m *MockWalkerAlgorithm) walk(visitor Visitor, startAt hash.SHA256Hash, getFn func(hash.SHA256Hash) (Document, error), nextsFn func(hash.SHA256Hash) ([]hash.SHA256Hash, error)) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "walk", visitor, startAt, getFn, nextsFn, numberOfNodes) + ret := m.ctrl.Call(m, "walk", visitor, startAt, getFn, nextsFn) ret0, _ := ret[0].(error) return ret0 } // walk indicates an expected call of walk -func (mr *MockWalkerAlgorithmMockRecorder) walk(visitor, startAt, getFn, nextsFn, numberOfNodes interface{}) *gomock.Call { +func (mr *MockWalkerAlgorithmMockRecorder) walk(visitor, startAt, getFn, nextsFn interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "walk", reflect.TypeOf((*MockWalkerAlgorithm)(nil).walk), visitor, startAt, getFn, nextsFn, numberOfNodes) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "walk", reflect.TypeOf((*MockWalkerAlgorithm)(nil).walk), visitor, startAt, getFn, nextsFn) } // MockPayloadStore is a mock of PayloadStore interface diff --git a/network/engine/engine.go b/network/engine/engine.go index e89b4b8547..a6524bed1c 100644 --- a/network/engine/engine.go +++ b/network/engine/engine.go @@ -74,7 +74,6 @@ func flagSet() *pflag.FlagSet { "Required when `enableTLS` is `true`.") flagSet.String("network.certKeyFile", defs.CertKeyFile, "PEM file containing the private key of the server certificate. "+ "Required when `network.enableTLS` is `true`.") - flagSet.String("network.databaseFile", defs.DatabaseFile, "File path to the network database.") flagSet.String("network.trustStoreFile", defs.TrustStoreFile, "PEM file containing the trusted CA certificates for authenticating remote gRPC servers.") flagSet.Int("network.advertHashesInterval", defs.AdvertHashesInterval, "Interval (in milliseconds) that specifies how often the node should broadcast its last hashes to other nodes.") return flagSet diff --git a/network/network.go b/network/network.go index 2904f3e925..affcac1537 100644 --- a/network/network.go +++ b/network/network.go @@ -31,6 +31,9 @@ import ( "github.com/nuts-foundation/nuts-node/network/proto" "github.com/pkg/errors" "go.etcd.io/bbolt" + "os" + "path" + "path/filepath" "sync" "time" ) @@ -66,13 +69,16 @@ func NewNetworkInstance(config Config, keyStore crypto.KeyStore) *NetworkEngine } // Configure configures the NetworkEngine subsystem -func (n *NetworkEngine) Configure() error { +func (n *NetworkEngine) Configure(config core.NutsConfig) error { var err error n.configOnce.Do(func() { - db, bboltErr := bbolt.Open(n.Config.DatabaseFile, boltDBFileMode, bbolt.DefaultOptions) + dbFile := path.Join(config.Datadir, "network", "data.db") + if err = os.MkdirAll(filepath.Dir(dbFile), os.ModePerm); err != nil { + return + } + db, bboltErr := bbolt.Open(dbFile, boltDBFileMode, bbolt.DefaultOptions) if bboltErr != nil { err = fmt.Errorf("unable to create bbolt database: %w", err) - return } n.documentGraph = dag.NewBBoltDAG(db) n.payloadStore = dag.NewBBoltPayloadStore(db) diff --git a/network/network_integration_test.go b/network/network_integration_test.go index 85d42c4b5d..0b00cae53b 100644 --- a/network/network_integration_test.go +++ b/network/network_integration_test.go @@ -36,7 +36,6 @@ import ( "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "hash/crc32" - "os" "path" "sync" "testing" @@ -194,7 +193,6 @@ func addDocumentAndWaitForItToArrive(t *testing.T, payload string, sender *Netwo func startNode(name string, directory string) (*NetworkEngine, error) { log.Logger().Infof("Starting node: %s", name) - os.MkdirAll(directory, os.ModePerm) logrus.SetLevel(logrus.DebugLevel) core.NewNutsConfig().Load(&cobra.Command{}) mutex.Lock() @@ -206,7 +204,6 @@ func startNode(name string, directory string) (*NetworkEngine, error) { keyStore: nutsCrypto.Instance(), Config: Config{ GrpcAddr: fmt.Sprintf(":%d", nameToPort(name)), - DatabaseFile: path.Join(directory, "network.db"), PublicAddr: fmt.Sprintf("localhost:%d", nameToPort(name)), CertFile: "test/certificate-and-key.pem", CertKeyFile: "test/certificate-and-key.pem", @@ -215,7 +212,7 @@ func startNode(name string, directory string) (*NetworkEngine, error) { AdvertHashesInterval: 500, }, } - if err := instance.Configure(); err != nil { + if err := instance.Configure(core.NutsConfig{Datadir: directory}); err != nil { return nil, err } if err := instance.Start(); err != nil { diff --git a/network/network_test.go b/network/network_test.go index 478b6cd662..4fe2145e22 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -103,17 +103,24 @@ func TestNetwork_Diagnostics(t *testing.T) { } func TestNetwork_Configure(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() t.Run("ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() cxt := createNetwork(t, ctrl) cxt.protocol.EXPECT().Configure(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) cxt.p2pNetwork.EXPECT().Configure(gomock.Any()) - err := cxt.network.Configure() + err := cxt.network.Configure(core.NutsConfig{Datadir: io.TestDirectory(t)}) if !assert.NoError(t, err) { return } }) + t.Run("unable to create datadir", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cxt := createNetwork(t, ctrl) + err := cxt.network.Configure(core.NutsConfig{Datadir: "network_test.go"}) + assert.Error(t, err) + }) } func TestNetwork_CreateDocument(t *testing.T) { diff --git a/network/test.go b/network/test.go index c166b5ef4a..2e3f22c341 100644 --- a/network/test.go +++ b/network/test.go @@ -19,16 +19,16 @@ package network import ( + "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/crypto" "github.com/sirupsen/logrus" - "path" ) // NewTestNetworkInstance creates a new Network instance that writes it data to a test directory. func NewTestNetworkInstance(testDirectory string) *NetworkEngine { config := TestNetworkConfig(testDirectory) newInstance := NewNetworkInstance(config, crypto.NewTestCryptoInstance(testDirectory)) - if err := newInstance.Configure(); err != nil { + if err := newInstance.Configure(core.NutsConfig{Datadir: testDirectory}); err != nil { logrus.Fatal(err) } return newInstance @@ -37,7 +37,6 @@ func NewTestNetworkInstance(testDirectory string) *NetworkEngine { // NewTestNetworkInstance creates new network config with a test directory as data path. func TestNetworkConfig(testDirectory string) Config { config := DefaultConfig() - config.DatabaseFile = path.Join(testDirectory, "network.db") config.GrpcAddr = ":5555" config.EnableTLS = false config.PublicAddr = "test:5555" diff --git a/vdr/config.go b/vdr/config.go index 123d3bc9d3..44f33b0b50 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -1,8 +1,5 @@ package vdr -// ConfDataDir is the config name for specifiying the data location of the requiredFiles -const ConfDataDir = "vdr.datadir" - // ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). const ConfClientTimeout = "vdr.clientTimeout" @@ -11,14 +8,12 @@ const ModuleName = "Verifiable Data Registry" // Config holds the config for the VDR engine type Config struct { - Datadir string `koanf:"datadir"` - ClientTimeout int `koanf:"clientTimeout"` + ClientTimeout int `koanf:"clientTimeout"` } // DefaultConfig returns a fresh Config filled with default values func DefaultConfig() Config { return Config{ - Datadir: "./data", ClientTimeout: 10, } } diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 5040b1b11a..8bd9f16833 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -61,7 +61,6 @@ func flagSet() *pflag.FlagSet { flagSet := pflag.NewFlagSet("vdr", pflag.ContinueOnError) defs := vdr.DefaultConfig() - flagSet.String(vdr.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) flagSet.Int(vdr.ConfClientTimeout, defs.ClientTimeout, fmt.Sprintf("Time-out for the client in seconds (e.g. when using the CLI), default: %d", defs.ClientTimeout)) return flagSet diff --git a/vdr/vdr.go b/vdr/vdr.go index 72eb58c0dc..71b25d9e1a 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -71,7 +71,7 @@ func NewVDR(config Config, cryptoClient crypto.KeyStore, networkClient network.N } // Configure initializes the db, but only when in server mode -func (r *VDR) Configure() error { +func (r *VDR) Configure(_ core.NutsConfig) error { var err error r.configOnce.Do(func() { @@ -97,10 +97,6 @@ func (r *VDR) Diagnostics() []core.DiagnosticResult { return []core.DiagnosticResult{} } -func (r *VDR) getEventsDir() string { - return r.Config.Datadir + "/events" -} - // Create generates a new DID Document func (r VDR) Create() (*did.Document, error) { doc, err := r.didDocCreator.Create()