This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun 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 4b2ea14b13e Fix support for a path in `[api] base_url` (#47840)
4b2ea14b13e is described below

commit 4b2ea14b13e7ea4bd36c778485f63cdeecf69711
Author: Jed Cunningham <[email protected]>
AuthorDate: Mon Mar 17 06:14:49 2025 -0600

    Fix support for a path in `[api] base_url` (#47840)
    
    * Fix missing trailing slash when base_url is set via env var
    
    When `[api] base_url` does not have a trailing slash configured, we
    update it to have one. However, this was done with `conf.set`, which
    does _not_ actually take precedence over the original value if it was
    configured with an env var. So, instead, we will set the fixed value in
    an env var.
    
    * Fix auth manager support for paths in base_url
    
    * Fix tests
    
    ---------
    
    Co-authored-by: pierrejeambrun <[email protected]>
---
 airflow/api_fastapi/app.py             | 22 +++++++++++-----------
 airflow/ui/src/main.tsx                |  5 ++++-
 tests/operators/test_trigger_dagrun.py |  2 +-
 3 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/airflow/api_fastapi/app.py b/airflow/api_fastapi/app.py
index a96055a4d08..07061ae70b3 100644
--- a/airflow/api_fastapi/app.py
+++ b/airflow/api_fastapi/app.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import logging
+import os
 from contextlib import AsyncExitStack, asynccontextmanager
 from typing import TYPE_CHECKING
 from urllib.parse import urlsplit
@@ -40,8 +41,14 @@ from airflow.exceptions import AirflowConfigException
 if TYPE_CHECKING:
     from airflow.api_fastapi.auth.managers.base_auth_manager import 
BaseAuthManager
 
-# Define the path in which the potential auth manager fastapi is mounted
-AUTH_MANAGER_FASTAPI_APP_PREFIX = "/auth"
+API_BASE_URL = conf.get("api", "base_url")
+if API_BASE_URL and not API_BASE_URL.endswith("/"):
+    API_BASE_URL += "/"
+    os.environ["AIRFLOW__API__BASE_URL"] = API_BASE_URL
+API_ROOT_PATH = urlsplit(API_BASE_URL).path
+
+# Define the full path on which the potential auth manager fastapi is mounted
+AUTH_MANAGER_FASTAPI_APP_PREFIX = f"{API_ROOT_PATH}auth"
 
 log = logging.getLogger(__name__)
 
@@ -64,20 +71,13 @@ async def lifespan(app: FastAPI):
 def create_app(apps: str = "all") -> FastAPI:
     apps_list = apps.split(",") if apps else ["all"]
 
-    fastapi_base_url = conf.get("api", "base_url", fallback="")
-    if fastapi_base_url and not fastapi_base_url.endswith("/"):
-        fastapi_base_url += "/"
-        conf.set("api", "base_url", fastapi_base_url)
-
-    root_path = urlsplit(fastapi_base_url).path.removesuffix("/")
-
     app = FastAPI(
         title="Airflow API",
         description="Airflow API. All endpoints located under ``/public`` can 
be used safely, are stable and backward compatible. "
         "Endpoints located under ``/ui`` are dedicated to the UI and are 
subject to breaking change "
         "depending on the need of the frontend. Users should not rely on those 
but use the public ones instead.",
         lifespan=lifespan,
-        root_path=root_path,
+        root_path=API_ROOT_PATH.removesuffix("/"),
     )
 
     if "execution" in apps_list or "all" in apps_list:
@@ -144,7 +144,7 @@ def init_auth_manager(app: FastAPI | None = None) -> 
BaseAuthManager:
     am.init()
 
     if app and (auth_manager_fastapi_app := am.get_fastapi_app()):
-        app.mount(AUTH_MANAGER_FASTAPI_APP_PREFIX, auth_manager_fastapi_app)
+        app.mount("/auth", auth_manager_fastapi_app)
         app.state.auth_manager = am
 
     return am
diff --git a/airflow/ui/src/main.tsx b/airflow/ui/src/main.tsx
index 73aef5517f2..5c6215ab178 100644
--- a/airflow/ui/src/main.tsx
+++ b/airflow/ui/src/main.tsx
@@ -44,7 +44,10 @@ axios.interceptors.response.use(
       const params = new URLSearchParams();
 
       params.set("next", globalThis.location.href);
-      globalThis.location.replace(`/public/auth/login?${params.toString()}`);
+      const baseUrl = 
document.querySelector("head>base")?.getAttribute("href") ?? "";
+      const loginPath = new URL("public/auth/login", baseUrl).pathname;
+
+      globalThis.location.replace(`${loginPath}?${params.toString()}`);
     }
 
     return Promise.reject(error);
diff --git a/tests/operators/test_trigger_dagrun.py 
b/tests/operators/test_trigger_dagrun.py
index e8464dde2b3..bb6aa842a80 100644
--- a/tests/operators/test_trigger_dagrun.py
+++ b/tests/operators/test_trigger_dagrun.py
@@ -99,7 +99,7 @@ class TestDagRunOperator:
 
         if AIRFLOW_V_3_0_PLUS:
             base_url = conf.get_mandatory_value("api", "base_url").lower()
-            expected_url = 
f"{base_url}/dags/{triggered_dag_run.dag_id}/runs/{triggered_dag_run.run_id}"
+            expected_url = 
f"{base_url}dags/{triggered_dag_run.dag_id}/runs/{triggered_dag_run.run_id}"
 
             link = triggering_task.operator_extra_links[0].get_link(
                 operator=triggering_task, ti_key=triggering_ti.key

Reply via email to