diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml index 5eb7aed77053..3a3afbab95dd 100644 --- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml +++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml @@ -1358,6 +1358,20 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' + /public/version/: + get: + tags: + - Version + summary: Get Version + description: Get version information. + operationId: get_version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/VersionInfo' components: schemas: AppBuilderMenuItemResponse: @@ -2574,3 +2588,19 @@ components: - value title: VariableResponse description: Variable serializer for responses. + VersionInfo: + properties: + version: + type: string + title: Version + git_version: + anyOf: + - type: string + - type: 'null' + title: Git Version + type: object + required: + - version + - git_version + title: VersionInfo + description: Version information serializer for responses. diff --git a/airflow/api_fastapi/core_api/routes/public/__init__.py b/airflow/api_fastapi/core_api/routes/public/__init__.py index 89d216e438f8..ab307409add0 100644 --- a/airflow/api_fastapi/core_api/routes/public/__init__.py +++ b/airflow/api_fastapi/core_api/routes/public/__init__.py @@ -26,6 +26,7 @@ from airflow.api_fastapi.core_api.routes.public.pools import pools_router from airflow.api_fastapi.core_api.routes.public.providers import providers_router from airflow.api_fastapi.core_api.routes.public.variables import variables_router +from airflow.api_fastapi.core_api.routes.public.version import version_router public_router = AirflowRouter(prefix="/public") @@ -38,3 +39,4 @@ public_router.include_router(pools_router) public_router.include_router(providers_router) public_router.include_router(plugins_router) +public_router.include_router(version_router) diff --git a/airflow/api_fastapi/core_api/routes/public/version.py b/airflow/api_fastapi/core_api/routes/public/version.py new file mode 100644 index 000000000000..218e0b90702d --- /dev/null +++ b/airflow/api_fastapi/core_api/routes/public/version.py @@ -0,0 +1,34 @@ +# 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 airflow +from airflow.api_fastapi.common.router import AirflowRouter +from airflow.api_fastapi.core_api.serializers.version import VersionInfo +from airflow.utils.platform import get_airflow_git_version + +version_router = AirflowRouter(tags=["Version"], prefix="/version") + + +@version_router.get("/") +async def get_version() -> VersionInfo: + """Get version information.""" + airflow_version = airflow.__version__ + git_version = get_airflow_git_version() + version_info = VersionInfo(version=airflow_version, git_version=git_version) + return VersionInfo.model_validate(version_info) diff --git a/airflow/api_fastapi/core_api/serializers/version.py b/airflow/api_fastapi/core_api/serializers/version.py new file mode 100644 index 000000000000..01c4c45376f7 --- /dev/null +++ b/airflow/api_fastapi/core_api/serializers/version.py @@ -0,0 +1,26 @@ +# 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 pydantic import BaseModel + + +class VersionInfo(BaseModel): + """Version information serializer for responses.""" + + version: str + git_version: str | None diff --git a/airflow/ui/openapi-gen/queries/common.ts b/airflow/ui/openapi-gen/queries/common.ts index c621a3fdb54a..b5e730822ffd 100644 --- a/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow/ui/openapi-gen/queries/common.ts @@ -12,6 +12,7 @@ import { PoolService, ProviderService, VariableService, + VersionService, } from "../requests/services.gen"; import { DagRunState } from "../requests/types.gen"; @@ -345,6 +346,18 @@ export const UsePluginServiceGetPluginsKeyFn = ( } = {}, queryKey?: Array, ) => [usePluginServiceGetPluginsKey, ...(queryKey ?? [{ limit, offset }])]; +export type VersionServiceGetVersionDefaultResponse = Awaited< + ReturnType +>; +export type VersionServiceGetVersionQueryResult< + TData = VersionServiceGetVersionDefaultResponse, + TError = unknown, +> = UseQueryResult; +export const useVersionServiceGetVersionKey = "VersionServiceGetVersion"; +export const UseVersionServiceGetVersionKeyFn = (queryKey?: Array) => [ + useVersionServiceGetVersionKey, + ...(queryKey ?? []), +]; export type VariableServicePostVariableMutationResult = Awaited< ReturnType >; diff --git a/airflow/ui/openapi-gen/queries/prefetch.ts b/airflow/ui/openapi-gen/queries/prefetch.ts index 36a6c251cb94..3f681a4a13b6 100644 --- a/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow/ui/openapi-gen/queries/prefetch.ts @@ -12,6 +12,7 @@ import { PoolService, ProviderService, VariableService, + VersionService, } from "../requests/services.gen"; import { DagRunState } from "../requests/types.gen"; import * as Common from "./common"; @@ -429,3 +430,14 @@ export const prefetchUsePluginServiceGetPlugins = ( queryKey: Common.UsePluginServiceGetPluginsKeyFn({ limit, offset }), queryFn: () => PluginService.getPlugins({ limit, offset }), }); +/** + * Get Version + * Get version information. + * @returns VersionInfo Successful Response + * @throws ApiError + */ +export const prefetchUseVersionServiceGetVersion = (queryClient: QueryClient) => + queryClient.prefetchQuery({ + queryKey: Common.UseVersionServiceGetVersionKeyFn(), + queryFn: () => VersionService.getVersion(), + }); diff --git a/airflow/ui/openapi-gen/queries/queries.ts b/airflow/ui/openapi-gen/queries/queries.ts index 7141ac00011d..31d9e94d6172 100644 --- a/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow/ui/openapi-gen/queries/queries.ts @@ -17,6 +17,7 @@ import { PoolService, ProviderService, VariableService, + VersionService, } from "../requests/services.gen"; import { DAGPatchBody, @@ -562,6 +563,25 @@ export const usePluginServiceGetPlugins = < queryFn: () => PluginService.getPlugins({ limit, offset }) as TData, ...options, }); +/** + * Get Version + * Get version information. + * @returns VersionInfo Successful Response + * @throws ApiError + */ +export const useVersionServiceGetVersion = < + TData = Common.VersionServiceGetVersionDefaultResponse, + TError = unknown, + TQueryKey extends Array = unknown[], +>( + queryKey?: TQueryKey, + options?: Omit, "queryKey" | "queryFn">, +) => + useQuery({ + queryKey: Common.UseVersionServiceGetVersionKeyFn(queryKey), + queryFn: () => VersionService.getVersion() as TData, + ...options, + }); /** * Post Variable * Create a variable. diff --git a/airflow/ui/openapi-gen/queries/suspense.ts b/airflow/ui/openapi-gen/queries/suspense.ts index 8fd858a985c4..eb91e8f1ba93 100644 --- a/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow/ui/openapi-gen/queries/suspense.ts @@ -12,6 +12,7 @@ import { PoolService, ProviderService, VariableService, + VersionService, } from "../requests/services.gen"; import { DagRunState } from "../requests/types.gen"; import * as Common from "./common"; @@ -552,3 +553,22 @@ export const usePluginServiceGetPluginsSuspense = < queryFn: () => PluginService.getPlugins({ limit, offset }) as TData, ...options, }); +/** + * Get Version + * Get version information. + * @returns VersionInfo Successful Response + * @throws ApiError + */ +export const useVersionServiceGetVersionSuspense = < + TData = Common.VersionServiceGetVersionDefaultResponse, + TError = unknown, + TQueryKey extends Array = unknown[], +>( + queryKey?: TQueryKey, + options?: Omit, "queryKey" | "queryFn">, +) => + useSuspenseQuery({ + queryKey: Common.UseVersionServiceGetVersionKeyFn(queryKey), + queryFn: () => VersionService.getVersion() as TData, + ...options, + }); diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow/ui/openapi-gen/requests/schemas.gen.ts index afd16d804091..2f3eb390b485 100644 --- a/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -1856,3 +1856,27 @@ export const $VariableResponse = { title: "VariableResponse", description: "Variable serializer for responses.", } as const; + +export const $VersionInfo = { + properties: { + version: { + type: "string", + title: "Version", + }, + git_version: { + anyOf: [ + { + type: "string", + }, + { + type: "null", + }, + ], + title: "Git Version", + }, + }, + type: "object", + required: ["version", "git_version"], + title: "VersionInfo", + description: "Version information serializer for responses.", +} as const; diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts index c4b0c987c432..b9d9f5265510 100644 --- a/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow/ui/openapi-gen/requests/services.gen.ts @@ -54,6 +54,7 @@ import type { GetProvidersResponse, GetPluginsData, GetPluginsResponse, + GetVersionResponse, } from "./types.gen"; export class AssetService { @@ -807,3 +808,18 @@ export class PluginService { }); } } + +export class VersionService { + /** + * Get Version + * Get version information. + * @returns VersionInfo Successful Response + * @throws ApiError + */ + public static getVersion(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/public/version/", + }); + } +} diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts index aef0f76f28e1..66f4437cec10 100644 --- a/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow/ui/openapi-gen/requests/types.gen.ts @@ -441,6 +441,14 @@ export type VariableResponse = { value: string | null; }; +/** + * Version information serializer for responses. + */ +export type VersionInfo = { + version: string; + git_version: string | null; +}; + export type NextRunAssetsData = { dagId: string; }; @@ -633,6 +641,8 @@ export type GetPluginsData = { export type GetPluginsResponse = PluginCollectionResponse; +export type GetVersionResponse = VersionInfo; + export type $OpenApiTs = { "/ui/next_run_assets/{dag_id}": { get: { @@ -1269,4 +1279,14 @@ export type $OpenApiTs = { }; }; }; + "/public/version/": { + get: { + res: { + /** + * Successful Response + */ + 200: VersionInfo; + }; + }; + }; }; diff --git a/tests/api_fastapi/core_api/routes/public/test_version.py b/tests/api_fastapi/core_api/routes/public/test_version.py new file mode 100644 index 000000000000..2000b03ccbf0 --- /dev/null +++ b/tests/api_fastapi/core_api/routes/public/test_version.py @@ -0,0 +1,51 @@ +# 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 unittest import mock + +import pytest + +from tests_common.test_utils.db import clear_db_jobs + +pytestmark = [pytest.mark.db_test, pytest.mark.skip_if_database_isolation_mode] + + +class TestVersionEndpoint: + @pytest.fixture(autouse=True) + def setup(self) -> None: + clear_db_jobs() + + def teardown_method(self): + clear_db_jobs() + + +class TestGetVersion(TestVersionEndpoint): + @mock.patch( + "airflow.api_fastapi.core_api.routes.public.version.airflow.__version__", + "MOCK_VERSION", + ) + @mock.patch( + "airflow.api_fastapi.core_api.routes.public.version.get_airflow_git_version", + return_value="GIT_COMMIT", + ) + def test_airflow_version_info(self, mock_get_airflow_get_commit, client): + response = client().get("/public/version") + + assert 200 == response.status_code + assert {"git_version": "GIT_COMMIT", "version": "MOCK_VERSION"} == response.json() + mock_get_airflow_get_commit.assert_called_once_with()