ReadEnvSecrets¶
Read a .env file and the process environment into an EnvSecrets.
A path with no file at it is not an error: the result is an EnvSecrets
holding the environment alone. A file that is there but cannot be read, or
that has a malformed line, is a DotEnvError, and neither is safe to carry
on from.
Call this once at startup. It reads the file synchronously, so the calling actor's scheduler thread waits for the disk.
Reading the file needs FileRead and FileStat on dotenv. A relative
path resolves against the working directory, so pass an absolute path when
that directory is not trusted. A file larger than 1 MiB is not read.
An environment variable overrides the .env file. When a name is set in
both, the value from vars wins and the file's value is discarded, so the
file supplies only the names the environment leaves unset. A .env file is
a development convenience; that ordering keeps one from shadowing a secret
a deployment supplies. Where one name is set twice in the same source, the
first of them wins, which is what getenv returns for the environment.
Names are matched exactly. On Windows, where the environment's own names are
case-insensitive, a .env name that differs from an environment name only
in case is a second name and is not overridden.
Each line of the file is one of:
- blank, or a comment beginning with
#, both of which are ignored NAME=VALUE, split at the first=
A name is one or more letters, digits and underscores, not starting with a
digit. Anything else is malformed, including the export NAME=VALUE that a
shell would accept.
Surrounding whitespace is removed from the name and from the value. A value
wrapped in a matching pair of " or ' has those quotes removed and keeps
the whitespace inside them. Everything else is part of the value: there is
no escaping, no ${...} substitution, no value spanning two lines, and no
comment after a value, so A=b # note binds b # note.
An environment variable's value is taken verbatim instead, so the same text
can mean different things depending on which source supplied it. A=hunter2
keeps its trailing space from the environment and loses it from the file.
Nothing is written back into the process environment. A name read from the
file is visible through the returned EnvSecrets and nowhere else, so it
does not reach a subprocess.
use "files"
use "sensitive"
actor Main
new create(env: Env) =>
let dotenv = FilePath(
FileAuth(env.root),
".env",
recover val FileCaps .> set(FileRead) .> set(FileStat) end)
let secrets =
match ReadEnvSecrets(env.vars, dotenv)
| let s: EnvSecrets => s
| let e: DotEnvError =>
env.err.print(e.string())
env.exitcode(1)
return
end
try
let password = secrets("DB_PASSWORD")?
env.out.print("The password is " + password.string())
else
env.out.print("DB_PASSWORD is not set")
end
Constructors¶
create¶
Returns¶
- ReadEnvSecrets val^
Public Functions¶
apply¶
Read dotenv and vars into an EnvSecrets, or report why the file
could not be used.
Parameters¶
Returns¶
- (EnvSecrets val | DotEnvError)
eq¶
Parameters¶
- that: ReadEnvSecrets val
Returns¶
- Bool val
ne¶
Parameters¶
- that: ReadEnvSecrets val
Returns¶
- Bool val