Skip to content

Commit

Permalink
Modify config.nim to read a config file and env variables
Browse files Browse the repository at this point in the history
  • Loading branch information
tanelso2 committed Nov 18, 2024
1 parent 569ee84 commit 9840f1c
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 53 deletions.
2 changes: 1 addition & 1 deletion src/nim.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ mm:orc
# debugger:native
# d:useMalloc
## END VALGRIND
# d:quitEarly
# d:quitEarly
6 changes: 3 additions & 3 deletions src/tiny_container_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ proc startTcm(disableSetup = false, useHttps = true) {.async.} =

import argparse
var p = newParser:
flag("--no-management", help="Run the API server only")
flag("--disable-management", help="Run the API server only")
flag("--disable-setup", help="Disable the setup (installing packages) and run the tcm tasks")
flag("--disable-https", help="Disable https")
flag("-d", "--debug", help="Enable debug messaging")
Expand All @@ -72,13 +72,13 @@ proc main() =
var opts = p.parse(commandLineParams())

let disableSetup = opts.disable_setup
let useHttps = not opts.disable_https and defaultConfig.httpsEnabled
let useHttps = not opts.disable_https and config.httpsEnabled()
if opts.debug:
debugMode = true
setLogFilter(lvlDebug)
else:
setLogFilter(lvlInfo)
if opts.no_management:
if opts.disable_management:
logWarn "No management mode enabled, not starting the main tcm tasks"
else:
asyncCheck startTcm(disableSetup=disableSetup, useHttps=useHttps)
Expand Down
10 changes: 6 additions & 4 deletions src/tiny_container_manager/api_server.nim
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ router application:
respOk

proc runServer* =
let portNum = config.tcmApiPort
let host = config.tcmHost()
let portNum = config.tcmApiPort()
let port = Port(portNum)
let bindAddr = if config.bindAll: "0.0.0.0" else: "127.0.0.1"
if config.bindAll:
let bindAddr = if config.bindAll(): "0.0.0.0" else: "127.0.0.1"
if config.bindAll():
logWarn fmt"Binding to all addresses at {bindAddr}"
logInfo fmt"Starting server at {bindAddr}"
logInfo fmt"Starting server at {bindAddr}:{portNum}"
logInfo fmt"Listening for API connections at {host}"
let settings = newSettings(port=port, bindAddr=bindAddr)
var jester = initJester(application, settings=settings)
jester.serve()
2 changes: 1 addition & 1 deletion src/tiny_container_manager/auth.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import
./config,
nim_utils/logline

proc getKeys*(dir = config.keysDir): seq[string] =
proc getKeys*(dir: string = config.keysDir()): seq[string] =
result = newSeq[string]()
for (_, path) in walkDir(dir):
let user = path.extractFilename()
Expand Down
151 changes: 124 additions & 27 deletions src/tiny_container_manager/config.nim
Original file line number Diff line number Diff line change
@@ -1,30 +1,127 @@
import
os
import
os,
std/options,
std/strutils,
std/sugar,
std/tables,
nim_utils/logline,
yanyl

const email* = "[email protected]"
const configDir* = "/opt/tiny-container-manager"
const containerDir* = configDir / "containers"
const keysDir* = configDir / "keys"
const tcmHost* = "tcm.thomasnelson.me"
const tcmApiPort* = 6060
const bindAll* = false
const defaultConfigFile* = "/opt/tiny-container-manager/config.yaml"
var defaultConfigFileCache = none(YNode)
proc fileConfig(): YNode =
if defaultConfigFileCache.isNone():
let f = open(defaultConfigFile)
defer: f.close()
let content = f.readAll()
result = content.loadNode()
defaultConfigFileCache = some result
else:
result = defaultConfigFileCache.get()

type
TCMConfig* = object
email*: string
configDir*: string
containerDir*: string
keysDir*: string
tcmHost*: string
tcmApiPort*: int
httpsEnabled*: bool

const defaultConfig*: TCMConfig = TCMConfig(
email: "[email protected]",
configDir: "/opt/tiny-container-manager",
containerDir: configDir / "containers",
keysDir: configDir / "keys",
tcmHost: "tcm.thomasnelson.me",
tcmApiPort: 6060,
httpsEnabled: true
)
ConfigOption*[T] = object of RootObj
envVariable: Option[string]
configFileProperty: Option[string]
defaultValue: Option[T]
parser: string -> T
StringOpt* = ConfigOption[string]
IntOpt* = ConfigOption[int]
BoolOpt* = ConfigOption[bool]

