mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
fix: Don't load from secrets dir if nonexistent or inaccessible (#4002)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
parent
f11af52d30
commit
65ece35966
@ -4,9 +4,12 @@ from pathlib import Path
|
||||
|
||||
import dotenv
|
||||
|
||||
from mealie.core.settings import app_settings_constructor
|
||||
|
||||
from .settings import AppDirectories, AppSettings
|
||||
from mealie.core.settings import (
|
||||
AppDirectories,
|
||||
AppLoggingSettings,
|
||||
AppSettings,
|
||||
app_settings_constructor,
|
||||
)
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
BASE_DIR = CWD.parent.parent
|
||||
@ -38,3 +41,8 @@ def get_app_dirs() -> AppDirectories:
|
||||
@lru_cache
|
||||
def get_app_settings() -> AppSettings:
|
||||
return app_settings_constructor(env_file=ENV, production=PRODUCTION, data_dir=determine_data_dir())
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_logging_settings() -> AppLoggingSettings:
|
||||
return AppLoggingSettings(PRODUCTION=PRODUCTION)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
from .config import get_app_dirs, get_app_settings
|
||||
from .config import get_app_dirs, get_logging_settings
|
||||
from .logger.config import configured_logger
|
||||
|
||||
__root_logger: None | logging.Logger = None
|
||||
@ -18,25 +18,25 @@ def get_logger(module=None) -> logging.Logger:
|
||||
global __root_logger
|
||||
|
||||
if __root_logger is None:
|
||||
app_settings = get_app_settings()
|
||||
app_logging_settings = get_logging_settings()
|
||||
|
||||
mode = "development"
|
||||
|
||||
if app_settings.TESTING:
|
||||
if app_logging_settings.TESTING:
|
||||
mode = "testing"
|
||||
elif app_settings.PRODUCTION:
|
||||
elif app_logging_settings.PRODUCTION:
|
||||
mode = "production"
|
||||
|
||||
dirs = get_app_dirs()
|
||||
|
||||
substitutions = {
|
||||
"DATA_DIR": dirs.DATA_DIR.as_posix(),
|
||||
"LOG_LEVEL": app_settings.LOG_LEVEL.upper(),
|
||||
"LOG_LEVEL": app_logging_settings.LOG_LEVEL.upper(),
|
||||
}
|
||||
|
||||
__root_logger = configured_logger(
|
||||
mode=mode,
|
||||
config_override=app_settings.LOG_CONFIG_OVERRIDE,
|
||||
config_override=app_logging_settings.LOG_CONFIG_OVERRIDE,
|
||||
substitutions=substitutions,
|
||||
)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
@ -34,10 +35,54 @@ def determine_secrets(data_dir: Path, production: bool) -> str:
|
||||
return new_secret
|
||||
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
def get_secrets_dir() -> str | None:
|
||||
"""
|
||||
Returns a directory to load secret settings from, or `None` if the secrets
|
||||
directory does not exist or cannot be accessed.
|
||||
"""
|
||||
# Avoid a circular import by importing here instead of at the file's top-level.
|
||||
# get_logger -> AppSettings -> get_logger
|
||||
from mealie.core.root_logger import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
secrets_dir = "/run/secrets"
|
||||
|
||||
# Check that the secrets directory exists.
|
||||
if not os.path.exists(secrets_dir):
|
||||
logger.warning(f"Secrets directory '{secrets_dir}' does not exist")
|
||||
return None
|
||||
|
||||
# Likewise, check we have permission to read from the secrets directory.
|
||||
if not os.access(secrets_dir, os.R_OK):
|
||||
logger.warning(f"Secrets directory '{secrets_dir}' cannot be read from. Check permissions")
|
||||
return None
|
||||
|
||||
# The secrets directory exists and can be accessed.
|
||||
return secrets_dir
|
||||
|
||||
|
||||
class AppLoggingSettings(BaseSettings):
|
||||
"""
|
||||
Subset of AppSettings to only access logging-related settings.
|
||||
|
||||
This is separated out from AppSettings to allow logging during construction
|
||||
of AppSettings.
|
||||
"""
|
||||
|
||||
TESTING: bool = False
|
||||
PRODUCTION: bool
|
||||
|
||||
LOG_CONFIG_OVERRIDE: Path | None = None
|
||||
"""path to custom logging configuration file"""
|
||||
|
||||
LOG_LEVEL: str = "info"
|
||||
"""corresponds to standard Python log levels"""
|
||||
|
||||
|
||||
class AppSettings(AppLoggingSettings):
|
||||
theme: Theme = Theme()
|
||||
|
||||
PRODUCTION: bool
|
||||
BASE_URL: str = "http://localhost:8080"
|
||||
"""trailing slashes are trimmed (ex. `http://localhost:8080/` becomes ``http://localhost:8080`)"""
|
||||
|
||||
@ -56,12 +101,6 @@ class AppSettings(BaseSettings):
|
||||
|
||||
SECRET: str
|
||||
|
||||
LOG_CONFIG_OVERRIDE: Path | None = None
|
||||
"""path to custom logging configuration file"""
|
||||
|
||||
LOG_LEVEL: str = "info"
|
||||
"""corresponds to standard Python log levels"""
|
||||
|
||||
GIT_COMMIT_HASH: str = "unknown"
|
||||
|
||||
ALLOW_SIGNUP: bool = False
|
||||
@ -69,17 +108,13 @@ class AppSettings(BaseSettings):
|
||||
DAILY_SCHEDULE_TIME: str = "23:45"
|
||||
"""Local server time, in HH:MM format. See `DAILY_SCHEDULE_TIME_UTC` for the parsed UTC equivalent"""
|
||||
|
||||
_logger: logging.Logger | None = None
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
if self._logger is None:
|
||||
# lazy load the logger, since get_logger depends on the settings being loaded
|
||||
from mealie.core.root_logger import get_logger
|
||||
# Avoid a circular import by importing here instead of at the file's top-level.
|
||||
# get_logger -> AppSettings -> get_logger
|
||||
from mealie.core.root_logger import get_logger
|
||||
|
||||
self._logger = get_logger()
|
||||
|
||||
return self._logger
|
||||
return get_logger()
|
||||
|
||||
@property
|
||||
def DAILY_SCHEDULE_TIME_UTC(self) -> ScheduleTime:
|
||||
@ -303,11 +338,7 @@ class AppSettings(BaseSettings):
|
||||
"""Validates OpenAI settings are all set"""
|
||||
return bool(self.OPENAI_API_KEY and self.OPENAI_MODEL)
|
||||
|
||||
# ===============================================
|
||||
# Testing Config
|
||||
|
||||
TESTING: bool = False
|
||||
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow", secrets_dir="/run/secrets")
|
||||
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
||||
|
||||
|
||||
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
||||
@ -319,6 +350,9 @@ def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, e
|
||||
app_settings = AppSettings(
|
||||
_env_file=env_file, # type: ignore
|
||||
_env_file_encoding=env_encoding, # type: ignore
|
||||
# `get_secrets_dir` must be called here rather than within `AppSettings`
|
||||
# to avoid a circular import.
|
||||
_secrets_dir=get_secrets_dir(), # type: ignore
|
||||
**{"SECRET": determine_secrets(data_dir, production)},
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user