From 808a1e9deb4c9b01c78884fdc468852593490b64 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Wed, 3 Jul 2019 18:59:49 +0200 Subject: [PATCH] Add skeleton of a config Took way longer than it should had because I was researching exisiting options. As it turns out, nothing nice exists that would handle: - Multiple overridiable config files - Defaults provided in a struct - Output in a struct License: MIT Signed-off-by: Jakub Sztandera --- .golangci.yml | 1 + go.mod | 5 ++++ go.sum | 7 +++++ node/config/def.go | 38 ++++++++++++++++++++++++ node/config/load.go | 34 ++++++++++++++++++++++ node/config/load_test.go | 63 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 node/config/def.go create mode 100644 node/config/load.go create mode 100644 node/config/load_test.go diff --git a/.golangci.yml b/.golangci.yml index 6ffacd4b2..d8c5cf8c5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,6 +18,7 @@ linters: issues: exclude: - "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this" + - "Potential file inclusion via variable" exclude-use-default: false exclude-rules: diff --git a/go.mod b/go.mod index a729ed395..f0813cf75 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/filecoin-project/go-lotus go 1.12 require ( + github.com/BurntSushi/toml v0.3.1 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.3.1 // indirect github.com/ipfs/go-datastore v0.0.5 github.com/ipfs/go-ipfs-routing v0.1.0 github.com/ipfs/go-log v0.0.2-0.20190703113630-0c3cfb1eccc4 @@ -25,9 +28,11 @@ require ( github.com/libp2p/go-maddr-filter v0.0.4 github.com/multiformats/go-multiaddr v0.0.4 github.com/multiformats/go-multihash v0.0.5 + github.com/stretchr/testify v1.3.0 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 go.uber.org/dig v1.7.0 // indirect go.uber.org/fx v1.9.0 go.uber.org/goleak v0.10.0 // indirect gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 513d9e062..795f01f84 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -22,6 +23,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= @@ -40,6 +43,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -364,4 +369,6 @@ gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 h1:Ggy3mWN4l3PUFPfSG0Y gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8/go.mod h1:cKXr3E0k4aosgycml1b5z33BVV6hai1Kh7uDgFOkbcs= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/node/config/def.go b/node/config/def.go new file mode 100644 index 000000000..5c1042e0a --- /dev/null +++ b/node/config/def.go @@ -0,0 +1,38 @@ +package config + +import "time" + +// Root is starting point of the config +type Root struct { + API API +} + +// API contains configs for API endpoint +type API struct { + ListenAddress string + Timeout Duration +} + +// Default returns the default config +func Default() *Root { + def := Root{ + API: API{ + ListenAddress: "/ip6/::1/tcp/1234/http", + Timeout: Duration(30 * time.Second), + }, + } + return &def +} + +// Duration is a wrapper type for time.Duration for decoding it from TOML +type Duration time.Duration + +// UnmarshalText implements interface for TOML decoding +func (dur *Duration) UnmarshalText(text []byte) error { + d, err := time.ParseDuration(string(text)) + if err != nil { + return err + } + *dur = Duration(d) + return err +} diff --git a/node/config/load.go b/node/config/load.go new file mode 100644 index 000000000..2f2d223b3 --- /dev/null +++ b/node/config/load.go @@ -0,0 +1,34 @@ +package config + +import ( + "io" + "os" + + "github.com/BurntSushi/toml" +) + +// FromFile loads config from a specified file overriding defaults specified in +// the source code. If file does not exist or is empty defaults are asummed. +func FromFile(path string) (*Root, error) { + file, err := os.Open(path) + switch { + case os.IsNotExist(err): + return Default(), nil + case err != nil: + return nil, err + } + + defer file.Close() //nolint:errcheck // The file is RO + return FromReader(file) +} + +// FromReader loads config from a reader instance. +func FromReader(reader io.Reader) (*Root, error) { + cfg := Default() + _, err := toml.DecodeReader(reader, cfg) + if err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/node/config/load_test.go b/node/config/load_test.go new file mode 100644 index 000000000..543a00103 --- /dev/null +++ b/node/config/load_test.go @@ -0,0 +1,63 @@ +package config + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeNothing(t *testing.T) { + assert := assert.New(t) + + { + cfg, err := FromFile(os.DevNull) + assert.Nil(err, "error should be nil") + assert.Equal(Default(), cfg, + "config from empty file should be the same as default") + } + + { + cfg, err := FromFile("./does-not-exist.toml") + assert.Nil(err, "error should be nil") + assert.Equal(Default(), cfg, + "config from not exisiting file should be the same as default") + } +} + +func TestParitalConfig(t *testing.T) { + assert := assert.New(t) + cfgString := ` + [API] + Timeout = "10s" + ` + expected := Default() + expected.API.Timeout = Duration(10 * time.Second) + + { + cfg, err := FromReader(bytes.NewReader([]byte(cfgString))) + assert.NoError(err, "error should be nil") + assert.Equal(expected, cfg, + "config from reader should contain changes") + } + + { + f, err := ioutil.TempFile("", "config-*.toml") + fname := f.Name() + + assert.NoError(err, "tmp file shold not error") + _, err = f.WriteString(cfgString) + assert.NoError(err, "writing to tmp file should not error") + err = f.Close() + assert.NoError(err, "closing tmp file should not error") + defer os.Remove(fname) //nolint:errcheck + + cfg, err := FromFile(fname) + assert.Nil(err, "error should be nil") + assert.Equal(expected, cfg, + "config from reader should contain changes") + } +}