proc id[T](x: T): T = x

proc contains(n: YNode, k: string): bool =
expectYMap n:
result = k in n.mapVal

proc stringOpt*(env: Option[string] = none(string), fileProperty = none(string), defaultValue = none(string)): StringOpt =
return ConfigOption[string](envVariable : env,
configFileProperty : fileProperty,
defaultValue : defaultValue,
parser : id)

proc intOpt*(env: Option[string] = none(string), fileProperty = none(string), defaultValue = none(int)): IntOpt =
return ConfigOption[int](envVariable: env,
configFileProperty: fileProperty,
defaultValue: defaultValue,
parser: parseInt)

proc boolOpt*(env = none(string), fileProperty = none(string), defaultValue = none(bool)): BoolOpt =
return ConfigOption[bool](envVariable: env,
configFileProperty: fileProperty,
defaultValue: defaultValue,
parser: parseBool)

proc get*[T](opt: ConfigOption[T]): Option[T] =
result = none(T)
if opt.envVariable.isSome() and result.isNone():
let envVar = opt.envVariable.get()
if existsEnv(envVar):
let val = getEnv(envVar)
result = some(opt.parser(val))
if opt.configFileProperty.isSome() and result.isNone():
if fileExists(defaultConfigFile):
let configResult = fileConfig()
let prop = opt.configFileProperty.get()
if prop in configResult:
let val = configResult.getStr(prop)
result = some(opt.parser(val))
if opt.defaultValue.isSome() and result.isNone():
result = opt.defaultValue

type
TCMConfigOptions* = object
email*: StringOpt = stringOpt(env = some("TCM_EMAIL"),
fileProperty = some("email"),
defaultValue = some("[email protected]"))
configDir*: StringOpt = stringOpt(env = some "TCM_CONFIG_DIR",
fileProperty = some("configDir"),
defaultValue = some("/opt/tiny-container-manager"))
host*: StringOpt = stringOpt(env = some "TCM_HOST",
fileProperty = some "host",
defaultValue = some "tcm.example.com")
apiPort*: IntOpt = intOpt(env = some "TCM_API_PORT",
fileProperty = some "apiPort",
defaultValue = some 6060)
httpsEnabled*: BoolOpt = boolOpt(env = some "TCM_HTTPS_ENABLED",
fileProperty = some "httpsEnabled",
defaultValue = some true)
bindAll*: BoolOpt = boolOpt(env = some "TCM_BIND_ALL",
fileProperty = some "bindAll",
defaultValue = some false)

let
opts = TCMConfigOptions()

proc email*(): string =
{.gcsafe.}:
opts.email.get.get()

proc configDir*(): string =
{.gcsafe.}:
opts.configDir.get().get()

proc keysDir*(): string =
{.gcsafe.}:
configDir() / "keys"

proc containerDir*(): string =
{.gcsafe.}:
configDir() / "containers"

proc tcmHost*(): string =
{.gcsafe.}:
result = opts.host.get().get()

proc tcmApiPort*(): int =
{.gcsafe.}:
opts.apiPort.get().get()

proc httpsEnabled*(): bool =
{.gcsafe.}:
opts.httpsEnabled.get().get()

proc bindAll*(): bool =
{.gcsafe.}:
opts.bindAll.get().get()
10 changes: 5 additions & 5 deletions src/tiny_container_manager/container.nim
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ proc parseContainer*(filename: string): Container =
proc isConfigFile(filename: string): bool =
result = filename.endsWith(".yaml") or filename.endsWith(".yml")

proc getContainerConfigs*(directory: string = config.containerDir): seq[Container] =
proc getContainerConfigs*(directory: string = config.containerDir()): seq[Container] =
discard directory.existsOrCreateDir
var containers: seq[Container] = newSeq[Container]()
for path in walkFiles(fmt"{directory}/*"):
Expand All @@ -166,26 +166,26 @@ type
ErrAlreadyExists* = object of ValueError
ErrDoesNotExist* = object of OSError

proc deleteNamedContainer*(name: string, dir = config.containerDir) =
proc deleteNamedContainer*(name: string, dir: string = config.containerDir()) =
let filename = fmt"{name}.yaml"
let path = dir / filename
if not path.fileExists:
raise newException(ErrDoesNotExist, fmt"{path} doesn't exist")
path.removePath()

