-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(wakunode2): support configuration via environment variables
- Loading branch information
Lorenzo Delgado
authored
Nov 3, 2022
1 parent
85d2842
commit d1df046
Showing
11 changed files
with
494 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
{.used.} | ||
|
||
import | ||
std/[os, options], | ||
stew/results, | ||
stew/shims/net as stewNet, | ||
testutils/unittests, | ||
confutils, | ||
confutils/defs, | ||
confutils/std/net | ||
import | ||
../../waku/common/confutils/envvar/defs as confEnvvarDefs, | ||
../../waku/common/confutils/envvar/std/net as confEnvvarNet | ||
|
||
|
||
type ConfResult[T] = Result[T, string] | ||
|
||
type TestConf = object | ||
configFile* {. | ||
desc: "Configuration file path" | ||
name: "config-file" }: Option[InputFile] | ||
|
||
testFile* {. | ||
desc: "Configuration test file path" | ||
name: "test-file" }: Option[InputFile] | ||
|
||
listenAddress* {. | ||
defaultValue: ValidIpAddress.init("127.0.0.1"), | ||
desc: "Listening address", | ||
name: "listen-address"}: ValidIpAddress | ||
|
||
tcpPort* {. | ||
desc: "TCP listening port", | ||
defaultValue: 60000, | ||
name: "tcp-port" }: Port | ||
|
||
|
||
{.push warning[ProveInit]: off.} | ||
|
||
proc load*(T: type TestConf, prefix: string): ConfResult[T] = | ||
try: | ||
let conf = TestConf.load( | ||
secondarySources = proc (conf: TestConf, sources: auto) = | ||
sources.addConfigFile(Envvar, InputFile(prefix)) | ||
) | ||
ok(conf) | ||
except CatchableError: | ||
err(getCurrentExceptionMsg()) | ||
|
||
{.pop.} | ||
|
||
|
||
suite "nim-confutils - envvar": | ||
test "load configuration from environment variables": | ||
## Given | ||
let prefix = "test-prefix" | ||
|
||
let | ||
listenAddress = "1.1.1.1" | ||
tcpPort = "8080" | ||
configFile = "/tmp/test.conf" | ||
|
||
## When | ||
os.putEnv("TEST_PREFIX_CONFIG_FILE", configFile) | ||
os.putEnv("TEST_PREFIX_LISTEN_ADDRESS", listenAddress) | ||
os.putEnv("TEST_PREFIX_TCP_PORT", tcpPort) | ||
|
||
let confLoadRes = TestConf.load(prefix) | ||
|
||
## Then | ||
check confLoadRes.isOk() | ||
|
||
let conf = confLoadRes.get() | ||
check: | ||
conf.listenAddress == ValidIpAddress.init(listenAddress) | ||
conf.tcpPort == Port(8080) | ||
|
||
conf.configFile.isSome() | ||
conf.configFile.get().string == configFile | ||
|
||
conf.testFile.isNone() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{.used.} | ||
|
||
import | ||
testutils/unittests | ||
import | ||
../../waku/common/envvar_serialization/utils | ||
|
||
|
||
suite "nim-envvar-serialization - utils": | ||
test "construct env var key": | ||
## Given | ||
let prefix = "some-prefix" | ||
let name = @["db-url"] | ||
|
||
## When | ||
let key = constructKey(prefix, name) | ||
|
||
## Then | ||
check: | ||
key == "SOME_PREFIX_DB_URL" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
|
||
import | ||
confutils/defs as confutilsDefs | ||
import | ||
../../envvar_serialization | ||
|
||
export | ||
envvar_serialization, confutilsDefs | ||
|
||
|
||
template readConfutilsType(T: type) = | ||
template readValue*(r: var EnvvarReader, value: var T) = | ||
value = T r.readValue(string) | ||
|
||
readConfutilsType InputFile | ||
readConfutilsType InputDir | ||
readConfutilsType OutPath | ||
readConfutilsType OutDir | ||
readConfutilsType OutFile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
|
||
import | ||
std/strutils, | ||
stew/shims/net | ||
import | ||
../../../envvar_serialization | ||
|
||
export | ||
net, | ||
envvar_serialization | ||
|
||
|
||
proc readValue*(r: var EnvvarReader, value: var ValidIpAddress) {.raises: [SerializationError].} = | ||
try: | ||
value = ValidIpAddress.init(r.readValue(string)) | ||
except ValueError: | ||
raise newException(EnvvarError, "Invalid IP address") | ||
|
||
proc readValue*(r: var EnvvarReader, value: var Port) {.raises: [SerializationError, ValueError].} = | ||
try: | ||
value = parseUInt(r.readValue(string)).Port | ||
except ValueError: | ||
raise newException(EnvvarError, "Invalid Port") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
|
||
import | ||
stew/shims/macros, | ||
serialization | ||
import | ||
./envvar_serialization/reader, | ||
./envvar_serialization/writer | ||
|
||
export | ||
serialization, | ||
reader, | ||
writer | ||
|
||
|
||
serializationFormat Envvar | ||
|
||
Envvar.setReader EnvvarReader | ||
Envvar.setWriter EnvvarWriter, PreferredOutput = void | ||
|
||
|
||
template supports*(_: type Envvar, T: type): bool = | ||
# The Envvar format should support every type | ||
true | ||
|
||
template decode*(_: type Envvar, | ||
prefix: string, | ||
RecordType: distinct type, | ||
params: varargs[untyped]): auto = | ||
mixin init, ReaderType | ||
|
||
{.noSideEffect.}: | ||
var reader = unpackArgs(init, [EnvvarReader, prefix, params]) | ||
reader.readValue(RecordType) | ||
|
||
template encode*(_: type Envvar, | ||
prefix: string, | ||
value: auto, | ||
params: varargs[untyped]) = | ||
mixin init, WriterType, writeValue | ||
|
||
{.noSideEffect.}: | ||
var writer = unpackArgs(init, [EnvvarWriter, prefix, params]) | ||
writeValue writer, value | ||
|
||
template loadFile*(_: type Envvar, | ||
prefix: string, | ||
RecordType: distinct type, | ||
params: varargs[untyped]): auto = | ||
mixin init, ReaderType, readValue | ||
|
||
var reader = unpackArgs(init, [EnvvarReader, prefix, params]) | ||
reader.readValue(RecordType) | ||
|
||
template saveFile*(_: type Envvar, prefix: string, value: auto, params: varargs[untyped]) = | ||
mixin init, WriterType, writeValue | ||
|
||
var writer = unpackArgs(init, [EnvvarWriter, prefix, params]) | ||
writer.writeValue(value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
|
||
import | ||
std/[tables, typetraits, options, os], | ||
serialization/object_serialization, | ||
serialization/errors | ||
import | ||
./utils | ||
|
||
|
||
type | ||
EnvvarReader* = object | ||
prefix: string | ||
key: seq[string] | ||
|
||
EnvvarError* = object of SerializationError | ||
|
||
EnvvarReaderError* = object of EnvvarError | ||
|
||
GenericEnvvarReaderError* = object of EnvvarReaderError | ||
deserializedField*: string | ||
innerException*: ref CatchableError | ||
|
||
|
||
proc handleReadException*(r: EnvvarReader, | ||
Record: type, | ||
fieldName: string, | ||
field: auto, | ||
err: ref CatchableError) {.raises: [GenericEnvvarReaderError].} = | ||
var ex = new GenericEnvvarReaderError | ||
ex.deserializedField = fieldName | ||
ex.innerException = err | ||
raise ex | ||
|
||
proc init*(T: type EnvvarReader, prefix: string): T = | ||
result.prefix = prefix | ||
|
||
proc readValue*[T](r: var EnvvarReader, value: var T) {.raises: [ValueError, SerializationError].} = | ||
mixin readValue | ||
|
||
when T is string: | ||
let key = constructKey(r.prefix, r.key) | ||
value = os.getEnv(key) | ||
|
||
elif T is (SomePrimitives or range): | ||
let key = constructKey(r.prefix, r.key) | ||
getValue(key, value) | ||
|
||
elif T is Option: | ||
template getUnderlyingType[T](_: Option[T]): untyped = T | ||
let key = constructKey(r.prefix, r.key) | ||
if os.existsEnv(key): | ||
type uType = getUnderlyingType(value) | ||
when uType is string: | ||
value = some(os.getEnv(key)) | ||
else: | ||
value = some(r.readValue(uType)) | ||
|
||
elif T is (seq or array): | ||
when uTypeIsPrimitives(T): | ||
let key = constructKey(r.prefix, r.key) | ||
getValue(key, value) | ||
|
||
else: | ||
let key = r.key[^1] | ||
for i in 0..<value.len: | ||
r.key[^1] = key & $i | ||
r.readValue(value[i]) | ||
|
||
elif T is (object or tuple): | ||
type T = type(value) | ||
when T.totalSerializedFields > 0: | ||
let fields = T.fieldReadersTable(EnvvarReader) | ||
var expectedFieldPos = 0 | ||
r.key.add "" | ||
value.enumInstanceSerializedFields(fieldName, field): | ||
when T is tuple: | ||
r.key[^1] = $expectedFieldPos | ||
var reader = fields[][expectedFieldPos].reader | ||
expectedFieldPos += 1 | ||
else: | ||
r.key[^1] = fieldName | ||
var reader = findFieldReader(fields[], fieldName, expectedFieldPos) | ||
|
||
if reader != nil: | ||
reader(value, r) | ||
discard r.key.pop() | ||
|
||
else: | ||
const typeName = typetraits.name(T) | ||
{.fatal: "Failed to convert from Envvar an unsupported type: " & typeName.} |
Oops, something went wrong.