feat: provide clear errors for missing required envs
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Simon 2026-03-23 12:55:05 +01:00
parent f8a3cc4c47
commit 61952f986d
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
6 changed files with 146 additions and 27 deletions

View file

@ -32,11 +32,91 @@ get_env_or_file = fn var_name, default ->
end
end
# Same as get_env_or_file but raises if the value is not set
# Same as get_env_or_file but raises if the value is not set or empty (after trim).
# Empty values lead to unclear runtime errors; failing at boot with a clear message is preferred.
get_env_or_file! = fn var_name, error_message ->
case get_env_or_file.(var_name, nil) do
nil -> raise error_message
value -> value
nil ->
raise error_message
value when is_binary(value) ->
trimmed = String.trim(value)
if trimmed == "" do
raise """
#{error_message}
(Variable #{var_name} or #{var_name}_FILE is set but the value is empty.)
"""
else
trimmed
end
value ->
value
end
end
# Returns default when env_value is nil, empty after trim, or not a valid positive integer.
# Used for PORT, POOL_SIZE, SMTP_PORT to avoid ArgumentError on empty or invalid values.
parse_positive_integer = fn env_value, default ->
case env_value do
nil ->
default
v when is_binary(v) ->
case String.trim(v) do
"" ->
default
trimmed ->
case Integer.parse(trimmed) do
{n, _} when n > 0 -> n
_ -> default
end
end
_ ->
default
end
end
# Returns default when the key is missing or the value is empty (after trim).
# Use for optional string ENV vars (e.g. DATABASE_PORT) so empty string is treated as "unset".
get_env_non_empty = fn key, default ->
case System.get_env(key) do
nil ->
default
v when is_binary(v) ->
trimmed = String.trim(v)
if trimmed == "", do: default, else: trimmed
v ->
v
end
end
# Returns the trimmed value when set and non-empty; otherwise raises with error_message.
# Use for required vars (DATABASE_HOST, etc.) so "set but empty" fails at boot with a clear message.
get_env_required = fn key, error_message ->
case System.get_env(key) do
nil ->
raise error_message
v when is_binary(v) ->
trimmed = String.trim(v)
if trimmed == "" do
raise """
#{error_message}
(Variable #{key} is set but empty.)
"""
else
trimmed
end
v ->
v
end
end
@ -49,12 +129,14 @@ build_database_url = fn ->
nil ->
# Build URL from separate components
host =
System.get_env("DATABASE_HOST") ||
raise "DATABASE_HOST is required when DATABASE_URL is not set"
get_env_required.("DATABASE_HOST", """
DATABASE_HOST is required when DATABASE_URL is not set.
""")
user =
System.get_env("DATABASE_USER") ||
raise "DATABASE_USER is required when DATABASE_URL is not set"
get_env_required.("DATABASE_USER", """
DATABASE_USER is required when DATABASE_URL is not set.
""")
password =
get_env_or_file!.("DATABASE_PASSWORD", """
@ -62,10 +144,11 @@ build_database_url = fn ->
""")
database =
System.get_env("DATABASE_NAME") ||
raise "DATABASE_NAME is required when DATABASE_URL is not set"
get_env_required.("DATABASE_NAME", """
DATABASE_NAME is required when DATABASE_URL is not set.
""")
port = System.get_env("DATABASE_PORT", "5432")
port = get_env_non_empty.("DATABASE_PORT", "5432")
# URL-encode the password to handle special characters
encoded_password = URI.encode_www_form(password)
@ -102,7 +185,7 @@ if config_env() == :prod do
config :mv, Mv.Repo,
# ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
pool_size: parse_positive_integer.(System.get_env("POOL_SIZE"), 10),
socket_options: maybe_ipv6
# The secret key base is used to sign/encrypt cookies and other secrets.
@ -120,11 +203,14 @@ if config_env() == :prod do
# PHX_HOST or DOMAIN can be used to set the host for the application.
# DOMAIN is commonly used in deployment environments (e.g., Portainer templates).
host =
System.get_env("PHX_HOST") ||
System.get_env("DOMAIN") ||
raise "Please define the PHX_HOST or DOMAIN environment variable."
get_env_non_empty.("PHX_HOST", nil) ||
get_env_non_empty.("DOMAIN", nil) ||
raise """
Please define the PHX_HOST or DOMAIN environment variable.
(Variable may be set but empty.)
"""
port = String.to_integer(System.get_env("PORT") || "4000")
port = parse_positive_integer.(System.get_env("PORT"), 4000)
config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
@ -230,11 +316,7 @@ if config_env() == :prod do
smtp_host_env = System.get_env("SMTP_HOST")
if smtp_host_env && String.trim(smtp_host_env) != "" do
smtp_port_env =
case System.get_env("SMTP_PORT") do
nil -> 587
v -> String.to_integer(String.trim(v))
end
smtp_port_env = parse_positive_integer.(System.get_env("SMTP_PORT"), 587)
smtp_password_env =
case System.get_env("SMTP_PASSWORD") do