feat: last cleanup and fix tests
Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -22,7 +22,6 @@ import { catalogServiceRef } from '@backstage/plugin-catalog-node';
|
||||
|
||||
import {
|
||||
type AuthenticationStrategy,
|
||||
CustomResource,
|
||||
kubernetesAuthStrategyExtensionPoint,
|
||||
type KubernetesAuthStrategyExtensionPoint,
|
||||
type KubernetesClustersSupplier,
|
||||
@@ -37,25 +36,13 @@ import {
|
||||
kubernetesObjectsProviderExtensionPoint,
|
||||
type KubernetesObjectsProviderExtensionPoint,
|
||||
KubernetesObjectsProviderFactory,
|
||||
KubernetesObjectTypes,
|
||||
type KubernetesServiceLocator,
|
||||
kubernetesServiceLocatorExtensionPoint,
|
||||
type KubernetesServiceLocatorExtensionPoint,
|
||||
KubernetesServiceLocatorFactory,
|
||||
ObjectToFetch,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import { KubernetesBuilder } from './service/KubernetesBuilder';
|
||||
import { KubernetesClientBasedFetcher } from './service/KubernetesFetcher';
|
||||
import { DispatchStrategy } from './auth/DispatchStrategy';
|
||||
import { getCombinedClusterSupplier } from './cluster-locator';
|
||||
import { buildDefaultAuthStrategyMap } from './auth/buildDefaultAuthStrategyMap';
|
||||
import { buildDefaultServiceLocator } from './service-locator/buildDefaultServiceLocator';
|
||||
import { Duration } from 'luxon';
|
||||
import {
|
||||
ALL_OBJECTS,
|
||||
DEFAULT_OBJECTS,
|
||||
KubernetesFanOutHandler,
|
||||
} from './service/KubernetesFanOutHandler';
|
||||
import { KubernetesRouter } from './service/KubernetesRouter';
|
||||
import { KubernetesInitializer } from './service/KubernetnesInitializer';
|
||||
|
||||
class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint {
|
||||
private objectsProvider: KubernetesObjectsProviderFactory | undefined;
|
||||
@@ -151,20 +138,21 @@ class ServiceLocator implements KubernetesServiceLocatorExtensionPoint {
|
||||
}
|
||||
|
||||
class AuthStrategy implements KubernetesAuthStrategyExtensionPoint {
|
||||
private authStrategies: Map<string, AuthenticationStrategy>;
|
||||
|
||||
constructor() {
|
||||
this.authStrategies = new Map<string, AuthenticationStrategy>();
|
||||
}
|
||||
private authStrategies: Map<string, AuthenticationStrategy> | undefined;
|
||||
|
||||
getAuthenticationStrategies() {
|
||||
return this.authStrategies;
|
||||
}
|
||||
|
||||
addAuthStrategy(key: string, authStrategy: AuthenticationStrategy) {
|
||||
if (!this.authStrategies) {
|
||||
this.authStrategies = new Map<string, AuthenticationStrategy>();
|
||||
}
|
||||
|
||||
if (key.includes('-')) {
|
||||
throw new Error('Strategy name can not include dashes');
|
||||
}
|
||||
|
||||
this.authStrategies.set(key, authStrategy);
|
||||
}
|
||||
}
|
||||
@@ -226,121 +214,27 @@ export const kubernetesPlugin = createBackendPlugin({
|
||||
}) {
|
||||
// TODO: this could do with a cleanup and push some of this initalization somewhere else
|
||||
if (config.has('kubernetes')) {
|
||||
const defaultFetcherFactory = async () =>
|
||||
new KubernetesClientBasedFetcher({
|
||||
logger,
|
||||
});
|
||||
|
||||
const fetcher =
|
||||
extPointFetcher.getFetcher()?.({
|
||||
getDefault: defaultFetcherFactory,
|
||||
}) ?? defaultFetcherFactory();
|
||||
|
||||
const returnedAuthStrategyMap =
|
||||
extPointAuthStrategy.getAuthenticationStrategies();
|
||||
|
||||
const authStrategyMap =
|
||||
returnedAuthStrategyMap.size > 0
|
||||
? returnedAuthStrategyMap
|
||||
: buildDefaultAuthStrategyMap({ logger, config });
|
||||
|
||||
const refreshInterval = Duration.fromObject({
|
||||
minutes: 60,
|
||||
const initializer = KubernetesInitializer.create({
|
||||
logger,
|
||||
config,
|
||||
catalog,
|
||||
auth,
|
||||
fetcher: extPointFetcher.getFetcher(),
|
||||
clusterSupplier: extPointClusterSuplier.getClusterSupplier(),
|
||||
serviceLocator: extPointServiceLocator.getServiceLocator(),
|
||||
objectsProvider: extPointObjectsProvider.getObjectsProvider(),
|
||||
authStrategyMap: extPointAuthStrategy.getAuthenticationStrategies(),
|
||||
});
|
||||
|
||||
const defaultClusterSupplierFactory = async () =>
|
||||
getCombinedClusterSupplier(
|
||||
config,
|
||||
catalog,
|
||||
new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(authStrategyMap.entries()),
|
||||
}),
|
||||
logger,
|
||||
refreshInterval,
|
||||
auth,
|
||||
);
|
||||
const {
|
||||
fetcher,
|
||||
authStrategyMap,
|
||||
clusterSupplier,
|
||||
serviceLocator,
|
||||
objectsProvider,
|
||||
} = await initializer.init();
|
||||
|
||||
const clusterSupplier =
|
||||
extPointClusterSuplier.getClusterSupplier()?.({
|
||||
getDefault: defaultClusterSupplierFactory,
|
||||
}) ?? defaultClusterSupplierFactory();
|
||||
|
||||
const defaultServiceLocatorFactory = async () =>
|
||||
buildDefaultServiceLocator({
|
||||
config,
|
||||
clusterSupplier: await clusterSupplier,
|
||||
});
|
||||
|
||||
const serviceLocator =
|
||||
extPointServiceLocator.getServiceLocator()?.({
|
||||
getDefault: defaultServiceLocatorFactory,
|
||||
clusterSupplier: await clusterSupplier,
|
||||
}) ?? defaultServiceLocatorFactory();
|
||||
|
||||
const objectTypesToFetchStrings = config.getOptionalStringArray(
|
||||
'kubernetes.objectTypes',
|
||||
) as KubernetesObjectTypes[];
|
||||
|
||||
const apiVersionOverrides = config.getOptionalConfig(
|
||||
'kubernetes.apiVersionOverrides',
|
||||
);
|
||||
|
||||
let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;
|
||||
|
||||
if (objectTypesToFetchStrings) {
|
||||
objectTypesToFetch = ALL_OBJECTS.filter(obj =>
|
||||
objectTypesToFetchStrings.includes(obj.objectType),
|
||||
);
|
||||
}
|
||||
|
||||
if (apiVersionOverrides) {
|
||||
objectTypesToFetch ??= DEFAULT_OBJECTS;
|
||||
|
||||
for (const obj of objectTypesToFetch) {
|
||||
if (apiVersionOverrides.has(obj.objectType)) {
|
||||
obj.apiVersion = apiVersionOverrides.getString(obj.objectType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const customResources: CustomResource[] = (
|
||||
config.getOptionalConfigArray('kubernetes.customResources') ?? []
|
||||
).map(
|
||||
c =>
|
||||
({
|
||||
group: c.getString('group'),
|
||||
apiVersion: c.getString('apiVersion'),
|
||||
plural: c.getString('plural'),
|
||||
objectType: 'customresources',
|
||||
} as CustomResource),
|
||||
);
|
||||
|
||||
const defaultObjectsProviderFactory = async () =>
|
||||
new KubernetesFanOutHandler({
|
||||
logger,
|
||||
config,
|
||||
fetcher: await fetcher,
|
||||
serviceLocator: await serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch,
|
||||
authStrategy: new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(authStrategyMap.entries()),
|
||||
}),
|
||||
});
|
||||
|
||||
const objectsProvider =
|
||||
extPointObjectsProvider.getObjectsProvider()?.({
|
||||
clusterSupplier: await clusterSupplier,
|
||||
getDefault: defaultObjectsProviderFactory,
|
||||
serviceLocator: await serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch,
|
||||
authStrategy: new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(authStrategyMap.entries()),
|
||||
}),
|
||||
}) ?? defaultObjectsProviderFactory();
|
||||
|
||||
const builder: KubernetesBuilder = KubernetesBuilder.createBuilder({
|
||||
const router = KubernetesRouter.create({
|
||||
logger,
|
||||
config,
|
||||
catalog,
|
||||
@@ -349,14 +243,13 @@ export const kubernetesPlugin = createBackendPlugin({
|
||||
auth,
|
||||
httpAuth,
|
||||
authStrategyMap: Object.fromEntries(authStrategyMap.entries()),
|
||||
fetcher: await fetcher,
|
||||
clusterSupplier: await clusterSupplier,
|
||||
serviceLocator: await serviceLocator,
|
||||
objectsProvider: await objectsProvider,
|
||||
fetcher,
|
||||
clusterSupplier,
|
||||
serviceLocator,
|
||||
objectsProvider,
|
||||
});
|
||||
|
||||
const { router } = await builder.build();
|
||||
http.use(router);
|
||||
http.use(await router.getRouter());
|
||||
} else {
|
||||
logger.warn(
|
||||
'Failed to initialize kubernetes backend: valid kubernetes config is missing',
|
||||
|
||||
+6
-16
@@ -64,18 +64,14 @@ export interface KubernetesEnvironment {
|
||||
objectsProvider: KubernetesObjectsProvider;
|
||||
}
|
||||
|
||||
export type KubernetesBuilderReturn = Promise<{
|
||||
router: express.Router;
|
||||
}>;
|
||||
|
||||
export class KubernetesBuilder {
|
||||
static createBuilder(env: KubernetesEnvironment) {
|
||||
return new KubernetesBuilder(env);
|
||||
export class KubernetesRouter {
|
||||
static create(env: KubernetesEnvironment) {
|
||||
return new KubernetesRouter(env);
|
||||
}
|
||||
|
||||
constructor(protected readonly env: KubernetesEnvironment) {}
|
||||
|
||||
public async build(): KubernetesBuilderReturn {
|
||||
public async getRouter() {
|
||||
const {
|
||||
logger,
|
||||
config,
|
||||
@@ -97,9 +93,7 @@ export class KubernetesBuilder {
|
||||
logger.warn(
|
||||
'Failed to initialize kubernetes backend: kubernetes config is missing',
|
||||
);
|
||||
return {
|
||||
router: Router(),
|
||||
} as unknown as KubernetesBuilderReturn;
|
||||
return Router();
|
||||
}
|
||||
|
||||
const proxy = this.buildProxy(
|
||||
@@ -110,7 +104,7 @@ export class KubernetesBuilder {
|
||||
authStrategyMap,
|
||||
);
|
||||
|
||||
const router = this.buildRouter(
|
||||
return this.buildRouter(
|
||||
objectsProvider,
|
||||
clusterSupplier,
|
||||
catalog,
|
||||
@@ -119,10 +113,6 @@ export class KubernetesBuilder {
|
||||
httpAuth,
|
||||
authStrategyMap,
|
||||
);
|
||||
|
||||
return {
|
||||
router,
|
||||
};
|
||||
}
|
||||
|
||||
private buildProxy(
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
AuthenticationStrategy,
|
||||
CustomResource,
|
||||
KubernetesClustersSupplier,
|
||||
KubernetesClusterSupplierFactory,
|
||||
KubernetesFetcher,
|
||||
KubernetesFetcherFactory,
|
||||
KubernetesObjectsProviderFactory,
|
||||
KubernetesObjectTypes,
|
||||
KubernetesServiceLocator,
|
||||
KubernetesServiceLocatorFactory,
|
||||
ObjectToFetch,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import { KubernetesClientBasedFetcher } from './KubernetesFetcher';
|
||||
import {
|
||||
AuthService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { buildDefaultAuthStrategyMap } from '../auth/buildDefaultAuthStrategyMap';
|
||||
import { Duration } from 'luxon';
|
||||
import { getCombinedClusterSupplier } from '../cluster-locator';
|
||||
import { DispatchStrategy } from '../auth/DispatchStrategy';
|
||||
import { CatalogService } from '@backstage/plugin-catalog-node';
|
||||
import { buildDefaultServiceLocator } from '../service-locator/buildDefaultServiceLocator';
|
||||
import {
|
||||
ALL_OBJECTS,
|
||||
DEFAULT_OBJECTS,
|
||||
KubernetesFanOutHandler,
|
||||
} from './KubernetesFanOutHandler';
|
||||
|
||||
export class KubernetesInitializer {
|
||||
constructor(
|
||||
private readonly opts: {
|
||||
fetcher?: KubernetesFetcherFactory;
|
||||
authStrategyMap?: Map<string, AuthenticationStrategy>;
|
||||
clusterSupplier?: KubernetesClusterSupplierFactory;
|
||||
logger: LoggerService;
|
||||
config: RootConfigService;
|
||||
catalog: CatalogService;
|
||||
auth: AuthService;
|
||||
serviceLocator?: KubernetesServiceLocatorFactory;
|
||||
objectsProvider?: KubernetesObjectsProviderFactory;
|
||||
},
|
||||
) {}
|
||||
|
||||
static create(opts: {
|
||||
fetcher?: KubernetesFetcherFactory;
|
||||
clusterSupplier?: KubernetesClusterSupplierFactory;
|
||||
serviceLocator?: KubernetesServiceLocatorFactory;
|
||||
objectsProvider?: KubernetesObjectsProviderFactory;
|
||||
authStrategyMap?: Map<string, AuthenticationStrategy>;
|
||||
logger: LoggerService;
|
||||
config: RootConfigService;
|
||||
catalog: CatalogService;
|
||||
auth: AuthService;
|
||||
}) {
|
||||
return new KubernetesInitializer(opts);
|
||||
}
|
||||
|
||||
private async defaultFetcher() {
|
||||
return new KubernetesClientBasedFetcher({
|
||||
logger: this.opts.logger,
|
||||
});
|
||||
}
|
||||
|
||||
private async defaultAuthStrategy() {
|
||||
return buildDefaultAuthStrategyMap({
|
||||
logger: this.opts.logger,
|
||||
config: this.opts.config,
|
||||
});
|
||||
}
|
||||
|
||||
private async defaultClusterSupplier(opts: {
|
||||
authStrategyMap: Map<string, AuthenticationStrategy>;
|
||||
}) {
|
||||
const refreshInterval = Duration.fromObject({
|
||||
minutes: 60,
|
||||
});
|
||||
|
||||
return getCombinedClusterSupplier(
|
||||
this.opts.config,
|
||||
this.opts.catalog,
|
||||
new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),
|
||||
}),
|
||||
this.opts.logger,
|
||||
refreshInterval,
|
||||
this.opts.auth,
|
||||
);
|
||||
}
|
||||
|
||||
private async defaultServiceLocator(opts: {
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
}) {
|
||||
return buildDefaultServiceLocator({
|
||||
config: this.opts.config,
|
||||
clusterSupplier: opts.clusterSupplier,
|
||||
});
|
||||
}
|
||||
|
||||
private async defaultObjectsProvider(opts: {
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
authStrategyMap: Map<string, AuthenticationStrategy>;
|
||||
fetcher: KubernetesFetcher;
|
||||
serviceLocator: KubernetesServiceLocator;
|
||||
customResources: CustomResource[];
|
||||
objectTypesToFetch: ObjectToFetch[];
|
||||
}) {
|
||||
return new KubernetesFanOutHandler({
|
||||
logger: this.opts.logger,
|
||||
config: this.opts.config,
|
||||
fetcher: opts.fetcher,
|
||||
serviceLocator: opts.serviceLocator,
|
||||
customResources: opts.customResources,
|
||||
objectTypesToFetch: opts.objectTypesToFetch,
|
||||
authStrategy: new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private async defaultObjectsProviderOptions() {
|
||||
const customResources: CustomResource[] = (
|
||||
this.opts.config.getOptionalConfigArray('kubernetes.customResources') ??
|
||||
[]
|
||||
).map(
|
||||
c =>
|
||||
({
|
||||
group: c.getString('group'),
|
||||
apiVersion: c.getString('apiVersion'),
|
||||
plural: c.getString('plural'),
|
||||
objectType: 'customresources',
|
||||
} as CustomResource),
|
||||
);
|
||||
const objectTypesToFetchStrings = this.opts.config.getOptionalStringArray(
|
||||
'kubernetes.objectTypes',
|
||||
) as KubernetesObjectTypes[];
|
||||
|
||||
const apiVersionOverrides = this.opts.config.getOptionalConfig(
|
||||
'kubernetes.apiVersionOverrides',
|
||||
);
|
||||
|
||||
let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;
|
||||
|
||||
if (objectTypesToFetchStrings) {
|
||||
objectTypesToFetch = ALL_OBJECTS.filter(obj =>
|
||||
objectTypesToFetchStrings.includes(obj.objectType),
|
||||
);
|
||||
}
|
||||
|
||||
if (apiVersionOverrides) {
|
||||
objectTypesToFetch ??= DEFAULT_OBJECTS;
|
||||
|
||||
for (const obj of objectTypesToFetch) {
|
||||
if (apiVersionOverrides.has(obj.objectType)) {
|
||||
obj.apiVersion = apiVersionOverrides.getString(obj.objectType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
customResources,
|
||||
objectTypesToFetch: objectTypesToFetch ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
async init() {
|
||||
const fetcher =
|
||||
(await this.opts.fetcher?.({ getDefault: this.defaultFetcher })) ??
|
||||
(await this.defaultFetcher());
|
||||
|
||||
const authStrategyMap =
|
||||
this.opts.authStrategyMap ?? (await this.defaultAuthStrategy());
|
||||
|
||||
const clusterSupplier =
|
||||
(await this.opts.clusterSupplier?.({
|
||||
getDefault: () => this.defaultClusterSupplier({ authStrategyMap }),
|
||||
})) ?? (await this.defaultClusterSupplier({ authStrategyMap }));
|
||||
|
||||
const serviceLocator =
|
||||
(await this.opts.serviceLocator?.({
|
||||
getDefault: () => this.defaultServiceLocator({ clusterSupplier }),
|
||||
clusterSupplier,
|
||||
})) ?? (await this.defaultServiceLocator({ clusterSupplier }));
|
||||
|
||||
const { customResources, objectTypesToFetch } =
|
||||
await this.defaultObjectsProviderOptions();
|
||||
|
||||
const objectsProvider =
|
||||
(await this.opts.objectsProvider?.({
|
||||
getDefault: () =>
|
||||
this.defaultObjectsProvider({
|
||||
clusterSupplier,
|
||||
authStrategyMap,
|
||||
fetcher,
|
||||
serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch,
|
||||
}),
|
||||
clusterSupplier,
|
||||
serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch,
|
||||
authStrategy: new DispatchStrategy({
|
||||
authStrategyMap: Object.fromEntries(authStrategyMap.entries()),
|
||||
}),
|
||||
})) ??
|
||||
(await this.defaultObjectsProvider({
|
||||
clusterSupplier,
|
||||
authStrategyMap,
|
||||
fetcher,
|
||||
serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch,
|
||||
}));
|
||||
|
||||
return {
|
||||
fetcher,
|
||||
authStrategyMap,
|
||||
clusterSupplier,
|
||||
serviceLocator,
|
||||
objectsProvider,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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 { Logger } from 'winston';
|
||||
import {
|
||||
AuthenticationStrategy,
|
||||
KubernetesClustersSupplier,
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectsProvider,
|
||||
KubernetesServiceLocator,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import express from 'express';
|
||||
import { KubernetesBuilder } from './KubernetesBuilder';
|
||||
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
RootConfigService,
|
||||
AuthService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogService } from '@backstage/plugin-catalog-node';
|
||||
|
||||
interface RouterOptions {
|
||||
logger: Logger;
|
||||
config: RootConfigService;
|
||||
catalog: CatalogService;
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
discovery: DiscoveryService;
|
||||
permissions: PermissionEvaluator;
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
authStrategyMap: { [key: string]: AuthenticationStrategy };
|
||||
fetcher: KubernetesFetcher;
|
||||
serviceLocator: KubernetesServiceLocator;
|
||||
objectsProvider: KubernetesObjectsProvider;
|
||||
}
|
||||
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { router } = await KubernetesBuilder.createBuilder(options).build();
|
||||
return router;
|
||||
}
|
||||
Reference in New Issue
Block a user