mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-19 23:08:08 +02:00
* refactor env var handling - use shared helper - use left-to-right parser * add tests * formatting
106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
import os
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Annotated
|
|
|
|
from pydantic import AfterValidator, ValidationInfo
|
|
|
|
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
|
|
secrets_dir = os.environ.get("CREDENTIALS_DIRECTORY", "/run/secrets")
|
|
# read secret files as env vars too
|
|
if os.path.isdir(secrets_dir) and os.access(secrets_dir, os.R_OK):
|
|
for secret_file in os.listdir(secrets_dir):
|
|
if secret_file.startswith("FRIGATE_"):
|
|
FRIGATE_ENV_VARS[secret_file] = (
|
|
Path(os.path.join(secrets_dir, secret_file)).read_text().strip()
|
|
)
|
|
|
|
|
|
# Matches a FRIGATE_* identifier following an opening brace.
|
|
_FRIGATE_IDENT_RE = re.compile(r"FRIGATE_[A-Za-z0-9_]+")
|
|
|
|
|
|
def substitute_frigate_vars(value: str) -> str:
|
|
"""Substitute `{FRIGATE_*}` placeholders in *value*.
|
|
|
|
Reproduces the subset of `str.format()` brace semantics that Frigate's
|
|
config has historically supported, while leaving unrelated brace content
|
|
(e.g. ffmpeg `%{localtime\\:...}` expressions) untouched:
|
|
|
|
* `{{` and `}}` collapse to literal `{` / `}` (the documented escape).
|
|
* `{FRIGATE_NAME}` is replaced from `FRIGATE_ENV_VARS`; an unknown name
|
|
raises `KeyError` to preserve the existing "Invalid substitution"
|
|
error path.
|
|
* A `{` that begins `{FRIGATE_` but is not a well-formed
|
|
`{FRIGATE_NAME}` placeholder raises `ValueError` (malformed
|
|
placeholder). Callers that catch `KeyError` to allow unknown-var
|
|
passthrough will still surface malformed syntax as an error.
|
|
* Any other `{` or `}` is treated as a literal and passed through.
|
|
"""
|
|
out: list[str] = []
|
|
i = 0
|
|
n = len(value)
|
|
while i < n:
|
|
ch = value[i]
|
|
if ch == "{":
|
|
# Escaped literal `{{`.
|
|
if i + 1 < n and value[i + 1] == "{":
|
|
out.append("{")
|
|
i += 2
|
|
continue
|
|
# Possible `{FRIGATE_*}` placeholder.
|
|
if value.startswith("{FRIGATE_", i):
|
|
ident_match = _FRIGATE_IDENT_RE.match(value, i + 1)
|
|
if (
|
|
ident_match is not None
|
|
and ident_match.end() < n
|
|
and value[ident_match.end()] == "}"
|
|
):
|
|
key = ident_match.group(0)
|
|
if key not in FRIGATE_ENV_VARS:
|
|
raise KeyError(key)
|
|
out.append(FRIGATE_ENV_VARS[key])
|
|
i = ident_match.end() + 1
|
|
continue
|
|
# Looks like a FRIGATE placeholder but is malformed
|
|
# (no closing brace, illegal char, format spec, etc.).
|
|
raise ValueError(
|
|
f"Malformed FRIGATE_ placeholder near {value[i : i + 32]!r}"
|
|
)
|
|
# Plain `{` — pass through (e.g. `%{localtime\:...}`).
|
|
out.append("{")
|
|
i += 1
|
|
continue
|
|
if ch == "}":
|
|
# Escaped literal `}}`.
|
|
if i + 1 < n and value[i + 1] == "}":
|
|
out.append("}")
|
|
i += 2
|
|
continue
|
|
out.append("}")
|
|
i += 1
|
|
continue
|
|
out.append(ch)
|
|
i += 1
|
|
return "".join(out)
|
|
|
|
|
|
def validate_env_string(v: str) -> str:
|
|
return substitute_frigate_vars(v)
|
|
|
|
|
|
EnvString = Annotated[str, AfterValidator(validate_env_string)]
|
|
|
|
|
|
def validate_env_vars(v: dict[str, str], info: ValidationInfo) -> dict[str, str]:
|
|
if isinstance(info.context, dict) and info.context.get("install", False):
|
|
for k, val in v.items():
|
|
os.environ[k] = val
|
|
if k.startswith("FRIGATE_"):
|
|
FRIGATE_ENV_VARS[k] = val
|
|
|
|
return v
|
|
|
|
|
|
EnvVars = Annotated[dict[str, str], AfterValidator(validate_env_vars)]
|