From 7f9846fd554669da8ee3f5ce1833109abfa211d7 Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Tue, 7 Oct 2025 18:06:10 +0200 Subject: [PATCH 1/6] Add Kubernetes Router extension point, add fetcher to custom objects provider Signed-off-by: Ilya Savich --- .changeset/full-needles-drive.md | 6 +++ plugins/kubernetes-backend/src/plugin.ts | 27 ++++++++++++ .../src/service/KubernetesInitializer.ts | 1 + .../src/service/KubernetesRouter.test.ts | 36 ++++++++++++++++ .../src/service/KubernetesRouter.ts | 24 +++++++++-- plugins/kubernetes-node/src/extensions.ts | 43 ++++++++++++++++++- 6 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 .changeset/full-needles-drive.md diff --git a/.changeset/full-needles-drive.md b/.changeset/full-needles-drive.md new file mode 100644 index 0000000000..d0d7a59551 --- /dev/null +++ b/.changeset/full-needles-drive.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-node': minor +--- + +Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index b4283d4914..e845abf39b 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -36,6 +36,9 @@ import { kubernetesObjectsProviderExtensionPoint, type KubernetesObjectsProviderExtensionPoint, KubernetesObjectsProviderFactory, + KubernetesRouterExtensionPoint, + kubernetesRouterExtensionPoint, + KubernetesRouterFactory, type KubernetesServiceLocator, kubernetesServiceLocatorExtensionPoint, type KubernetesServiceLocatorExtensionPoint, @@ -157,6 +160,24 @@ class AuthStrategy implements KubernetesAuthStrategyExtensionPoint { } } +class CustomRouter implements KubernetesRouterExtensionPoint { + private router: KubernetesRouterFactory | undefined; + + getRouter() { + return this.router; + } + + addRouter(router: KubernetesRouterFactory) { + if (this.router) { + throw new Error( + 'Multiple Kubernetes routers is not supported at this time', + ); + } + + this.router = router; + } +} + /** * This is the backend plugin that provides the Kubernetes integration. * @public @@ -169,6 +190,7 @@ export const kubernetesPlugin = createBackendPlugin({ const extPointAuthStrategy = new AuthStrategy(); const extPointFetcher = new Fetcher(); const extPointServiceLocator = new ServiceLocator(); + const extPointRouter = new CustomRouter() env.registerExtensionPoint( kubernetesObjectsProviderExtensionPoint, @@ -190,6 +212,10 @@ export const kubernetesPlugin = createBackendPlugin({ kubernetesServiceLocatorExtensionPoint, extPointServiceLocator, ); + env.registerExtensionPoint( + kubernetesRouterExtensionPoint, + extPointRouter, + ); env.registerInit({ deps: { @@ -247,6 +273,7 @@ export const kubernetesPlugin = createBackendPlugin({ clusterSupplier, serviceLocator, objectsProvider, + customRouter: extPointRouter.getRouter(), }); http.use(await router.getRouter()); diff --git a/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts b/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts index 46ce5fa888..a12f291ba2 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts @@ -209,6 +209,7 @@ export class KubernetesInitializer { customResources, objectTypesToFetch, }), + fetcher, clusterSupplier, serviceLocator, customResources, diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts index 4157cf9444..eb643704c0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts @@ -28,6 +28,7 @@ import { KubernetesFetcher, KubernetesServiceLocator, KubernetesCredential, + kubernetesRouterExtensionPoint, } from '@backstage/plugin-kubernetes-node'; import { HEADER_KUBERNETES_CLUSTER, @@ -809,4 +810,39 @@ metadata: 'Unsupported kubernetes.serviceLocatorMethod "unsupported"', ); }); + + it('custom router', async () => { + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testRouter', + register(env) { + env.registerInit({ + deps: { extension: kubernetesRouterExtensionPoint }, + async init({ extension }) { + extension.addRouter(({ getDefault }) => { + const router = getDefault(); + + router.get('/test', (_req, res) => { + res.json({ status: 'ok' }); + }); + + return router; + }); + }, + }); + }, + }), + ], + }); + app = server; + + const response = await request(app).get('/api/kubernetes/test'); + + expect(response.body).toEqual({ status: 'ok' }); + expect(response.status).toEqual(200); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts index 589e75a172..5aa4f9cebe 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts @@ -40,7 +40,7 @@ import { AuthMetadata, KubernetesClustersSupplier, KubernetesFetcher, - KubernetesObjectsProvider, + KubernetesObjectsProvider, KubernetesRouterFactory, KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; import { addResourceRoutesToRouter } from '../routes/resourcesRoutes'; @@ -62,6 +62,7 @@ export interface KubernetesEnvironment { clusterSupplier: KubernetesClustersSupplier; serviceLocator: KubernetesServiceLocator; objectsProvider: KubernetesObjectsProvider; + customRouter?: KubernetesRouterFactory; } export class KubernetesRouter { @@ -86,6 +87,7 @@ export class KubernetesRouter { catalog, discovery, httpAuth, + customRouter, } = this.env; logger.info('Initializing Kubernetes backend'); @@ -108,7 +110,23 @@ export class KubernetesRouter { authStrategyMap, ); - return this.buildRouter( + return customRouter?.({ + getDefault: () => this.buildDefaultRouter( + objectsProvider, + clusterSupplier, + catalog, + proxy, + permissions, + httpAuth, + authStrategyMap, + ), + objectsProvider, + clusterSupplier, + catalog, + permissions, + httpAuth, + authStrategyMap, + }) ?? this.buildDefaultRouter( objectsProvider, clusterSupplier, catalog, @@ -138,7 +156,7 @@ export class KubernetesRouter { }); } - private buildRouter( + private buildDefaultRouter( objectsProvider: KubernetesObjectsProvider, clusterSupplier: KubernetesClustersSupplier, catalog: CatalogService, diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts index 6f112ece23..d6b5037402 100644 --- a/plugins/kubernetes-node/src/extensions.ts +++ b/plugins/kubernetes-node/src/extensions.ts @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { + createExtensionPoint, + HttpAuthService, +} from '@backstage/backend-plugin-api'; import { AuthenticationStrategy, CustomResource, @@ -23,6 +26,9 @@ import { KubernetesObjectsProvider, KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; +import type express from 'express'; +import type { CatalogService } from '@backstage/plugin-catalog-node'; +import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; /** * A factory function for creating a KubernetesObjectsProvider. @@ -33,6 +39,7 @@ export type KubernetesObjectsProviderFactory = (opts: { getDefault: () => Promise; clusterSupplier: KubernetesClustersSupplier; serviceLocator: KubernetesServiceLocator; + fetcher: KubernetesFetcher; customResources: CustomResource[]; objectTypesToFetch?: ObjectToFetch[]; authStrategy: AuthenticationStrategy; @@ -168,3 +175,37 @@ export const kubernetesServiceLocatorExtensionPoint = createExtensionPoint({ id: 'kubernetes.service-locator', }); + +/** + * A factory function for creating a kubernetes router. + * + * @public + */ +export type KubernetesRouterFactory = (opts: { + getDefault: () => express.Router; + objectsProvider: KubernetesObjectsProvider; + clusterSupplier: KubernetesClustersSupplier; + catalog: CatalogService; + permissions: PermissionEvaluator; + httpAuth: HttpAuthService; + authStrategyMap: { [key: string]: AuthenticationStrategy }; +}) => express.Router; + +/** + * The interface for {@link kubernetesRouterExtensionPoint}. + * + * @public + */ +export interface KubernetesRouterExtensionPoint { + addRouter(router: KubernetesRouterFactory): void; +} + +/** + * An extension point the exposes the ability to configure a kubernetes service locator. + * + * @public + */ +export const kubernetesRouterExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.router', + }); From 62355e177dd25d3a5cdacd30545ee8fcf8fb7f37 Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Tue, 7 Oct 2025 19:12:46 +0200 Subject: [PATCH 2/6] fix build Signed-off-by: Ilya Savich --- plugins/kubernetes-backend/src/plugin.ts | 9 ++-- .../src/service/KubernetesRouter.ts | 41 +++++++++++-------- plugins/kubernetes-node/report.api.md | 27 ++++++++++++ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index e845abf39b..5a2a22f2b5 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -168,7 +168,7 @@ class CustomRouter implements KubernetesRouterExtensionPoint { } addRouter(router: KubernetesRouterFactory) { - if (this.router) { + if (this.router) { throw new Error( 'Multiple Kubernetes routers is not supported at this time', ); @@ -190,7 +190,7 @@ export const kubernetesPlugin = createBackendPlugin({ const extPointAuthStrategy = new AuthStrategy(); const extPointFetcher = new Fetcher(); const extPointServiceLocator = new ServiceLocator(); - const extPointRouter = new CustomRouter() + const extPointRouter = new CustomRouter(); env.registerExtensionPoint( kubernetesObjectsProviderExtensionPoint, @@ -212,10 +212,7 @@ export const kubernetesPlugin = createBackendPlugin({ kubernetesServiceLocatorExtensionPoint, extPointServiceLocator, ); - env.registerExtensionPoint( - kubernetesRouterExtensionPoint, - extPointRouter, - ); + env.registerExtensionPoint(kubernetesRouterExtensionPoint, extPointRouter); env.registerInit({ deps: { diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts index 5aa4f9cebe..9ca56c502e 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts @@ -40,7 +40,8 @@ import { AuthMetadata, KubernetesClustersSupplier, KubernetesFetcher, - KubernetesObjectsProvider, KubernetesRouterFactory, + KubernetesObjectsProvider, + KubernetesRouterFactory, KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; import { addResourceRoutesToRouter } from '../routes/resourcesRoutes'; @@ -110,8 +111,26 @@ export class KubernetesRouter { authStrategyMap, ); - return customRouter?.({ - getDefault: () => this.buildDefaultRouter( + return ( + customRouter?.({ + getDefault: () => + this.buildDefaultRouter( + objectsProvider, + clusterSupplier, + catalog, + proxy, + permissions, + httpAuth, + authStrategyMap, + ), + objectsProvider, + clusterSupplier, + catalog, + permissions, + httpAuth, + authStrategyMap, + }) ?? + this.buildDefaultRouter( objectsProvider, clusterSupplier, catalog, @@ -119,21 +138,7 @@ export class KubernetesRouter { permissions, httpAuth, authStrategyMap, - ), - objectsProvider, - clusterSupplier, - catalog, - permissions, - httpAuth, - authStrategyMap, - }) ?? this.buildDefaultRouter( - objectsProvider, - clusterSupplier, - catalog, - proxy, - permissions, - httpAuth, - authStrategyMap, + ) ); } diff --git a/plugins/kubernetes-node/report.api.md b/plugins/kubernetes-node/report.api.md index 2e96057be8..6e8a80a041 100644 --- a/plugins/kubernetes-node/report.api.md +++ b/plugins/kubernetes-node/report.api.md @@ -5,11 +5,14 @@ ```ts import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import type { CatalogService } from '@backstage/plugin-catalog-node'; import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node'; import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; import { Entity } from '@backstage/catalog-model'; +import type express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { FetchResponse } from '@backstage/plugin-kubernetes-common'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node'; import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node'; @@ -20,6 +23,7 @@ import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backsta import { LoggerService } from '@backstage/backend-plugin-api'; import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node'; +import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; // @public (undocumented) export interface AuthenticationStrategy { @@ -198,6 +202,7 @@ export type KubernetesObjectsProviderFactory = (opts: { getDefault: () => Promise; clusterSupplier: KubernetesClustersSupplier_2; serviceLocator: KubernetesServiceLocator_2; + fetcher: KubernetesFetcher_2; customResources: CustomResource_2[]; objectTypesToFetch?: ObjectToFetch_2[]; authStrategy: AuthenticationStrategy_2; @@ -221,6 +226,28 @@ export type KubernetesObjectTypes = | 'daemonsets' | 'secrets'; +// @public +export interface KubernetesRouterExtensionPoint { + // (undocumented) + addRouter(router: KubernetesRouterFactory): void; +} + +// @public +export const kubernetesRouterExtensionPoint: ExtensionPoint; + +// @public +export type KubernetesRouterFactory = (opts: { + getDefault: () => express.Router; + objectsProvider: KubernetesObjectsProvider_2; + clusterSupplier: KubernetesClustersSupplier_2; + catalog: CatalogService; + permissions: PermissionEvaluator; + httpAuth: HttpAuthService; + authStrategyMap: { + [key: string]: AuthenticationStrategy_2; + }; +}) => express.Router; + // @public export interface KubernetesServiceLocator { // (undocumented) From b0dfe347b9456911e3d4a1f698776fec939304df Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Wed, 8 Oct 2025 12:48:02 +0200 Subject: [PATCH 3/6] fix type deps Signed-off-by: Ilya Savich # Conflicts: # yarn.lock --- plugins/kubernetes-node/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index cadd27eabf..3f32b82864 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -52,6 +52,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", + "@types/express": "^4.17.6", "msw": "^1.3.1", "supertest": "^7.0.0" } From a393d69ea39479e6aafb80d8538f28623db410f7 Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Thu, 30 Oct 2025 10:41:47 +0100 Subject: [PATCH 4/6] fix type deps Signed-off-by: Ilya Savich --- plugins/kubernetes-node/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 3f32b82864..9ad3619456 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -43,6 +43,7 @@ "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/types": "workspace:^", "@kubernetes/client-node": "1.4.0", + "@types/express": "^4.17.6", "node-fetch": "^2.7.0", "winston": "^3.2.1" }, @@ -52,7 +53,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", - "@types/express": "^4.17.6", "msw": "^1.3.1", "supertest": "^7.0.0" } From 26845863b9703d449b2a7a37f05ee2f2e45cbaf9 Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Thu, 20 Nov 2025 13:32:21 +0100 Subject: [PATCH 5/6] fix rebase conflicts Signed-off-by: Ilya Savich --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index b1c15c04e8..c8020fa46e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5892,6 +5892,7 @@ __metadata: "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/types": "workspace:^" "@kubernetes/client-node": "npm:1.4.0" + "@types/express": "npm:^4.17.6" msw: "npm:^1.3.1" node-fetch: "npm:^2.7.0" supertest: "npm:^7.0.0" From 7c6530b036e7f036172c7fb05429f771b1ecf1c6 Mon Sep 17 00:00:00 2001 From: Ilya Savich Date: Fri, 5 Dec 2025 11:13:10 +0100 Subject: [PATCH 6/6] Remove trivial deps Signed-off-by: Ilya Savich --- .../kubernetes-backend/src/service/KubernetesRouter.ts | 3 --- plugins/kubernetes-node/report.api.md | 6 ------ plugins/kubernetes-node/src/extensions.ts | 10 +--------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts index 9ca56c502e..48842f562c 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts @@ -125,9 +125,6 @@ export class KubernetesRouter { ), objectsProvider, clusterSupplier, - catalog, - permissions, - httpAuth, authStrategyMap, }) ?? this.buildDefaultRouter( diff --git a/plugins/kubernetes-node/report.api.md b/plugins/kubernetes-node/report.api.md index 6e8a80a041..c23a0b5bb8 100644 --- a/plugins/kubernetes-node/report.api.md +++ b/plugins/kubernetes-node/report.api.md @@ -5,14 +5,12 @@ ```ts import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import type { CatalogService } from '@backstage/plugin-catalog-node'; import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node'; import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; import { Entity } from '@backstage/catalog-model'; import type express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { FetchResponse } from '@backstage/plugin-kubernetes-common'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node'; import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node'; @@ -23,7 +21,6 @@ import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backsta import { LoggerService } from '@backstage/backend-plugin-api'; import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node'; -import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; // @public (undocumented) export interface AuthenticationStrategy { @@ -240,9 +237,6 @@ export type KubernetesRouterFactory = (opts: { getDefault: () => express.Router; objectsProvider: KubernetesObjectsProvider_2; clusterSupplier: KubernetesClustersSupplier_2; - catalog: CatalogService; - permissions: PermissionEvaluator; - httpAuth: HttpAuthService; authStrategyMap: { [key: string]: AuthenticationStrategy_2; }; diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts index d6b5037402..50b529d05b 100644 --- a/plugins/kubernetes-node/src/extensions.ts +++ b/plugins/kubernetes-node/src/extensions.ts @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - createExtensionPoint, - HttpAuthService, -} from '@backstage/backend-plugin-api'; +import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { AuthenticationStrategy, CustomResource, @@ -27,8 +24,6 @@ import { KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; import type express from 'express'; -import type { CatalogService } from '@backstage/plugin-catalog-node'; -import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; /** * A factory function for creating a KubernetesObjectsProvider. @@ -185,9 +180,6 @@ export type KubernetesRouterFactory = (opts: { getDefault: () => express.Router; objectsProvider: KubernetesObjectsProvider; clusterSupplier: KubernetesClustersSupplier; - catalog: CatalogService; - permissions: PermissionEvaluator; - httpAuth: HttpAuthService; authStrategyMap: { [key: string]: AuthenticationStrategy }; }) => express.Router;