Merge pull request #7373 from livetocode/feature/k8s-add-factory
Add a factory class for customizing the services used by the kubernetes backend plugin
This commit is contained in:
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<express.Router>;
|
||||
|
||||
// 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<ClusterDetails[]>;
|
||||
// (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,16 @@ export interface KubernetesClustersSupplier {
|
||||
getClusters(): Promise<ClusterDetails[]>;
|
||||
}
|
||||
|
||||
// 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) "KubernetesFetcher" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -89,6 +164,32 @@ export interface KubernetesFetcher {
|
||||
): Promise<FetchResponseWrapper>;
|
||||
}
|
||||
|
||||
// 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<ObjectsByEntityResponse>;
|
||||
}
|
||||
|
||||
// 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 +211,6 @@ export interface KubernetesServiceLocator {
|
||||
getClustersByServiceId(serviceId: string): Promise<ClusterDetails[]>;
|
||||
}
|
||||
|
||||
// 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 +231,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)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
export * from './service/router';
|
||||
export * from './service/KubernetesBuilder';
|
||||
export * from './types/types';
|
||||
|
||||
export { DEFAULT_OBJECTS } from './service/KubernetesFanOutHandler';
|
||||
|
||||
+114
-8
@@ -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<KubernetesFanOutHandler>;
|
||||
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<ClusterDetails[]> {
|
||||
return Promise.resolve([someCluster]);
|
||||
},
|
||||
};
|
||||
|
||||
const fetcher: KubernetesFetcher = {
|
||||
fetchObjectsForService(
|
||||
_params: ObjectFetchParams,
|
||||
): Promise<FetchResponseWrapper> {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ObjectsByEntityResponse> {
|
||||
const entityName =
|
||||
requestBody.entity?.metadata?.annotations?.[
|
||||
'backstage.io/kubernetes-id'
|
||||
|
||||
@@ -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<express.Router> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import type {
|
||||
FetchResponse,
|
||||
KubernetesFetchError,
|
||||
KubernetesRequestBody,
|
||||
ObjectsByEntityResponse,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
export interface ObjectFetchParams {
|
||||
@@ -121,3 +124,19 @@ 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<ObjectsByEntityResponse>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user