proc writeFile*(c: Container, dir = config.containerDir) =
proc writeFile*(c: Container, dir = config.containerDir()) =
let path = dir / c.filename
var s = newFileStream(path, fmWrite)
defer: s.close()
s.write(c.toYaml().toString())

proc add*(c: Container, dir = config.containerDir) =
proc add*(c: Container, dir = config.containerDir()) =
let path = dir / c.filename
if path.fileExists:
raise newException(ErrAlreadyExists, fmt"{path} already exists")
c.writeFile()

proc getContainerByName*(name: string, dir = config.containerDir): Option[Container] =
proc getContainerByName*(name: string, dir = config.containerDir()): Option[Container] =
let path = dir / fmt"{name}.yaml"
if not path.fileExists:
return none(Container)
Expand Down
4 changes: 2 additions & 2 deletions src/tiny_container_manager/container_collection.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type
dir: string


proc newContainersCollection*(dir = config.containerDir): ContainersCollection =
proc newContainersCollection*(dir = config.containerDir()): ContainersCollection =
proc getExpected(): Future[seq[Container]] {.async.} =
return getContainerConfigs(dir)

Expand All @@ -25,4 +25,4 @@ proc newContainersCollection*(dir = config.containerDir): ContainersCollection
matches: (c: Container, dc: DContainer) => c.matches(dc),
remove: removeContainer,
create: createContainer
)
)
2 changes: 0 additions & 2 deletions src/tiny_container_manager/json_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,3 @@ template jsonResp*(s: string) =

template jsonResp*[T](t: T) =
jsonResp jsonDump(t)


6 changes: 3 additions & 3 deletions src/tiny_container_manager/nginx/conffile.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ type

proc apiConfig*(): NginxConfig =
let rootCon = ContainerRef(path: "/",
port: config.tcmApiPort,
port: config.tcmApiPort(),
container: Container())
NginxConfig(
# The 00- ensures that this will be loaded by default
# when there is not a complete match on the site name
name: "00-tcm_api",
allHosts: @[config.tcmHost],
allHosts: @[config.tcmHost()],
containers: @[rootCon]
)

Expand Down Expand Up @@ -159,7 +159,7 @@ proc compare*(expected: NginxConfig, actual: ActualNginxConfig, useHttps: bool):
proc requestCert*(target: NginxConfig) {.async.} =
let allHosts = target.allHosts
let hostCmdLine = allHosts.map((x) => fmt"-d {x}").join(" ")
let certbotCmd = fmt"certbot certonly --nginx -n --keep {hostCmdLine} --email {config.email} --agree-tos"
let certbotCmd = fmt"certbot certonly --nginx -n --keep {hostCmdLine} --email {config.email()} --agree-tos"
logInfo certbotCmd
logInfo await certbotCmd.asyncExec()

Expand Down
4 changes: 2 additions & 2 deletions src/tiny_container_manager/shell_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ proc setupFirewall*() {.async.} =
discard await asyncExec("ufw allow http")
discard await asyncExec("ufw allow https")
discard await asyncExec("ufw allow 9100") # node_exporter
if config.bindAll:
discard await asyncExec(fmt"ufw allow {config.tcmApiPort}")
if config.bindAll():
discard await asyncExec(fmt"ufw allow {config.tcmApiPort()}")
discard await asyncExec("ufw enable")

proc checkNginxService*(): Future[bool] {.async.} =
Expand Down
6 changes: 3 additions & 3 deletions tiny_container_manager.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT"
srcDir = "src"
bin = @["tiny_container_manager"]

const nimVersion = "2.0.2"
const nimVersion = "2.0.12"
import strformat

# Dependencies
Expand All @@ -17,8 +17,8 @@ requires fmt"nim >= {nimVersion}"
requires "jester == 0.6.0"
requires "argparse"
requires "prometheus"
requires "https://github.com/tanelso2/nim_utils == 0.4.0"
requires "yanyl == 1.2.0"
requires "https://github.com/tanelso2/nim_utils >= 0.4.0"
requires "yanyl == 1.2.2"

task test, "Runs the test suite":
exec "nimble build -y && testament p 'tests/*.nim'"
Expand Down

0 comments on commit 9840f1c

Please sign in to comment.