This is an automated email from the ASF dual-hosted git repository.
vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 2563e07a139 Fix FAB DB manager discovery in migration-only contexts
(#64145)
2563e07a139 is described below
commit 2563e07a139db5e60c86734da2f8c6bd26d982a8
Author: Subham <[email protected]>
AuthorDate: Tue Mar 24 19:01:46 2026 +0530
Fix FAB DB manager discovery in migration-only contexts (#64145)
---
airflow-core/src/airflow/utils/db_manager.py | 33 +++++++++---
airflow-core/tests/unit/utils/test_db_manager.py | 68 +++++++++++++++++++++++-
2 files changed, 93 insertions(+), 8 deletions(-)
diff --git a/airflow-core/src/airflow/utils/db_manager.py
b/airflow-core/src/airflow/utils/db_manager.py
index 2b1feff7bf9..ef0f515a945 100644
--- a/airflow-core/src/airflow/utils/db_manager.py
+++ b/airflow-core/src/airflow/utils/db_manager.py
@@ -196,16 +196,17 @@ class RunDBManager(LoggingMixin):
"""
def __init__(self):
- from airflow.api_fastapi.app import create_auth_manager
from airflow.providers_manager import ProvidersManager
super().__init__()
self._managers: list[BaseDBManager] = []
- # Start with auto-discovered DB managers from installed providers
+ # Start with auto-discovered DB managers from installed providers.
+ # ProvidersManager reads the ``db-managers`` key from each provider's
+ # get_provider_info() and is the primary source of truth.
managers: list[str] = list(ProvidersManager().db_managers)
- # Add any explicitly configured managers not already discovered
+ # Add any explicitly configured managers not already discovered.
managers_config = conf.get("database", "external_db_managers",
fallback=None)
if managers_config:
for m in managers_config.split(","):
@@ -213,10 +214,28 @@ class RunDBManager(LoggingMixin):
if stripped not in managers:
managers.append(stripped)
- # Add DB manager declared by the configured auth manager (existing
behavior, deduplicated)
- auth_manager_db_manager = create_auth_manager().get_db_manager()
- if auth_manager_db_manager and auth_manager_db_manager not in managers:
- managers.append(auth_manager_db_manager)
+ # Add the DB manager declared by the configured auth manager as a
+ # final fallback for backward compatibility.
+ # This is wrapped in a try/except because in migration-only contexts
+ # (e.g. the Helm migrateDatabaseJob) the auth manager may not be fully
+ # initializable — a Flask app context or other runtime state may be
+ # absent. A failure here must not silently drop the auth manager's DB
+ # manager from the migration list; ProvidersManager discovery above is
+ # the reliable path in those contexts.
+ try:
+ from airflow.api_fastapi.app import create_auth_manager
+
+ auth_manager_db_manager = create_auth_manager().get_db_manager()
+ if auth_manager_db_manager and auth_manager_db_manager not in
managers:
+ managers.append(auth_manager_db_manager)
+ except Exception:
+ self.log.debug(
+ "Could not retrieve DB manager from auth manager during
RunDBManager "
+ "initialisation. This is expected in migration-only contexts
where the "
+ "auth manager cannot be fully initialised. DB managers
discovered via "
+ "ProvidersManager will still be used.",
+ exc_info=True,
+ )
for module in managers:
manager = import_string(module.strip())
diff --git a/airflow-core/tests/unit/utils/test_db_manager.py
b/airflow-core/tests/unit/utils/test_db_manager.py
index efd417b12f4..61716e11c7d 100644
--- a/airflow-core/tests/unit/utils/test_db_manager.py
+++ b/airflow-core/tests/unit/utils/test_db_manager.py
@@ -230,10 +230,76 @@ class TestRunDBManager:
def
test_initdb_and_upgradedb_pass_use_migration_files_to_var_kwarg_manager(self,
session):
VarKwargExternalManager.initdb_kwargs = []
VarKwargExternalManager.upgradedb_kwargs = []
-
run_db_manager = _create_run_db_manager(VarKwargExternalManager)
run_db_manager.initdb(session=session, use_migration_files=True)
run_db_manager.upgradedb(session=session, use_migration_files=False)
assert VarKwargExternalManager.initdb_kwargs ==
[{"use_migration_files": True}]
assert VarKwargExternalManager.upgradedb_kwargs ==
[{"use_migration_files": False}]
+
+ @mock.patch("airflow.utils.db_manager.import_string")
+ def
test_run_db_manager_uses_providers_manager_when_auth_manager_fails(self,
mock_import):
+ """
+ When create_auth_manager() raises in a migration-only context (e.g.
the Helm
+ migrateDatabaseJob), RunDBManager must still load managers discovered
via
+ ProvidersManager — the exception must not silently drop all DB
managers.
+ """
+ sentinel = object()
+ mock_import.return_value = sentinel
+
+ with (
+ mock.patch(
+ "airflow.providers_manager.ProvidersManager",
+ ) as mock_pm,
+ mock.patch(
+ "airflow.api_fastapi.app.create_auth_manager",
+ side_effect=RuntimeError("No app context"),
+ ),
+ ):
+ mock_pm.return_value.db_managers =
["airflow.providers.fab.auth_manager.models.db.FABDBManager"]
+ with mock.patch("airflow.utils.db_manager.conf") as mock_conf:
+ mock_conf.get.return_value = None
+ rdm = RunDBManager.__new__(RunDBManager)
+ # Manually call __init__ so we control all side-effects
+ RunDBManager.__init__(rdm)
+
+ # The sentinel class returned by import_string must be in _managers
+ assert sentinel in rdm._managers
+
+ @mock.patch("airflow.utils.db_manager.import_string")
+ def
test_run_db_manager_includes_auth_manager_db_manager_when_available(self,
mock_import):
+ """
+ When create_auth_manager() succeeds and returns a DB manager class
name not
+ already in the ProvidersManager list, it must be appended to _managers.
+ """
+ sentinel_pm = object()
+ sentinel_am = object()
+ call_order = []
+
+ def _import(path):
+ if "fab" in path:
+ call_order.append("fab")
+ return sentinel_pm
+ call_order.append("auth_manager_extra")
+ return sentinel_am
+
+ mock_import.side_effect = _import
+
+ mock_am = mock.MagicMock()
+ mock_am.get_db_manager.return_value = "some.extra.AuthManagerDBManager"
+
+ with (
+ mock.patch("airflow.providers_manager.ProvidersManager") as
mock_pm,
+ mock.patch(
+ "airflow.api_fastapi.app.create_auth_manager",
+ return_value=mock_am,
+ ),
+ ):
+ mock_pm.return_value.db_managers =
["airflow.providers.fab.auth_manager.models.db.FABDBManager"]
+ with mock.patch("airflow.utils.db_manager.conf") as mock_conf:
+ mock_conf.get.return_value = None
+ rdm = RunDBManager.__new__(RunDBManager)
+ RunDBManager.__init__(rdm)
+
+ assert sentinel_pm in rdm._managers
+ assert sentinel_am in rdm._managers