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', + });