diff --git a/.changeset/young-peas-unite.md b/.changeset/young-peas-unite.md new file mode 100644 index 0000000000..4f22742b24 --- /dev/null +++ b/.changeset/young-peas-unite.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Enable customization of services used by the kubernetes backend plugin + +The createRouter function has been deprecated in favor of a KubernetesBuilder object. +Here's how you should upgrade your projects when configuring the Kubernetes backend plugin. +in your `packages/backend/src/plugins/kubernetes.ts` file for instance: + +```typescript +import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { + const { router } = await KubernetesBuilder.createBuilder({ + logger, + config, + }).build(); + return router; +} +``` diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 36bb724be6..2e0d9dccb9 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -59,14 +59,18 @@ add the following: ```typescript // In packages/backend/src/plugins/kubernetes.ts -import { createRouter } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; import { PluginEnvironment } from '../types'; export default async function createPlugin({ logger, config, }: PluginEnvironment) { - return await createRouter({ logger, config }); + const { router } = await KubernetesBuilder.createBuilder({ + logger, + config, + }).build(); + return router; } ``` diff --git a/packages/backend/src/plugins/kubernetes.ts b/packages/backend/src/plugins/kubernetes.ts index a19520312f..6d40d89b66 100644 --- a/packages/backend/src/plugins/kubernetes.ts +++ b/packages/backend/src/plugins/kubernetes.ts @@ -14,12 +14,16 @@ * limitations under the License. */ -import { createRouter } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; import { PluginEnvironment } from '../types'; export default async function createPlugin({ logger, config, }: PluginEnvironment) { - return await createRouter({ logger, config }); + const { router } = await KubernetesBuilder.createBuilder({ + logger, + config, + }).build(); + return router; } diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 3d52c4ff43..e6fb2d5cce 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -3,13 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; import express from 'express'; import type { FetchResponse } from '@backstage/plugin-kubernetes-common'; import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger as Logger_2 } from 'winston'; +import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; // Warning: (ae-missing-release-tag) "AWSClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -40,7 +40,7 @@ export interface ClusterDetails { // Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public @deprecated export function createRouter(options: RouterOptions): Promise; // Warning: (ae-missing-release-tag) "CustomResource" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -71,6 +71,71 @@ export interface FetchResponseWrapper { // @public (undocumented) export interface GKEClusterDetails extends ClusterDetails {} +// Warning: (ae-missing-release-tag) "KubernetesBuilder" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class KubernetesBuilder { + constructor(env: KubernetesEnvironment); + // (undocumented) + build(): Promise<{ + clusterDetails: ClusterDetails[]; + clusterSupplier: KubernetesClustersSupplier; + customResources: CustomResource[]; + fetcher: KubernetesFetcher; + objectsProvider: KubernetesObjectsProvider; + router: express.Router; + serviceLocator: KubernetesServiceLocator; + }>; + // (undocumented) + protected buildClusterSupplier(): KubernetesClustersSupplier; + // (undocumented) + protected buildCustomResources(): CustomResource[]; + // (undocumented) + protected buildFetcher(): KubernetesFetcher; + // (undocumented) + protected buildHttpServiceLocator( + _clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + // (undocumented) + protected buildMultiTenantServiceLocator( + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + // (undocumented) + protected buildObjectsProvider( + options: KubernetesObjectsProviderOptions, + ): KubernetesObjectsProvider; + // (undocumented) + protected buildRouter( + objectsProvider: KubernetesObjectsProvider, + clusterDetails: ClusterDetails[], + ): express.Router; + // (undocumented) + protected buildServiceLocator( + method: ServiceLocatorMethod, + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + // (undocumented) + static createBuilder(env: KubernetesEnvironment): KubernetesBuilder; + // (undocumented) + protected readonly env: KubernetesEnvironment; + // (undocumented) + protected fetchClusterDetails( + clusterSupplier: KubernetesClustersSupplier, + ): Promise; + // (undocumented) + protected getObjectTypesToFetch(): ObjectToFetch[] | undefined; + // (undocumented) + protected getServiceLocatorMethod(): ServiceLocatorMethod; + // (undocumented) + setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier): this; + // (undocumented) + setFetcher(fetcher?: KubernetesFetcher): this; + // (undocumented) + setObjectsProvider(objectsProvider?: KubernetesObjectsProvider): this; + // (undocumented) + setServiceLocator(serviceLocator?: KubernetesServiceLocator): this; +} + // Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -79,6 +144,43 @@ export interface KubernetesClustersSupplier { getClusters(): Promise; } +// Warning: (ae-missing-release-tag) "KubernetesEnvironment" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface KubernetesEnvironment { + // (undocumented) + config: Config; + // (undocumented) + logger: Logger_2; +} + +// Warning: (ae-missing-release-tag) "KubernetesFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface KubernetesFactory { + // (undocumented) + createHttpServiceLocator( + _clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + // (undocumented) + createKubernetesClustersSupplier(config: Config): KubernetesClustersSupplier; + // (undocumented) + createKubernetesFetcher(logger: Logger_2): KubernetesFetcher; + // (undocumented) + createKubernetesObjectsProvider( + options: KubernetesObjectsProviderOptions, + ): KubernetesObjectsProvider; + // (undocumented) + createMultiTenantServiceLocator( + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + // (undocumented) + createServiceLocator( + method: ServiceLocatorMethod, + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; +} + // Warning: (ae-missing-release-tag) "KubernetesFetcher" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -89,6 +191,32 @@ export interface KubernetesFetcher { ): Promise; } +// Warning: (ae-missing-release-tag) "KubernetesObjectsProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface KubernetesObjectsProvider { + // (undocumented) + getKubernetesObjectsByEntity( + request: ObjectsByEntityRequest, + ): Promise; +} + +// Warning: (ae-missing-release-tag) "KubernetesObjectsProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface KubernetesObjectsProviderOptions { + // (undocumented) + customResources: CustomResource[]; + // (undocumented) + fetcher: KubernetesFetcher; + // (undocumented) + logger: Logger_2; + // (undocumented) + objectTypesToFetch?: ObjectToFetch[]; + // (undocumented) + serviceLocator: KubernetesServiceLocator; +} + // Warning: (ae-missing-release-tag) "KubernetesObjectTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -110,16 +238,6 @@ export interface KubernetesServiceLocator { getClustersByServiceId(serviceId: string): Promise; } -// Warning: (ae-forgotten-export) The symbol "KubernetesFanOutHandler" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "makeRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const makeRouter: ( - logger: Logger_2, - kubernetesFanOutHandler: KubernetesFanOutHandler, - clusterDetails: ClusterDetails[], -) => express.Router; - // Warning: (ae-missing-release-tag) "ObjectFetchParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -140,6 +258,11 @@ export interface ObjectFetchParams { serviceId: string; } +// Warning: (ae-missing-release-tag) "ObjectsByEntityRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ObjectsByEntityRequest = KubernetesRequestBody; + // Warning: (ae-missing-release-tag) "ObjectToFetch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/kubernetes-backend/src/index.ts b/plugins/kubernetes-backend/src/index.ts index 29818f805c..ac514db104 100644 --- a/plugins/kubernetes-backend/src/index.ts +++ b/plugins/kubernetes-backend/src/index.ts @@ -21,6 +21,7 @@ */ export * from './service/router'; +export * from './service/KubernetesBuilder'; export * from './types/types'; export { DEFAULT_OBJECTS } from './service/KubernetesFanOutHandler'; diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts similarity index 54% rename from plugins/kubernetes-backend/src/service/router.test.ts rename to plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index c70ae44406..1b90e65fd7 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -14,22 +14,32 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { getVoidLogger, loadBackendConfig } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; -import { makeRouter } from './router'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; +import { + ClusterDetails, + FetchResponseWrapper, + KubernetesClustersSupplier, + KubernetesFetcher, + KubernetesServiceLocator, + ObjectFetchParams, +} from '../types/types'; +import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; +import { KubernetesBuilder } from './KubernetesBuilder'; -describe('router', () => { +describe('KubernetesBuilder', () => { let app: express.Express; let kubernetesFanOutHandler: jest.Mocked; + let config: Config; beforeAll(async () => { - kubernetesFanOutHandler = { - getKubernetesObjectsByEntity: jest.fn(), - } as any; + const logger = getVoidLogger(); + config = await loadBackendConfig({ logger, argv: [] }); - const router = makeRouter(getVoidLogger(), kubernetesFanOutHandler, [ + const clusters: ClusterDetails[] = [ { name: 'some-cluster', authProvider: 'serviceAccount', @@ -41,7 +51,22 @@ describe('router', () => { url: 'https://localhost:1235', authProvider: 'google', }, - ]); + ]; + const clusterSupplier: KubernetesClustersSupplier = { + async getClusters() { + return clusters; + }, + }; + + kubernetesFanOutHandler = { + getKubernetesObjectsByEntity: jest.fn(), + } as any; + + const { router } = await KubernetesBuilder.createBuilder({ config, logger }) + .setObjectsProvider(kubernetesFanOutHandler) + .setClusterSupplier(clusterSupplier) + .build(); + app = express().use(router); }); @@ -130,5 +155,86 @@ describe('router', () => { expect(response.status).toEqual(500); expect(response.body).toEqual({ error: 'some internal error' }); }); + + it('custom service locator', async () => { + const logger = getVoidLogger(); + const someCluster: ClusterDetails = { + name: 'some-cluster', + authProvider: 'serviceAccount', + url: 'https://localhost:1234', + serviceAccountToken: 'someToken', + }; + const clusters: ClusterDetails[] = [ + someCluster, + { + name: 'some-other-cluster', + url: 'https://localhost:1235', + authProvider: 'google', + }, + ]; + const clusterSupplier: KubernetesClustersSupplier = { + async getClusters() { + return clusters; + }, + }; + const pod = { + metadata: { + name: 'pod1', + }, + }; + const result: ObjectsByEntityResponse = { + items: [ + { + cluster: { + name: someCluster.name, + }, + errors: [], + resources: [ + { + type: 'pods', + resources: [pod], + }, + ], + }, + ], + }; + + const serviceLocator: KubernetesServiceLocator = { + getClustersByServiceId(_serviceId: string): Promise { + return Promise.resolve([someCluster]); + }, + }; + + const fetcher: KubernetesFetcher = { + fetchObjectsForService( + _params: ObjectFetchParams, + ): Promise { + return Promise.resolve({ + errors: [], + responses: [ + { + type: 'pods', + resources: [pod], + }, + ], + }); + }, + }; + + const { router } = await KubernetesBuilder.createBuilder({ + logger, + config, + }) + .setClusterSupplier(clusterSupplier) + .setServiceLocator(serviceLocator) + .setFetcher(fetcher) + .build(); + app = express().use(router); + + const response = await request(app).post('/services/test-service'); + + expect(response.body).toEqual(result); + expect(response.status).toEqual(200); + }); }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts new file mode 100644 index 0000000000..bde538bf55 --- /dev/null +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -0,0 +1,254 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed 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. + */ +import { Config } from '@backstage/config'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import { getCombinedClusterDetails } from '../cluster-locator'; +import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator'; +import { + ClusterDetails, + KubernetesObjectTypes, + ServiceLocatorMethod, + CustomResource, + KubernetesObjectsProvider, + ObjectsByEntityRequest, + KubernetesClustersSupplier, + KubernetesFetcher, + KubernetesServiceLocator, + KubernetesObjectsProviderOptions, +} from '../types/types'; +import { KubernetesClientProvider } from './KubernetesClientProvider'; +import { + DEFAULT_OBJECTS, + KubernetesFanOutHandler, +} from './KubernetesFanOutHandler'; +import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; + +export interface KubernetesEnvironment { + logger: Logger; + config: Config; +} + +export class KubernetesBuilder { + private clusterSupplier?: KubernetesClustersSupplier; + private objectsProvider?: KubernetesObjectsProvider; + private fetcher?: KubernetesFetcher; + private serviceLocator?: KubernetesServiceLocator; + + static createBuilder(env: KubernetesEnvironment) { + return new KubernetesBuilder(env); + } + + constructor(protected readonly env: KubernetesEnvironment) {} + + public async build() { + const logger = this.env.logger; + + logger.info('Initializing Kubernetes backend'); + + const customResources = this.buildCustomResources(); + + const fetcher = this.fetcher ?? this.buildFetcher(); + + const clusterSupplier = this.clusterSupplier ?? this.buildClusterSupplier(); + + const clusterDetails = await this.fetchClusterDetails(clusterSupplier); + + const serviceLocator = + this.serviceLocator ?? + this.buildServiceLocator(this.getServiceLocatorMethod(), clusterDetails); + + const objectsProvider = + this.objectsProvider ?? + this.buildObjectsProvider({ + logger, + fetcher, + serviceLocator, + customResources, + objectTypesToFetch: this.getObjectTypesToFetch(), + }); + + const router = this.buildRouter(objectsProvider, clusterDetails); + + return { + clusterDetails, + clusterSupplier, + customResources, + fetcher, + objectsProvider, + router, + serviceLocator, + }; + } + + public setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier) { + this.clusterSupplier = clusterSupplier; + return this; + } + + public setObjectsProvider(objectsProvider?: KubernetesObjectsProvider) { + this.objectsProvider = objectsProvider; + return this; + } + + public setFetcher(fetcher?: KubernetesFetcher) { + this.fetcher = fetcher; + return this; + } + + public setServiceLocator(serviceLocator?: KubernetesServiceLocator) { + this.serviceLocator = serviceLocator; + return this; + } + + protected buildCustomResources() { + const customResources: CustomResource[] = ( + this.env.config.getOptionalConfigArray('kubernetes.customResources') ?? [] + ).map( + c => + ({ + group: c.getString('group'), + apiVersion: c.getString('apiVersion'), + plural: c.getString('plural'), + } as CustomResource), + ); + + this.env.logger.info( + `action=LoadingCustomResources numOfCustomResources=${customResources.length}`, + ); + return customResources; + } + + protected buildClusterSupplier(): KubernetesClustersSupplier { + const config = this.env.config; + return { + getClusters() { + return getCombinedClusterDetails(config); + }, + }; + } + + protected buildObjectsProvider( + options: KubernetesObjectsProviderOptions, + ): KubernetesObjectsProvider { + return new KubernetesFanOutHandler(options); + } + + protected buildFetcher(): KubernetesFetcher { + return new KubernetesClientBasedFetcher({ + kubernetesClientProvider: new KubernetesClientProvider(), + logger: this.env.logger, + }); + } + + protected buildServiceLocator( + method: ServiceLocatorMethod, + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator { + switch (method) { + case 'multiTenant': + return this.buildMultiTenantServiceLocator(clusterDetails); + case 'http': + return this.buildHttpServiceLocator(clusterDetails); + default: + throw new Error( + `Unsupported kubernetes.clusterLocatorMethod "${method}"`, + ); + } + } + + protected buildMultiTenantServiceLocator( + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator { + return new MultiTenantServiceLocator(clusterDetails); + } + + protected buildHttpServiceLocator( + _clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator { + throw new Error('not implemented'); + } + + protected buildRouter( + objectsProvider: KubernetesObjectsProvider, + clusterDetails: ClusterDetails[], + ): express.Router { + const logger = this.env.logger; + const router = Router(); + router.use(express.json()); + + router.post('/services/:serviceId', async (req, res) => { + const serviceId = req.params.serviceId; + const requestBody: ObjectsByEntityRequest = req.body; + try { + const response = await objectsProvider.getKubernetesObjectsByEntity( + requestBody, + ); + res.json(response); + } catch (e) { + logger.error( + `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`, + ); + res.status(500).json({ error: e.message }); + } + }); + + router.get('/clusters', async (_, res) => { + res.json({ + items: clusterDetails.map(cd => ({ + name: cd.name, + dashboardUrl: cd.dashboardUrl, + authProvider: cd.authProvider, + })), + }); + }); + return router; + } + + protected async fetchClusterDetails( + clusterSupplier: KubernetesClustersSupplier, + ) { + const clusterDetails = await clusterSupplier.getClusters(); + + this.env.logger.info( + `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`, + ); + + return clusterDetails; + } + + protected getServiceLocatorMethod() { + return this.env.config.getString( + 'kubernetes.serviceLocatorMethod.type', + ) as ServiceLocatorMethod; + } + + protected getObjectTypesToFetch() { + const objectTypesToFetchStrings = this.env.config.getOptionalStringArray( + 'kubernetes.objectTypes', + ) as KubernetesObjectTypes[]; + + let objectTypesToFetch; + + if (objectTypesToFetchStrings) { + objectTypesToFetch = DEFAULT_OBJECTS.filter(obj => + objectTypesToFetchStrings.includes(obj.objectType), + ); + } + return objectTypesToFetch; + } +} diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index d520c66d7a..1f856e90a9 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -19,15 +19,17 @@ import { ClusterDetails, CustomResource, KubernetesFetcher, + KubernetesObjectsProviderOptions, KubernetesServiceLocator, + ObjectsByEntityRequest, ObjectToFetch, } from '../types/types'; -import { - ClusterObjects, - KubernetesRequestBody, -} from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; +import { + ClusterObjects, + ObjectsByEntityResponse, +} from '@backstage/plugin-kubernetes-common'; export const DEFAULT_OBJECTS: ObjectToFetch[] = [ { @@ -74,13 +76,10 @@ export const DEFAULT_OBJECTS: ObjectToFetch[] = [ }, ]; -export interface KubernetesFanOutHandlerOptions { - logger: Logger; - fetcher: KubernetesFetcher; - serviceLocator: KubernetesServiceLocator; - customResources: CustomResource[]; - objectTypesToFetch?: ObjectToFetch[]; -} +export interface KubernetesFanOutHandlerOptions + extends KubernetesObjectsProviderOptions {} + +export interface KubernetesRequestBody extends ObjectsByEntityRequest {} export class KubernetesFanOutHandler { private readonly logger: Logger; @@ -103,7 +102,9 @@ export class KubernetesFanOutHandler { this.objectTypesToFetch = new Set(objectTypesToFetch); } - async getKubernetesObjectsByEntity(requestBody: KubernetesRequestBody) { + async getKubernetesObjectsByEntity( + requestBody: KubernetesRequestBody, + ): Promise { const entityName = requestBody.entity?.metadata?.annotations?.[ 'backstage.io/kubernetes-id' diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 3fc32dac8b..61082267be 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -15,26 +15,10 @@ */ import { Config } from '@backstage/config'; -import express from 'express'; -import Router from 'express-promise-router'; import { Logger } from 'winston'; -import { getCombinedClusterDetails } from '../cluster-locator'; -import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator'; -import { - ClusterDetails, - KubernetesClustersSupplier, - KubernetesObjectTypes, - KubernetesServiceLocator, - ServiceLocatorMethod, - CustomResource, -} from '../types/types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; -import { KubernetesClientProvider } from './KubernetesClientProvider'; -import { - KubernetesFanOutHandler, - DEFAULT_OBJECTS, -} from './KubernetesFanOutHandler'; -import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; +import { KubernetesClustersSupplier } from '../types/types'; +import express from 'express'; +import { KubernetesBuilder } from './KubernetesBuilder'; export interface RouterOptions { logger: Logger; @@ -42,121 +26,24 @@ export interface RouterOptions { clusterSupplier?: KubernetesClustersSupplier; } -const getServiceLocator = ( - config: Config, - clusterDetails: ClusterDetails[], -): KubernetesServiceLocator => { - const serviceLocatorMethod = config.getString( - 'kubernetes.serviceLocatorMethod.type', - ) as ServiceLocatorMethod; - - switch (serviceLocatorMethod) { - case 'multiTenant': - return new MultiTenantServiceLocator(clusterDetails); - case 'http': - throw new Error('not implemented'); - default: - throw new Error( - `Unsupported kubernetes.clusterLocatorMethod "${serviceLocatorMethod}"`, - ); - } -}; - -export const makeRouter = ( - logger: Logger, - kubernetesFanOutHandler: KubernetesFanOutHandler, - clusterDetails: ClusterDetails[], -): express.Router => { - const router = Router(); - router.use(express.json()); - - router.post('/services/:serviceId', async (req, res) => { - const serviceId = req.params.serviceId; - const requestBody: KubernetesRequestBody = req.body; - try { - const response = - await kubernetesFanOutHandler.getKubernetesObjectsByEntity(requestBody); - res.json(response); - } catch (e) { - logger.error( - `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`, - ); - res.status(500).json({ error: e.message }); - } - }); - - router.get('/clusters', async (_, res) => { - res.json({ - items: clusterDetails.map(cd => ({ - name: cd.name, - dashboardUrl: cd.dashboardUrl, - authProvider: cd.authProvider, - })), - }); - }); - return router; -}; - +/** + * creates and configure a new router for handling the kubernetes backend APIs + * @param options - specifies the options required by this plugin + * @returns a new router + * @deprecated Please use the new KubernetesBuilder instead like this + * ``` + * import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; + * const { router } = await KubernetesBuilder.createBuilder({ + * logger, + * config, + * }).build(); + * ``` + */ export async function createRouter( options: RouterOptions, ): Promise { - const logger = options.logger; - - logger.info('Initializing Kubernetes backend'); - - const customResources: CustomResource[] = ( - options.config.getOptionalConfigArray('kubernetes.customResources') ?? [] - ).map( - c => - ({ - group: c.getString('group'), - apiVersion: c.getString('apiVersion'), - plural: c.getString('plural'), - objectType: 'customresources', - } as CustomResource), - ); - - logger.info( - `action=LoadingCustomResources numOfCustomResources=${customResources.length}`, - ); - - const fetcher = new KubernetesClientBasedFetcher({ - kubernetesClientProvider: new KubernetesClientProvider(), - logger, - }); - - let clusterDetails: ClusterDetails[]; - - if (options.clusterSupplier) { - clusterDetails = await options.clusterSupplier.getClusters(); - } else { - clusterDetails = await getCombinedClusterDetails(options.config); - } - - logger.info( - `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`, - ); - - const serviceLocator = getServiceLocator(options.config, clusterDetails); - const objectTypesToFetchStrings = options.config.getOptionalStringArray( - 'kubernetes.objectTypes', - ) as KubernetesObjectTypes[]; - - let objectTypesToFetch; - - if (objectTypesToFetchStrings) { - objectTypesToFetch = DEFAULT_OBJECTS.filter(obj => - objectTypesToFetchStrings.includes(obj.objectType), - ); - } - - const kubernetesFanOutHandler = new KubernetesFanOutHandler({ - logger, - fetcher, - serviceLocator, - customResources, - objectTypesToFetch, - }); - - return makeRouter(logger, kubernetesFanOutHandler, clusterDetails); + const { router } = await KubernetesBuilder.createBuilder(options) + .setClusterSupplier(options.clusterSupplier) + .build(); + return router; } diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index cff87f265a..832a6a5bd5 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -14,9 +14,13 @@ * limitations under the License. */ +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; import type { FetchResponse, KubernetesFetchError, + KubernetesRequestBody, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; export interface ObjectFetchParams { @@ -121,3 +125,42 @@ export interface AWSClusterDetails extends ClusterDetails { assumeRole?: string; externalId?: string; } + +export interface KubernetesObjectsProviderOptions { + logger: Logger; + fetcher: KubernetesFetcher; + serviceLocator: KubernetesServiceLocator; + customResources: CustomResource[]; + objectTypesToFetch?: ObjectToFetch[]; +} + +export type ObjectsByEntityRequest = KubernetesRequestBody; + +export interface KubernetesObjectsProvider { + getKubernetesObjectsByEntity( + request: ObjectsByEntityRequest, + ): Promise; +} + +export interface KubernetesFactory { + createKubernetesClustersSupplier(config: Config): KubernetesClustersSupplier; + + createKubernetesObjectsProvider( + options: KubernetesObjectsProviderOptions, + ): KubernetesObjectsProvider; + + createKubernetesFetcher(logger: Logger): KubernetesFetcher; + + createServiceLocator( + method: ServiceLocatorMethod, + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + + createMultiTenantServiceLocator( + clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; + + createHttpServiceLocator( + _clusterDetails: ClusterDetails[], + ): KubernetesServiceLocator; +}