From 931dabc6f1c6bb01028e653c134246edfeda0ab5 Mon Sep 17 00:00:00 2001 From: Kaxil Naik Date: Tue, 14 Oct 2025 06:37:23 -0700 Subject: [PATCH] [v3-1-test] Remove ``dagReports`` API endpoint (#56609) The `/api/v2/dagReports` endpoint loaded user DAG files directly in the API server process via DagBag, violating Airflow's core architectural principle that the API server must never execute user code. The endpoint was not used by the UI and had no known consumers. Users needing DAG loading reports should use the `airflow dags report` CLI command instead, which runs in an isolated process designed to safely execute user code. (cherry picked from commit 828aaa0b1d95caf90612a648867c17aec7e87874) Co-authored-by: Kaxil Naik --- .../newsfragments/56609.significant.rst | 14 ++ .../core_api/datamodels/dag_report.py | 40 ------ .../openapi/v2-rest-api-generated.yaml | 47 ------ .../core_api/routes/public/__init__.py | 2 - .../core_api/routes/public/dag_report.py | 75 ---------- .../airflow/ui/openapi-gen/queries/common.ts | 8 +- .../ui/openapi-gen/queries/ensureQueryData.ts | 13 +- .../ui/openapi-gen/queries/prefetch.ts | 13 +- .../airflow/ui/openapi-gen/queries/queries.ts | 13 +- .../ui/openapi-gen/queries/suspense.ts | 13 +- .../ui/openapi-gen/requests/services.gen.ts | 29 +--- .../ui/openapi-gen/requests/types.gen.ts | 33 ----- .../core_api/routes/public/test_dag_report.py | 135 ------------------ 13 files changed, 20 insertions(+), 415 deletions(-) create mode 100644 airflow-core/newsfragments/56609.significant.rst delete mode 100644 airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_report.py delete mode 100644 airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_report.py delete mode 100644 airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_report.py diff --git a/airflow-core/newsfragments/56609.significant.rst b/airflow-core/newsfragments/56609.significant.rst new file mode 100644 index 0000000000000..b32e4c9611687 --- /dev/null +++ b/airflow-core/newsfragments/56609.significant.rst @@ -0,0 +1,14 @@ +Remove insecure dagReports API endpoint that executed user code in API server + +The ``/api/v2/dagReports`` endpoint has been removed because it loaded user DAG files directly in the API server process, violating Airflow's security architecture. This endpoint was not used in the UI and had no known consumers. Use the ``airflow dags report`` CLI command instead for DAG loading reports. + +* Types of change + + * [ ] Dag changes + * [ ] Config changes + * [x] API changes + * [ ] CLI changes + * [ ] Behaviour changes + * [ ] Plugin changes + * [ ] Dependency changes + * [ ] Code interface changes diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_report.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_report.py deleted file mode 100644 index aeab3f91af91b..0000000000000 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/dag_report.py +++ /dev/null @@ -1,40 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from __future__ import annotations - -from datetime import timedelta - -from airflow.api_fastapi.core_api.base import BaseModel - - -class DagReportResponse(BaseModel): - """DAG Report serializer for responses.""" - - file: str - duration: timedelta - dag_num: int - task_num: int - dags: str - warning_num: int - - -class DagReportCollectionResponse(BaseModel): - """DAG Report Collection serializer for responses.""" - - dag_reports: list[DagReportResponse] - total_entries: int diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index db6fbc09dbd99..5d582a8d2cd1a 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -2608,53 +2608,6 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /api/v2/dagReports: - get: - tags: - - DagReport - summary: Get Dag Reports - description: Get DAG report. - operationId: get_dag_reports - security: - - OAuth2PasswordBearer: [] - - HTTPBearer: [] - parameters: - - name: subdir - in: query - required: true - schema: - type: string - title: Subdir - responses: - '200': - description: Successful Response - content: - application/json: - schema: {} - '401': - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPExceptionResponse' - description: Unauthorized - '403': - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPExceptionResponse' - description: Forbidden - '400': - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPExceptionResponse' - description: Bad Request - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' /api/v2/config: get: tags: diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py index 65cdbef6642f1..0b501b4f99f71 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py @@ -27,7 +27,6 @@ from airflow.api_fastapi.core_api.routes.public.config import config_router from airflow.api_fastapi.core_api.routes.public.connections import connections_router from airflow.api_fastapi.core_api.routes.public.dag_parsing import dag_parsing_router -from airflow.api_fastapi.core_api.routes.public.dag_report import dag_report_router from airflow.api_fastapi.core_api.routes.public.dag_run import dag_run_router from airflow.api_fastapi.core_api.routes.public.dag_sources import dag_sources_router from airflow.api_fastapi.core_api.routes.public.dag_stats import dag_stats_router @@ -65,7 +64,6 @@ authenticated_router.include_router(dag_run_router) authenticated_router.include_router(dag_sources_router) authenticated_router.include_router(dag_stats_router) -authenticated_router.include_router(dag_report_router) authenticated_router.include_router(config_router) authenticated_router.include_router(dag_warning_router) authenticated_router.include_router(dags_router) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_report.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_report.py deleted file mode 100644 index 42539015b53d1..0000000000000 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_report.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from __future__ import annotations - -import ast -import os -from typing import cast - -from fastapi import Depends, HTTPException, status - -from airflow import settings -from airflow.api_fastapi.common.router import AirflowRouter -from airflow.api_fastapi.core_api.datamodels.dag_report import ( - DagReportCollectionResponse, - DagReportResponse, -) -from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc -from airflow.api_fastapi.core_api.security import ( - ReadableDagsFilterDep, - requires_access_dag, -) -from airflow.models.dagbag import DagBag - -dag_report_router = AirflowRouter(tags=["DagReport"], prefix="/dagReports") - - -@dag_report_router.get( - "", - responses=create_openapi_http_exception_doc( - [ - status.HTTP_400_BAD_REQUEST, - ] - ), - dependencies=[Depends(requires_access_dag(method="GET"))], -) -def get_dag_reports( - subdir: str, - readable_dags_filter: ReadableDagsFilterDep, -): - """Get DAG report.""" - fullpath = os.path.normpath(subdir) - if not fullpath.startswith(settings.DAGS_FOLDER): - raise HTTPException(status.HTTP_400_BAD_REQUEST, "subdir should be subpath of DAGS_FOLDER settings") - - dagbag = DagBag(fullpath) - - readable_dag_ids: set[str] | None = readable_dags_filter.value - if readable_dag_ids: - filtered_dagbag_stats = [ - file_load_stat - for file_load_stat in dagbag.dagbag_stats - if len(set(ast.literal_eval(file_load_stat.dags)) - readable_dag_ids) == 0 - ] - else: - filtered_dagbag_stats = [] - - return DagReportCollectionResponse( - dag_reports=cast("list[DagReportResponse]", filtered_dagbag_stats), - total_entries=len(filtered_dagbag_stats), - ) diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts index 974f97e752bc9..0c08df317b811 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts @@ -1,7 +1,7 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.2 import { UseQueryResult } from "@tanstack/react-query"; -import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagParsingService, DagReportService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagParsingService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; export type AssetServiceGetAssetsDefaultResponse = Awaited>; export type AssetServiceGetAssetsQueryResult = UseQueryResult; @@ -205,12 +205,6 @@ export const useDagStatsServiceGetDagStatsKey = "DagStatsServiceGetDagStats"; export const UseDagStatsServiceGetDagStatsKeyFn = ({ dagIds }: { dagIds?: string[]; } = {}, queryKey?: Array) => [useDagStatsServiceGetDagStatsKey, ...(queryKey ?? [{ dagIds }])]; -export type DagReportServiceGetDagReportsDefaultResponse = Awaited>; -export type DagReportServiceGetDagReportsQueryResult = UseQueryResult; -export const useDagReportServiceGetDagReportsKey = "DagReportServiceGetDagReports"; -export const UseDagReportServiceGetDagReportsKeyFn = ({ subdir }: { - subdir: string; -}, queryKey?: Array) => [useDagReportServiceGetDagReportsKey, ...(queryKey ?? [{ subdir }])]; export type ConfigServiceGetConfigDefaultResponse = Awaited>; export type ConfigServiceGetConfigQueryResult = UseQueryResult; export const useConfigServiceGetConfigKey = "ConfigServiceGetConfig"; diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts index 3208901e3ff38..9188e6a518474 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts @@ -1,7 +1,7 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.2 import { type QueryClient } from "@tanstack/react-query"; -import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagReportService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -384,17 +384,6 @@ export const ensureUseDagStatsServiceGetDagStatsData = (queryClient: QueryClient dagIds?: string[]; } = {}) => queryClient.ensureQueryData({ queryKey: Common.UseDagStatsServiceGetDagStatsKeyFn({ dagIds }), queryFn: () => DagStatsService.getDagStats({ dagIds }) }); /** -* Get Dag Reports -* Get DAG report. -* @param data The data for the request. -* @param data.subdir -* @returns unknown Successful Response -* @throws ApiError -*/ -export const ensureUseDagReportServiceGetDagReportsData = (queryClient: QueryClient, { subdir }: { - subdir: string; -}) => queryClient.ensureQueryData({ queryKey: Common.UseDagReportServiceGetDagReportsKeyFn({ subdir }), queryFn: () => DagReportService.getDagReports({ subdir }) }); -/** * Get Config * @param data The data for the request. * @param data.section diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts index 6b8755a48c844..46015f864029c 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts @@ -1,7 +1,7 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.2 import { type QueryClient } from "@tanstack/react-query"; -import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagReportService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -384,17 +384,6 @@ export const prefetchUseDagStatsServiceGetDagStats = (queryClient: QueryClient, dagIds?: string[]; } = {}) => queryClient.prefetchQuery({ queryKey: Common.UseDagStatsServiceGetDagStatsKeyFn({ dagIds }), queryFn: () => DagStatsService.getDagStats({ dagIds }) }); /** -* Get Dag Reports -* Get DAG report. -* @param data The data for the request. -* @param data.subdir -* @returns unknown Successful Response -* @throws ApiError -*/ -export const prefetchUseDagReportServiceGetDagReports = (queryClient: QueryClient, { subdir }: { - subdir: string; -}) => queryClient.prefetchQuery({ queryKey: Common.UseDagReportServiceGetDagReportsKeyFn({ subdir }), queryFn: () => DagReportService.getDagReports({ subdir }) }); -/** * Get Config * @param data The data for the request. * @param data.section diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts index 4e5ad32c71a5a..cf65f5e57a2db 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -1,7 +1,7 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.2 import { UseMutationOptions, UseQueryOptions, useMutation, useQuery } from "@tanstack/react-query"; -import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagParsingService, DagReportService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagParsingService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { BackfillPostBody, BulkBody_BulkTaskInstanceBody_, BulkBody_ConnectionBody_, BulkBody_PoolBody_, BulkBody_VariableBody_, ClearTaskInstancesBody, ConnectionBody, CreateAssetEventsBody, DAGPatchBody, DAGRunClearBody, DAGRunPatchBody, DAGRunsBatchBody, DagRunState, DagWarningType, PatchTaskInstanceBody, PoolBody, PoolPatchBody, TaskInstancesBatchBody, TriggerDAGRunPostBody, UpdateHITLDetailPayload, VariableBody, XComCreateBody, XComUpdateBody } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -384,17 +384,6 @@ export const useDagStatsServiceGetDagStats = , "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseDagStatsServiceGetDagStatsKeyFn({ dagIds }, queryKey), queryFn: () => DagStatsService.getDagStats({ dagIds }) as TData, ...options }); /** -* Get Dag Reports -* Get DAG report. -* @param data The data for the request. -* @param data.subdir -* @returns unknown Successful Response -* @throws ApiError -*/ -export const useDagReportServiceGetDagReports = = unknown[]>({ subdir }: { - subdir: string; -}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseDagReportServiceGetDagReportsKeyFn({ subdir }, queryKey), queryFn: () => DagReportService.getDagReports({ subdir }) as TData, ...options }); -/** * Get Config * @param data The data for the request. * @param data.section diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts index 3c3a61def574c..214a5b3ed8de0 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts @@ -1,7 +1,7 @@ // generated with @7nohe/openapi-react-query-codegen@1.6.2 import { UseQueryOptions, useSuspenseQuery } from "@tanstack/react-query"; -import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagReportService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; +import { AssetService, AuthLinksService, BackfillService, CalendarService, ConfigService, ConnectionService, DagRunService, DagService, DagSourceService, DagStatsService, DagVersionService, DagWarningService, DashboardService, DependenciesService, EventLogService, ExperimentalService, ExtraLinksService, GridService, ImportErrorService, JobService, LoginService, MonitorService, PluginService, PoolService, ProviderService, StructureService, TaskInstanceService, TaskService, VariableService, VersionService, XcomService } from "../requests/services.gen"; import { DagRunState, DagWarningType } from "../requests/types.gen"; import * as Common from "./common"; /** @@ -384,17 +384,6 @@ export const useDagStatsServiceGetDagStatsSuspense = , "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseDagStatsServiceGetDagStatsKeyFn({ dagIds }, queryKey), queryFn: () => DagStatsService.getDagStats({ dagIds }) as TData, ...options }); /** -* Get Dag Reports -* Get DAG report. -* @param data The data for the request. -* @param data.subdir -* @returns unknown Successful Response -* @throws ApiError -*/ -export const useDagReportServiceGetDagReportsSuspense = = unknown[]>({ subdir }: { - subdir: string; -}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseDagReportServiceGetDagReportsKeyFn({ subdir }, queryKey), queryFn: () => DagReportService.getDagReports({ subdir }) as TData, ...options }); -/** * Get Config * @param data The data for the request. * @param data.section diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts index 94cd799e7d605..cf8f1a731a462 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts @@ -3,7 +3,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetDagReportsData, GetDagReportsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutData, LogoutResponse, RefreshData, RefreshResponse, GetAuthMenusResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesData, GetGridTiSummariesResponse, GetCalendarData, GetCalendarResponse } from './types.gen'; +import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, DeleteDagAssetQueuedEventsData, DeleteDagAssetQueuedEventsResponse, GetDagAssetQueuedEventData, GetDagAssetQueuedEventResponse, DeleteDagAssetQueuedEventData, DeleteDagAssetQueuedEventResponse, NextRunAssetsData, NextRunAssetsResponse, ListBackfillsData, ListBackfillsResponse, CreateBackfillData, CreateBackfillResponse, GetBackfillData, GetBackfillResponse, PauseBackfillData, PauseBackfillResponse, UnpauseBackfillData, UnpauseBackfillResponse, CancelBackfillData, CancelBackfillResponse, CreateBackfillDryRunData, CreateBackfillDryRunResponse, ListBackfillsUiData, ListBackfillsUiResponse, DeleteConnectionData, DeleteConnectionResponse, GetConnectionData, GetConnectionResponse, PatchConnectionData, PatchConnectionResponse, GetConnectionsData, GetConnectionsResponse, PostConnectionData, PostConnectionResponse, BulkConnectionsData, BulkConnectionsResponse, TestConnectionData, TestConnectionResponse, CreateDefaultConnectionsResponse, HookMetaDataResponse, GetDagRunData, GetDagRunResponse, DeleteDagRunData, DeleteDagRunResponse, PatchDagRunData, PatchDagRunResponse, GetUpstreamAssetEventsData, GetUpstreamAssetEventsResponse, ClearDagRunData, ClearDagRunResponse, GetDagRunsData, GetDagRunsResponse, TriggerDagRunData, TriggerDagRunResponse, WaitDagRunUntilFinishedData, WaitDagRunUntilFinishedResponse, GetListDagRunsBatchData, GetListDagRunsBatchResponse, GetDagSourceData, GetDagSourceResponse, GetDagStatsData, GetDagStatsResponse, GetConfigData, GetConfigResponse, GetConfigValueData, GetConfigValueResponse, GetConfigsResponse, ListDagWarningsData, ListDagWarningsResponse, GetDagsData, GetDagsResponse, PatchDagsData, PatchDagsResponse, GetDagData, GetDagResponse, PatchDagData, PatchDagResponse, DeleteDagData, DeleteDagResponse, GetDagDetailsData, GetDagDetailsResponse, FavoriteDagData, FavoriteDagResponse, UnfavoriteDagData, UnfavoriteDagResponse, GetDagTagsData, GetDagTagsResponse, GetDagsUiData, GetDagsUiResponse, GetLatestRunInfoData, GetLatestRunInfoResponse, GetEventLogData, GetEventLogResponse, GetEventLogsData, GetEventLogsResponse, GetExtraLinksData, GetExtraLinksResponse, GetTaskInstanceData, GetTaskInstanceResponse, PatchTaskInstanceData, PatchTaskInstanceResponse, DeleteTaskInstanceData, DeleteTaskInstanceResponse, GetMappedTaskInstancesData, GetMappedTaskInstancesResponse, GetTaskInstanceDependenciesByMapIndexData, GetTaskInstanceDependenciesByMapIndexResponse, GetTaskInstanceDependenciesData, GetTaskInstanceDependenciesResponse, GetTaskInstanceTriesData, GetTaskInstanceTriesResponse, GetMappedTaskInstanceTriesData, GetMappedTaskInstanceTriesResponse, GetMappedTaskInstanceData, GetMappedTaskInstanceResponse, PatchTaskInstanceByMapIndexData, PatchTaskInstanceByMapIndexResponse, GetTaskInstancesData, GetTaskInstancesResponse, BulkTaskInstancesData, BulkTaskInstancesResponse, GetTaskInstancesBatchData, GetTaskInstancesBatchResponse, GetTaskInstanceTryDetailsData, GetTaskInstanceTryDetailsResponse, GetMappedTaskInstanceTryDetailsData, GetMappedTaskInstanceTryDetailsResponse, PostClearTaskInstancesData, PostClearTaskInstancesResponse, PatchTaskInstanceDryRunByMapIndexData, PatchTaskInstanceDryRunByMapIndexResponse, PatchTaskInstanceDryRunData, PatchTaskInstanceDryRunResponse, GetLogData, GetLogResponse, GetExternalLogUrlData, GetExternalLogUrlResponse, UpdateHitlDetailData, UpdateHitlDetailResponse, GetHitlDetailData, GetHitlDetailResponse, GetHitlDetailsData, GetHitlDetailsResponse, GetImportErrorData, GetImportErrorResponse, GetImportErrorsData, GetImportErrorsResponse, GetJobsData, GetJobsResponse, GetPluginsData, GetPluginsResponse, ImportErrorsResponse, DeletePoolData, DeletePoolResponse, GetPoolData, GetPoolResponse, PatchPoolData, PatchPoolResponse, GetPoolsData, GetPoolsResponse, PostPoolData, PostPoolResponse, BulkPoolsData, BulkPoolsResponse, GetProvidersData, GetProvidersResponse, GetXcomEntryData, GetXcomEntryResponse, UpdateXcomEntryData, UpdateXcomEntryResponse, GetXcomEntriesData, GetXcomEntriesResponse, CreateXcomEntryData, CreateXcomEntryResponse, GetTasksData, GetTasksResponse, GetTaskData, GetTaskResponse, DeleteVariableData, DeleteVariableResponse, GetVariableData, GetVariableResponse, PatchVariableData, PatchVariableResponse, GetVariablesData, GetVariablesResponse, PostVariableData, PostVariableResponse, BulkVariablesData, BulkVariablesResponse, ReparseDagFileData, ReparseDagFileResponse, GetDagVersionData, GetDagVersionResponse, GetDagVersionsData, GetDagVersionsResponse, GetHealthResponse, GetVersionResponse, LoginData, LoginResponse, LogoutData, LogoutResponse, RefreshData, RefreshResponse, GetAuthMenusResponse, GetDependenciesData, GetDependenciesResponse, HistoricalMetricsData, HistoricalMetricsResponse, DagStatsResponse2, StructureDataData, StructureDataResponse2, GetDagStructureData, GetDagStructureResponse, GetGridRunsData, GetGridRunsResponse, GetGridTiSummariesData, GetGridTiSummariesResponse, GetCalendarData, GetCalendarResponse } from './types.gen'; export class AssetService { /** @@ -1239,33 +1239,6 @@ export class DagStatsService { } -export class DagReportService { - /** - * Get Dag Reports - * Get DAG report. - * @param data The data for the request. - * @param data.subdir - * @returns unknown Successful Response - * @throws ApiError - */ - public static getDagReports(data: GetDagReportsData): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/v2/dagReports', - query: { - subdir: data.subdir - }, - errors: { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 422: 'Validation Error' - } - }); - } - -} - export class ConfigService { /** * Get Config diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts index 17deda05dae73..6b8306c70307d 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts @@ -2321,12 +2321,6 @@ export type GetDagStatsData = { export type GetDagStatsResponse = DagStatsCollectionResponse; -export type GetDagReportsData = { - subdir: string; -}; - -export type GetDagReportsResponse = unknown; - export type GetConfigData = { accept?: 'application/json' | 'text/plain' | '*/*'; section?: string | null; @@ -4358,33 +4352,6 @@ export type $OpenApiTs = { }; }; }; - '/api/v2/dagReports': { - get: { - req: GetDagReportsData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Bad Request - */ - 400: HTTPExceptionResponse; - /** - * Unauthorized - */ - 401: HTTPExceptionResponse; - /** - * Forbidden - */ - 403: HTTPExceptionResponse; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; '/api/v2/config': { get: { req: GetConfigData; diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_report.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_report.py deleted file mode 100644 index 3938894001de9..0000000000000 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_report.py +++ /dev/null @@ -1,135 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -import os -from unittest.mock import patch - -import pytest - -from airflow.utils.file import list_py_file_paths - -from tests_common.test_utils.config import conf_vars -from tests_common.test_utils.db import clear_db_dags, parse_and_sync_to_db - -pytestmark = pytest.mark.db_test - -TEST_DAG_FOLDER = os.environ["AIRFLOW__CORE__DAGS_FOLDER"] -TEST_DAG_FOLDER_WITH_SUBDIR = f"{TEST_DAG_FOLDER}/subdir2" -TEST_DAG_FOLDER_INVALID = "/invalid/path" -TEST_DAG_FOLDER_INVALID_2 = "/root/airflow/tests/dags/" - - -def get_corresponding_dag_file_count(dir: str, include_examples: bool = True) -> int: - from airflow import example_dags - - return len(list_py_file_paths(directory=dir)) + ( - len(list_py_file_paths(next(iter(example_dags.__path__)))) if include_examples else 0 - ) - - -class TestDagReportEndpoint: - @pytest.fixture(autouse=True) - def setup(self) -> None: - self.clear_db() - - def teardown_method(self) -> None: - self.clear_db() - - def clear_db(self): - clear_db_dags() - - @pytest.mark.parametrize( - "subdir,include_example,expected_total_entries", - [ - pytest.param( - TEST_DAG_FOLDER, - True, - get_corresponding_dag_file_count(TEST_DAG_FOLDER), - id="dag_path_with_example", - ), - pytest.param( - TEST_DAG_FOLDER, - False, - get_corresponding_dag_file_count(TEST_DAG_FOLDER, False), - id="dag_path_without_example", - ), - pytest.param( - TEST_DAG_FOLDER_WITH_SUBDIR, - True, - get_corresponding_dag_file_count(TEST_DAG_FOLDER_WITH_SUBDIR), - id="dag_path_subdir_with_example", - ), - pytest.param( - TEST_DAG_FOLDER_WITH_SUBDIR, - False, - get_corresponding_dag_file_count(TEST_DAG_FOLDER_WITH_SUBDIR, False), - id="dag_path_subdir_without_example", - ), - ], - ) - def test_should_response_200(self, test_client, subdir, include_example, expected_total_entries): - with conf_vars({("core", "load_examples"): str(include_example)}): - parse_and_sync_to_db(subdir, include_examples=include_example) - response = test_client.get("/dagReports", params={"subdir": subdir}) - assert response.status_code == 200 - response_json = response.json() - assert response_json["total_entries"] == expected_total_entries - - def test_should_response_200_with_empty_dagbag(self, test_client): - # the constructor of DagBag will call `collect_dags` method and store the result in `dagbag_stats` - def _mock_collect_dags(self, *args, **kwargs): - self.dagbag_stats = [] - - with patch("airflow.models.dagbag.DagBag.collect_dags", _mock_collect_dags): - response = test_client.get("/dagReports", params={"subdir": TEST_DAG_FOLDER}) - assert response.status_code == 200 - assert response.json() == {"dag_reports": [], "total_entries": 0} - - @pytest.mark.parametrize( - "subdir", - [ - pytest.param(TEST_DAG_FOLDER_INVALID, id="invalid_dag_path"), - pytest.param(TEST_DAG_FOLDER_INVALID_2, id="invalid_dag_path_2"), - ], - ) - def test_should_response_400(self, test_client, subdir): - response = test_client.get("/dagReports", params={"subdir": subdir}) - assert response.status_code == 400 - assert response.json() == {"detail": "subdir should be subpath of DAGS_FOLDER settings"} - - def test_should_response_422(self, test_client): - response = test_client.get("/dagReports") - assert response.status_code == 422 - assert response.json() == { - "detail": [ - { - "input": None, - "loc": ["query", "subdir"], - "msg": "Field required", - "type": "missing", - } - ] - } - - def test_should_respond_401(self, unauthenticated_test_client): - response = unauthenticated_test_client.get("/dagReports") - assert response.status_code == 401 - - def test_should_respond_403(self, unauthorized_test_client): - response = unauthorized_test_client.get("/dagReports") - assert response.status_code == 403