Merge pull request #21590 from andmagom/kubernetes/newExtensionPoint

New ExtensionPoints for Kubernetes Plugin
This commit is contained in:
Ben Lambert
2023-12-18 12:19:57 +01:00
committed by GitHub
17 changed files with 1222 additions and 626 deletions
+12
View File
@@ -0,0 +1,12 @@
---
'@backstage/plugin-kubernetes-node': patch
'@backstage/plugin-kubernetes-backend': patch
---
The `kubernetes-node` plugin has been modified to house a new extension points for Kubernetes backend plugin;
`KubernetesClusterSupplierExtensionPoint` is introduced .
`kubernetesAuthStrategyExtensionPoint` is introduced .
`kubernetesFetcherExtensionPoint` is introduced .
`kubernetesServiceLocatorExtensionPoint` is introduced .
The `kubernetes-backend` plugin was modified to use this new extension point.
+65 -169
View File
@@ -3,21 +3,20 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node';
import { CatalogApi } from '@backstage/catalog-client';
import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node';
import { Config } from '@backstage/config';
import type { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common';
import { CustomResourcesByEntity } from '@backstage/plugin-kubernetes-node';
import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node';
import { Duration } from 'luxon';
import { Entity } from '@backstage/catalog-model';
import express from 'express';
import type { FetchResponse } from '@backstage/plugin-kubernetes-common';
import type { JsonObject } from '@backstage/types';
import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common';
import { KubernetesObjectsByEntity } from '@backstage/plugin-kubernetes-node';
import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node';
import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node';
import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { Logger } from 'winston';
import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { RequestHandler } from 'http-proxy-middleware';
@@ -42,19 +41,11 @@ export class AnonymousStrategy implements AuthenticationStrategy {
validateCluster(): Error[];
}
// @public (undocumented)
export interface AuthenticationStrategy {
// (undocumented)
getCredential(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
// (undocumented)
validateCluster(authMetadata: AuthMetadata): Error[];
}
// @public @deprecated (undocumented)
export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy;
// @public
export type AuthMetadata = Record<string, string>;
// @public @deprecated (undocumented)
export type AuthMetadata = k8sAuthTypes.AuthMetadata;
// @public (undocumented)
export class AwsIamStrategy implements AuthenticationStrategy {
@@ -74,36 +65,17 @@ export class AzureIdentityStrategy implements AuthenticationStrategy {
validateCluster(): Error[];
}
// @public (undocumented)
export interface ClusterDetails {
// (undocumented)
authMetadata: AuthMetadata;
// (undocumented)
caData?: string | undefined;
// (undocumented)
caFile?: string | undefined;
customResources?: CustomResourceMatcher[];
dashboardApp?: string;
dashboardParameters?: JsonObject;
dashboardUrl?: string;
name: string;
skipMetricsLookup?: boolean;
// (undocumented)
skipTLSVerify?: boolean;
// (undocumented)
url: string;
}
// @public @deprecated (undocumented)
export type ClusterDetails = k8sAuthTypes.ClusterDetails;
// @public @deprecated
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public (undocumented)
export interface CustomResource extends ObjectToFetch {
// (undocumented)
objectType: 'customresources';
}
// @public @deprecated (undocumented)
export type CustomResource = k8sAuthTypes.CustomResource;
export { CustomResourcesByEntity };
// @public @deprecated (undocumented)
export type CustomResourcesByEntity = k8sAuthTypes.CustomResourcesByEntity;
// @public (undocumented)
export const DEFAULT_OBJECTS: ObjectToFetch[];
@@ -127,13 +99,8 @@ export type DispatchStrategyOptions = {
};
};
// @public (undocumented)
export interface FetchResponseWrapper {
// (undocumented)
errors: KubernetesFetchError[];
// (undocumented)
responses: FetchResponse[];
}
// @public @deprecated (undocumented)
export type FetchResponseWrapper = k8sAuthTypes.FetchResponseWrapper;
// @public (undocumented)
export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
@@ -169,37 +136,37 @@ export class KubernetesBuilder {
build(): KubernetesBuilderReturn;
// (undocumented)
protected buildAuthStrategyMap(): {
[key: string]: AuthenticationStrategy;
[key: string]: AuthenticationStrategy_2;
};
// (undocumented)
protected buildClusterSupplier(
refreshInterval: Duration,
): KubernetesClustersSupplier;
): KubernetesClustersSupplier_2;
// (undocumented)
protected buildCustomResources(): CustomResource[];
protected buildCustomResources(): CustomResource_2[];
// (undocumented)
protected buildFetcher(): KubernetesFetcher;
// (undocumented)
protected buildHttpServiceLocator(
_clusterSupplier: KubernetesClustersSupplier,
_clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
// (undocumented)
protected buildMultiTenantServiceLocator(
clusterSupplier: KubernetesClustersSupplier,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
// (undocumented)
protected buildObjectsProvider(
options: KubernetesObjectsProviderOptions,
): KubernetesObjectsProvider;
): KubernetesObjectsProvider_2;
// (undocumented)
protected buildProxy(
logger: Logger,
clusterSupplier: KubernetesClustersSupplier,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesProxy;
// (undocumented)
protected buildRouter(
objectsProvider: KubernetesObjectsProvider,
clusterSupplier: KubernetesClustersSupplier,
objectsProvider: KubernetesObjectsProvider_2,
clusterSupplier: KubernetesClustersSupplier_2,
catalogApi: CatalogApi,
proxy: KubernetesProxy,
permissionApi: PermissionEvaluator,
@@ -207,11 +174,11 @@ export class KubernetesBuilder {
// (undocumented)
protected buildServiceLocator(
method: ServiceLocatorMethod,
clusterSupplier: KubernetesClustersSupplier,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
// (undocumented)
protected buildSingleTenantServiceLocator(
clusterSupplier: KubernetesClustersSupplier,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
// (undocumented)
static createBuilder(env: KubernetesEnvironment): KubernetesBuilder;
@@ -219,26 +186,26 @@ export class KubernetesBuilder {
protected readonly env: KubernetesEnvironment;
// (undocumented)
protected fetchClusterDetails(
clusterSupplier: KubernetesClustersSupplier,
): Promise<ClusterDetails[]>;
clusterSupplier: KubernetesClustersSupplier_2,
): Promise<ClusterDetails_2[]>;
// (undocumented)
protected getAuthStrategyMap(): {
[key: string]: AuthenticationStrategy;
[key: string]: AuthenticationStrategy_2;
};
// (undocumented)
protected getClusterSupplier(): KubernetesClustersSupplier;
protected getClusterSupplier(): KubernetesClustersSupplier_2;
// (undocumented)
protected getFetcher(): KubernetesFetcher;
// (undocumented)
protected getObjectsProvider(
options: KubernetesObjectsProviderOptions,
): KubernetesObjectsProvider;
): KubernetesObjectsProvider_2;
// (undocumented)
protected getObjectTypesToFetch(): ObjectToFetch[] | undefined;
protected getObjectTypesToFetch(): ObjectToFetch_2[] | undefined;
// (undocumented)
protected getProxy(
logger: Logger,
clusterSupplier: KubernetesClustersSupplier,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesProxy;
// (undocumented)
protected getServiceLocator(): KubernetesServiceLocator;
@@ -249,13 +216,13 @@ export class KubernetesBuilder {
[key: string]: AuthenticationStrategy;
}): void;
// (undocumented)
setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier): this;
setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier_2): this;
// (undocumented)
setDefaultClusterRefreshInterval(refreshInterval: Duration): this;
// (undocumented)
setFetcher(fetcher?: KubernetesFetcher): this;
// (undocumented)
setObjectsProvider(objectsProvider?: KubernetesObjectsProvider): this;
setObjectsProvider(objectsProvider?: KubernetesObjectsProvider_2): this;
// (undocumented)
setProxy(proxy?: KubernetesProxy): this;
// (undocumented)
@@ -265,31 +232,23 @@ export class KubernetesBuilder {
// @public
export type KubernetesBuilderReturn = Promise<{
router: express.Router;
clusterSupplier: KubernetesClustersSupplier;
clusterSupplier: KubernetesClustersSupplier_2;
customResources: CustomResource[];
fetcher: KubernetesFetcher;
proxy: KubernetesProxy;
objectsProvider: KubernetesObjectsProvider;
objectsProvider: KubernetesObjectsProvider_2;
serviceLocator: KubernetesServiceLocator;
authStrategyMap: {
[key: string]: AuthenticationStrategy;
};
}>;
// @public
export interface KubernetesClustersSupplier {
getClusters(): Promise<ClusterDetails[]>;
}
// @public @deprecated (undocumented)
export type KubernetesClustersSupplier =
k8sAuthTypes.KubernetesClustersSupplier;
// @public
export type KubernetesCredential =
| {
type: 'bearer token';
token: string;
}
| {
type: 'anonymous';
};
// @public @deprecated (undocumented)
export type KubernetesCredential = k8sAuthTypes.KubernetesCredential;
// @public (undocumented)
export interface KubernetesEnvironment {
@@ -303,57 +262,30 @@ export interface KubernetesEnvironment {
permissions: PermissionEvaluator;
}
// @public
export interface KubernetesFetcher {
// (undocumented)
fetchObjectsForService(
params: ObjectFetchParams,
): Promise<FetchResponseWrapper>;
// (undocumented)
fetchPodMetricsByNamespaces(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
namespaces: Set<string>,
labelSelector?: string,
): Promise<FetchResponseWrapper>;
}
// @public @deprecated (undocumented)
export type KubernetesFetcher = k8sAuthTypes.KubernetesFetcher;
export { KubernetesObjectsByEntity };
export { KubernetesObjectsProvider };
// @public @deprecated (undocumented)
export type KubernetesObjectsProvider = k8sAuthTypes.KubernetesObjectsProvider;
// @public (undocumented)
export interface KubernetesObjectsProviderOptions {
// (undocumented)
config: Config;
// (undocumented)
customResources: CustomResource[];
customResources: k8sAuthTypes.CustomResource[];
// (undocumented)
fetcher: KubernetesFetcher;
fetcher: k8sAuthTypes.KubernetesFetcher;
// (undocumented)
logger: Logger;
// (undocumented)
objectTypesToFetch?: ObjectToFetch[];
objectTypesToFetch?: k8sAuthTypes.ObjectToFetch[];
// (undocumented)
serviceLocator: KubernetesServiceLocator;
serviceLocator: k8sAuthTypes.KubernetesServiceLocator;
}
// @public (undocumented)
export type KubernetesObjectTypes =
| 'pods'
| 'services'
| 'configmaps'
| 'deployments'
| 'limitranges'
| 'resourcequotas'
| 'replicasets'
| 'horizontalpodautoscalers'
| 'jobs'
| 'cronjobs'
| 'ingresses'
| 'customresources'
| 'statefulsets'
| 'daemonsets';
// @public @deprecated (undocumented)
export type KubernetesObjectTypes = k8sAuthTypes.KubernetesObjectTypes;
// @public
export class KubernetesProxy {
@@ -376,49 +308,17 @@ export type KubernetesProxyOptions = {
authStrategy: AuthenticationStrategy;
};
// @public
export interface KubernetesServiceLocator {
// (undocumented)
getClustersByEntity(
entity: Entity,
requestContext: ServiceLocatorRequestContext,
): Promise<{
clusters: ClusterDetails[];
}>;
}
// @public @deprecated (undocumented)
export type KubernetesServiceLocator = k8sAuthTypes.KubernetesServiceLocator;
// @public (undocumented)
export interface ObjectFetchParams {
// (undocumented)
clusterDetails: ClusterDetails;
// (undocumented)
credential: KubernetesCredential;
// (undocumented)
customResources: CustomResource[];
// (undocumented)
labelSelector?: string;
// (undocumented)
namespace?: string;
// (undocumented)
objectTypesToFetch: Set<ObjectToFetch>;
// (undocumented)
serviceId: string;
}
// @public @deprecated (undocumented)
export type ObjectFetchParams = k8sAuthTypes.ObjectFetchParams;
// @public (undocumented)
export type ObjectsByEntityRequest = KubernetesRequestBody;
// @public (undocumented)
export interface ObjectToFetch {
// (undocumented)
apiVersion: string;
// (undocumented)
group: string;
// (undocumented)
objectType: KubernetesObjectTypes;
// (undocumented)
plural: string;
}
// @public @deprecated (undocumented)
export type ObjectToFetch = k8sAuthTypes.ObjectToFetch;
// @public (undocumented)
export class OidcStrategy implements AuthenticationStrategy {
@@ -458,13 +358,9 @@ export class ServiceAccountStrategy implements AuthenticationStrategy {
// @public (undocumented)
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http';
// @public (undocumented)
export interface ServiceLocatorRequestContext {
// (undocumented)
customResources: CustomResourceMatcher[];
// (undocumented)
objectTypesToFetch: Set<ObjectToFetch>;
}
// @public @deprecated (undocumented)
export type ServiceLocatorRequestContext =
k8sAuthTypes.ServiceLocatorRequestContext;
// @public (undocumented)
export type SigningCreds = {
+2
View File
@@ -90,6 +90,8 @@
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
"@types/aws4": "^1.5.1",
"msw": "^1.0.0",
"supertest": "^6.1.3",
+9 -19
View File
@@ -14,25 +14,15 @@
* limitations under the License.
*/
import { AuthMetadata, ClusterDetails } from '../types/types';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node';
// TODO remove this re-export as a breaking change after a couple of releases
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy;
/**
* Authentication data used to make a request to Kubernetes
* @public
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesCredential =
| { type: 'bearer token'; token: string }
| { type: 'anonymous' };
/**
*
* @public
*/
export interface AuthenticationStrategy {
getCredential(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
validateCluster(authMetadata: AuthMetadata): Error[];
}
export type KubernetesCredential = k8sAuthTypes.KubernetesCredential;
+128 -5
View File
@@ -26,6 +26,20 @@ import {
KubernetesObjectsProviderExtensionPoint,
kubernetesObjectsProviderExtensionPoint,
KubernetesObjectsProvider,
KubernetesClusterSupplierExtensionPoint,
kubernetesClusterSupplierExtensionPoint,
KubernetesClustersSupplier,
KubernetesAuthStrategyExtensionPoint,
AuthenticationStrategy,
kubernetesAuthStrategyExtensionPoint,
KubernetesFetcher,
KubernetesServiceLocatorExtensionPoint,
KubernetesServiceLocator,
kubernetesServiceLocatorExtensionPoint,
} from '@backstage/plugin-kubernetes-node';
import {
KubernetesFetcherExtensionPoint,
kubernetesFetcherExtensionPoint,
} from '@backstage/plugin-kubernetes-node';
class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint {
@@ -45,6 +59,86 @@ class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint {
}
}
class ClusterSuplier implements KubernetesClusterSupplierExtensionPoint {
private clusterSupplier: KubernetesClustersSupplier | undefined;
getClusterSupplier() {
return this.clusterSupplier;
}
addClusterSupplier(clusterSupplier: KubernetesClustersSupplier) {
if (this.clusterSupplier) {
throw new Error(
'Multiple Kubernetes Cluster Suppliers is not supported at this time',
);
}
this.clusterSupplier = clusterSupplier;
}
}
class Fetcher implements KubernetesFetcherExtensionPoint {
private fetcher: KubernetesFetcher | undefined;
getFetcher() {
return this.fetcher;
}
addFetcher(fetcher: KubernetesFetcher) {
if (this.fetcher) {
throw new Error(
'Multiple Kubernetes Fetchers is not supported at this time',
);
}
this.fetcher = fetcher;
}
}
class ServiceLocator implements KubernetesServiceLocatorExtensionPoint {
private serviceLocator: KubernetesServiceLocator | undefined;
getServiceLocator() {
return this.serviceLocator;
}
addServiceLocator(serviceLocator: KubernetesServiceLocator) {
if (this.serviceLocator) {
throw new Error(
'Multiple Kubernetes Service Locators is not supported at this time',
);
}
this.serviceLocator = serviceLocator;
}
}
class AuthStrategy implements KubernetesAuthStrategyExtensionPoint {
private authStrategies: Array<{
key: string;
strategy: AuthenticationStrategy;
}>;
constructor() {
this.authStrategies = new Array<{
key: string;
strategy: AuthenticationStrategy;
}>();
}
static addAuthStrategiesFromArray(
authStrategies: Array<{ key: string; strategy: AuthenticationStrategy }>,
builder: KubernetesBuilder,
) {
authStrategies.forEach(st => builder.addAuthStrategy(st.key, st.strategy));
}
getAuthenticationStrategies() {
return this.authStrategies;
}
addAuthStrategy(key: string, authStrategy: AuthenticationStrategy) {
this.authStrategies.push({ key, strategy: authStrategy });
}
}
/**
* This is the backend plugin that provides the Kubernetes integration.
* @alpha
@@ -53,10 +147,31 @@ class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint {
export const kubernetesPlugin = createBackendPlugin({
pluginId: 'kubernetes',
register(env) {
const extensionPoint = new ObjectsProvider();
const extPointObjectsProvider = new ObjectsProvider();
const extPointClusterSuplier = new ClusterSuplier();
const extPointAuthStrategy = new AuthStrategy();
const extPointFetcher = new Fetcher();
const extPointServiceLocator = new ServiceLocator();
env.registerExtensionPoint(
kubernetesObjectsProviderExtensionPoint,
extensionPoint,
extPointObjectsProvider,
);
env.registerExtensionPoint(
kubernetesClusterSupplierExtensionPoint,
extPointClusterSuplier,
);
env.registerExtensionPoint(
kubernetesAuthStrategyExtensionPoint,
extPointAuthStrategy,
);
env.registerExtensionPoint(
kubernetesFetcherExtensionPoint,
extPointFetcher,
);
env.registerExtensionPoint(
kubernetesServiceLocatorExtensionPoint,
extPointServiceLocator,
);
env.registerInit({
@@ -70,14 +185,22 @@ export const kubernetesPlugin = createBackendPlugin({
async init({ http, logger, config, catalogApi, permissions }) {
const winstonLogger = loggerToWinstonLogger(logger);
// TODO: expose all of the customization & extension points of the builder here
const { router } = await KubernetesBuilder.createBuilder({
const builder: KubernetesBuilder = KubernetesBuilder.createBuilder({
logger: winstonLogger,
config,
catalogApi,
permissions,
})
.setObjectsProvider(extensionPoint.getObjectsProvider())
.build();
.setObjectsProvider(extPointObjectsProvider.getObjectsProvider())
.setClusterSupplier(extPointClusterSuplier.getClusterSupplier())
.setFetcher(extPointFetcher.getFetcher())
.setServiceLocator(extPointServiceLocator.getServiceLocator());
AuthStrategy.addAuthStrategiesFromArray(
extPointAuthStrategy.getAuthenticationStrategies(),
builder,
);
const { router } = await builder.build();
http.use(router);
},
});
@@ -14,106 +14,181 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Config, ConfigReader } from '@backstage/config';
import {
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,
ObjectsByEntityResponse,
KubernetesRequestAuth,
} from '@backstage/plugin-kubernetes-common';
import express from 'express';
import request from 'supertest';
import {
ClusterDetails,
FetchResponseWrapper,
KubernetesClustersSupplier,
KubernetesFetcher,
KubernetesServiceLocator,
ObjectFetchParams,
} from '../types/types';
import { KubernetesCredential } from '../auth/types';
import { KubernetesBuilder } from './KubernetesBuilder';
import { KubernetesFanOutHandler } from './KubernetesFanOutHandler';
import { CatalogApi } from '@backstage/catalog-client';
import {
HEADER_KUBERNETES_CLUSTER,
HEADER_KUBERNETES_AUTH,
} from './KubernetesProxy';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import {
ServiceMock,
mockServices,
setupRequestMockHandlers,
startTestBackend,
} from '@backstage/backend-test-utils';
import { rest } from 'msw';
import {
AuthorizeResult,
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
import {
PermissionsService,
createBackendModule,
} from '@backstage/backend-plugin-api';
import {
KubernetesObjectsProvider,
kubernetesAuthStrategyExtensionPoint,
kubernetesClusterSupplierExtensionPoint,
kubernetesObjectsProviderExtensionPoint,
kubernetesFetcherExtensionPoint,
kubernetesServiceLocatorExtensionPoint,
} from '@backstage/plugin-kubernetes-node';
import { ExtendedHttpServer } from '@backstage/backend-app-api';
describe('KubernetesBuilder', () => {
let app: express.Express;
let kubernetesFanOutHandler: jest.Mocked<KubernetesFanOutHandler>;
let config: Config;
let catalogApi: CatalogApi;
let permissions: jest.Mocked<PermissionEvaluator>;
let app: ExtendedHttpServer;
let objectsProviderMock: KubernetesObjectsProvider;
const happyK8SResult = {
items: [
{
clusterOne: {
pods: [
{
metadata: {
name: 'pod1',
},
},
],
},
},
],
} as any;
const policyMock: jest.Mocked<PermissionEvaluator> = {
authorize: jest.fn(),
authorizeConditional: jest.fn(),
};
const permissionsMock: ServiceMock<PermissionsService> =
mockServices.permissions.mock(policyMock);
beforeEach(async () => {
jest.resetAllMocks();
const logger = getVoidLogger();
config = new ConfigReader({
kubernetes: {
serviceLocatorMethod: { type: 'multiTenant' },
clusterLocatorMethods: [{ type: 'config', clusters: [] }],
},
objectsProviderMock = {
getKubernetesObjectsByEntity: jest.fn().mockImplementation(_ => {
return Promise.resolve(happyK8SResult);
}),
getCustomResourcesByEntity: jest.fn().mockImplementation(_ => {
return Promise.resolve(happyK8SResult);
}),
};
const clusterSupplierMock = {
getClusters: jest.fn().mockImplementation(_ => {
return Promise.resolve([
{
name: 'some-cluster',
url: 'https://localhost:1234',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
},
},
{
name: 'some-other-cluster',
url: 'https://localhost:1235',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'oidc',
[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'google',
},
},
]);
}),
};
jest.mock('@backstage/catalog-client', () => ({
CatalogClient: jest.fn().mockImplementation(() => ({
getEntityByRef: jest.fn().mockImplementation(entityRef => {
if (entityRef.name === 'noentity') {
return Promise.resolve(undefined);
}
return Promise.resolve({
kind: entityRef.kind,
metadata: {
name: entityRef.name,
namespace: entityRef.namespace,
},
} as Entity);
}),
})),
}));
const { server } = await startTestBackend({
features: [
mockServices.rootConfig.factory({
data: {
kubernetes: {
serviceLocatorMethod: {
type: 'multiTenant',
},
clusterLocatorMethods: [
{
type: 'config',
clusters: [],
},
],
},
},
}),
import('@backstage/plugin-kubernetes-backend/alpha'),
import('@backstage/plugin-permission-backend/alpha'),
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testObjectsProvider',
register(env) {
env.registerInit({
deps: { extension: kubernetesObjectsProviderExtensionPoint },
async init({ extension }) {
extension.addObjectsProvider(objectsProviderMock);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testClusterSupplier',
register(env) {
env.registerInit({
deps: { extension: kubernetesClusterSupplierExtensionPoint },
async init({ extension }) {
extension.addClusterSupplier(clusterSupplierMock);
},
});
},
}),
],
});
const clusters: ClusterDetails[] = [
{
name: 'some-cluster',
url: 'https://localhost:1234',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount',
},
},
{
name: 'some-other-cluster',
url: 'https://localhost:1235',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'oidc',
[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'google',
},
},
];
const clusterSupplier: KubernetesClustersSupplier = {
async getClusters() {
return clusters;
},
};
kubernetesFanOutHandler = {
getKubernetesObjectsByEntity: jest.fn(),
} as any;
permissions = {
authorize: jest.fn(),
authorizeConditional: jest.fn(),
};
const { router } = await KubernetesBuilder.createBuilder({
config,
logger,
catalogApi,
permissions,
})
.setObjectsProvider(kubernetesFanOutHandler)
.setClusterSupplier(clusterSupplier)
.build();
app = express().use(router);
app = server;
});
describe('get /clusters', () => {
it('happy path: lists clusters', async () => {
const response = await request(app).get('/clusters');
const response = await request(app).get('/api/kubernetes/clusters');
expect(response.status).toEqual(200);
expect(response.body).toStrictEqual({
@@ -133,70 +208,41 @@ describe('KubernetesBuilder', () => {
});
describe('post /services/:serviceId', () => {
it('happy path: lists kubernetes objects without auth in request body', async () => {
const result = {
clusterOne: {
pods: [
{
metadata: {
name: 'pod1',
},
},
],
},
} as any;
kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce(
Promise.resolve(result),
const response = await request(app).post(
'/api/kubernetes/services/test-service',
);
const response = await request(app).post('/services/test-service');
expect(response.status).toEqual(200);
expect(response.body).toEqual(result);
expect(response.body).toEqual(happyK8SResult);
});
it('happy path: lists kubernetes objects with auth in request body', async () => {
const result = {
clusterOne: {
pods: [
{
metadata: {
name: 'pod1',
},
},
],
},
} as any;
kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce(
Promise.resolve(result),
);
const response = await request(app)
.post('/services/test-service')
.post('/api/kubernetes/services/test-service')
.send({
auth: {
google: 'google_token_123',
},
})
.set('Content-Type', 'application/json');
expect(response.status).toEqual(200);
expect(response.body).toEqual(result);
expect(response.body).toEqual(happyK8SResult);
});
it('internal error: lists kubernetes objects', async () => {
kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockRejectedValue(
Error('some internal error'),
);
objectsProviderMock.getKubernetesObjectsByEntity = jest
.fn()
.mockRejectedValue(Error('some internal error'));
const response = await request(app).post('/services/test-service');
const response = await request(app).post(
'/api/kubernetes/services/test-service',
);
expect(response.status).toEqual(500);
expect(response.body).toEqual({ error: 'some internal error' });
});
it('custom service locator', async () => {
const logger = getVoidLogger();
const someCluster: ClusterDetails = {
const someCluster = {
name: 'some-cluster',
url: 'https://localhost:1234',
authMetadata: {
@@ -212,11 +258,13 @@ describe('KubernetesBuilder', () => {
authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' },
},
];
const clusterSupplier: KubernetesClustersSupplier = {
async getClusters() {
return clusters;
},
const clusterSupplierMock = {
getClusters: jest.fn().mockImplementation(_ => {
return Promise.resolve(clusters);
}),
};
const pod = {
metadata: {
name: 'pod1',
@@ -240,7 +288,7 @@ describe('KubernetesBuilder', () => {
],
};
const serviceLocator: KubernetesServiceLocator = {
const mockServiceLocator: KubernetesServiceLocator = {
getClustersByEntity(
_entity: Entity,
): Promise<{ clusters: ClusterDetails[] }> {
@@ -248,7 +296,7 @@ describe('KubernetesBuilder', () => {
},
};
const fetcher: KubernetesFetcher = {
const mockFetcher: KubernetesFetcher = {
fetchPodMetricsByNamespaces(
_clusterDetails: ClusterDetails,
_credential: KubernetesCredential,
@@ -271,20 +319,67 @@ describe('KubernetesBuilder', () => {
},
};
const { router } = await KubernetesBuilder.createBuilder({
logger,
config,
catalogApi,
permissions,
})
.setClusterSupplier(clusterSupplier)
.setServiceLocator(serviceLocator)
.setFetcher(fetcher)
.build();
app = express().use(router);
const { server } = await startTestBackend({
features: [
mockServices.rootConfig.factory({
data: {
kubernetes: {
serviceLocatorMethod: {
type: 'multiTenant',
},
clusterLocatorMethods: [
{
type: 'config',
clusters: [],
},
],
},
},
}),
import('@backstage/plugin-kubernetes-backend/alpha'),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testClusterSupplier',
register(env) {
env.registerInit({
deps: { extension: kubernetesClusterSupplierExtensionPoint },
async init({ extension }) {
extension.addClusterSupplier(clusterSupplierMock);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testFetcher',
register(env) {
env.registerInit({
deps: { extension: kubernetesFetcherExtensionPoint },
async init({ extension }) {
extension.addFetcher(mockFetcher);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testServiceLocator',
register(env) {
env.registerInit({
deps: { extension: kubernetesServiceLocatorExtensionPoint },
async init({ extension }) {
extension.addServiceLocator(mockServiceLocator);
},
});
},
}),
],
});
app = server;
const response = await request(app)
.post('/services/test-service')
.post('/api/kubernetes/services/test-service')
.send({
entity: {
metadata: {
@@ -298,9 +393,6 @@ describe('KubernetesBuilder', () => {
});
it('reads auth data for custom strategy', async () => {
permissions.authorize.mockResolvedValue([
{ result: AuthorizeResult.ALLOW },
]);
const mockFetcher = {
fetchPodMetricsByNamespaces: jest
.fn()
@@ -312,43 +404,93 @@ describe('KubernetesBuilder', () => {
],
}),
};
const { router } = await KubernetesBuilder.createBuilder({
logger: getVoidLogger(),
config,
catalogApi,
permissions,
})
.addAuthStrategy('custom', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockImplementation(async (_, requestAuth) => ({
type: 'bearer token',
token: requestAuth.custom as string,
})),
validateCluster: jest.fn().mockReturnValue([]),
})
.setClusterSupplier({
getClusters: jest
.fn<Promise<ClusterDetails[]>, []>()
.mockResolvedValue([
{
name: 'custom-cluster',
url: 'http://my.cluster.url',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom',
},
const clusterSupplierMock = {
getClusters: jest.fn().mockImplementation(_ => {
return Promise.resolve([
{
name: 'custom-cluster',
url: 'http://my.cluster.url',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom',
},
]),
})
.setFetcher(mockFetcher)
.build();
app = express().use(router);
},
]);
}),
};
const { server } = await startTestBackend({
features: [
mockServices.rootConfig.factory({
data: {
kubernetes: {
serviceLocatorMethod: {
type: 'multiTenant',
},
clusterLocatorMethods: [
{
type: 'config',
clusters: [],
},
],
},
},
}),
import('@backstage/plugin-kubernetes-backend/alpha'),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testClusterSupplier',
register(env) {
env.registerInit({
deps: { extension: kubernetesClusterSupplierExtensionPoint },
async init({ extension }) {
extension.addClusterSupplier(clusterSupplierMock);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testAuthStrategy',
register(env) {
env.registerInit({
deps: { extension: kubernetesAuthStrategyExtensionPoint },
async init({ extension }) {
extension.addAuthStrategy('custom', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockImplementation(async (_, requestAuth) => ({
type: 'bearer token',
token: requestAuth.custom as string,
})),
validateCluster: jest.fn().mockReturnValue([]),
});
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testFetcher',
register(env) {
env.registerInit({
deps: { extension: kubernetesFetcherExtensionPoint },
async init({ extension }) {
extension.addFetcher(mockFetcher);
},
});
},
}),
],
});
app = server;
await request(app)
.post('/services/test-service')
.post('/api/kubernetes/services/test-service')
.send({
entity: {
metadata: {
@@ -400,12 +542,8 @@ describe('KubernetesBuilder', () => {
},
};
permissions.authorize.mockReturnValue(
Promise.resolve([{ result: AuthorizeResult.ALLOW }]),
);
const proxyEndpointRequest = request(app)
.post('/proxy/api/v1/namespaces')
.post('/api/kubernetes/proxy/api/v1/namespaces')
.set(HEADER_KUBERNETES_CLUSTER, 'some-cluster')
.set(HEADER_KUBERNETES_AUTH, 'randomtoken')
.send(requestBody);
@@ -425,12 +563,8 @@ metadata:
name: new-ns
`;
permissions.authorize.mockReturnValue(
Promise.resolve([{ result: AuthorizeResult.ALLOW }]),
);
const proxyEndpointRequest = request(app)
.post('/proxy/api/v1/namespaces')
.post('/api/kubernetes/proxy/api/v1/namespaces')
.set(HEADER_KUBERNETES_CLUSTER, 'some-cluster')
.set(HEADER_KUBERNETES_AUTH, 'randomtoken')
.set('content-type', 'application/yaml')
@@ -451,12 +585,35 @@ metadata:
},
};
permissions.authorize.mockReturnValue(
Promise.resolve([{ result: AuthorizeResult.DENY }]),
);
permissionsMock.authorize.mockResolvedValue([
{ result: AuthorizeResult.DENY },
]);
const proxyEndpointRequest = request(app)
.post('/proxy/api/v1/namespaces')
const { server } = await startTestBackend({
features: [
mockServices.rootConfig.factory({
data: {
kubernetes: {
serviceLocatorMethod: {
type: 'multiTenant',
},
clusterLocatorMethods: [
{
type: 'config',
clusters: [],
},
],
},
},
}),
permissionsMock.factory,
import('@backstage/plugin-kubernetes-backend/alpha'),
// import('@backstage/plugin-permission-backend/alpha'),
],
});
const proxyEndpointRequest = request(server)
.post('/api/kubernetes/proxy/api/v1/namespaces')
.set(HEADER_KUBERNETES_CLUSTER, 'some-cluster')
.set(HEADER_KUBERNETES_AUTH, 'randomtoken')
.send(requestBody);
@@ -477,42 +634,90 @@ metadata:
return res(ctx.json({ items: [] }));
}),
);
permissions.authorize.mockResolvedValue([
{ result: AuthorizeResult.ALLOW },
]);
const { router } = await KubernetesBuilder.createBuilder({
logger: getVoidLogger(),
config,
catalogApi,
permissions,
})
.addAuthStrategy('custom', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn().mockReturnValue([]),
})
.setClusterSupplier({
getClusters: jest
.fn<Promise<ClusterDetails[]>, []>()
.mockResolvedValue([
{
name: 'custom-cluster',
url: 'http://my.cluster.url',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom',
},
const clusterSupplierMock = {
getClusters: jest.fn().mockImplementation(_ => {
return Promise.resolve([
{
name: 'custom-cluster',
url: 'http://my.cluster.url',
authMetadata: {
[ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom',
},
]),
})
.build();
app = express().use(router);
},
]);
}),
};
const { server } = await startTestBackend({
features: [
mockServices.rootConfig.factory({
data: {
kubernetes: {
serviceLocatorMethod: {
type: 'multiTenant',
},
clusterLocatorMethods: [
{
type: 'config',
clusters: [],
},
],
},
},
}),
import('@backstage/plugin-kubernetes-backend/alpha'),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testObjectsProvider',
register(env) {
env.registerInit({
deps: { extension: kubernetesObjectsProviderExtensionPoint },
async init({ extension }) {
extension.addObjectsProvider(objectsProviderMock);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testClusterSupplier',
register(env) {
env.registerInit({
deps: { extension: kubernetesClusterSupplierExtensionPoint },
async init({ extension }) {
extension.addClusterSupplier(clusterSupplierMock);
},
});
},
}),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testAuthStrategy',
register(env) {
env.registerInit({
deps: { extension: kubernetesAuthStrategyExtensionPoint },
async init({ extension }) {
extension.addAuthStrategy('custom', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn().mockReturnValue([]),
});
},
});
},
}),
],
});
app = server;
const proxyEndpointRequest = request(app)
.get('/proxy/api/v1/namespaces')
.get('/api/kubernetes/proxy/api/v1/namespaces')
.set(HEADER_KUBERNETES_CLUSTER, 'custom-cluster')
.set(HEADER_KUBERNETES_AUTH, 'custom-token');
worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough()));
@@ -522,29 +727,44 @@ metadata:
});
it('should not permit custom auth strategies with dashes', async () => {
const throwError = () =>
KubernetesBuilder.createBuilder({
logger: getVoidLogger(),
config,
catalogApi,
permissions,
}).addAuthStrategy('custom-strategy', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn().mockReturnValue([]),
const throwError = async () => {
await startTestBackend({
features: [
import('@backstage/plugin-kubernetes-backend/alpha'),
createBackendModule({
pluginId: 'kubernetes',
moduleId: 'testAuthStrategy',
register(env) {
env.registerInit({
deps: { extension: kubernetesAuthStrategyExtensionPoint },
async init({ extension }) {
extension.addAuthStrategy('custom-strategy', {
getCredential: jest
.fn<
Promise<KubernetesCredential>,
[ClusterDetails, KubernetesRequestAuth]
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn().mockReturnValue([]),
});
},
});
},
}),
],
});
};
expect(throwError).toThrow('Strategy name can not include dashes');
await expect(throwError).rejects.toThrow(
'Strategy name can not include dashes',
);
});
});
describe('get /.well-known/backstage/permissions/metadata', () => {
it('lists permissions supported by the kubernetes plugin', async () => {
const response = await request(app).get(
'/.well-known/backstage/permissions/metadata',
'/api/kubernetes/.well-known/backstage/permissions/metadata',
);
expect(response.status).toEqual(200);
@@ -46,7 +46,6 @@ import { MultiTenantServiceLocator } from '../service-locator/MultiTenantService
import { SingleTenantServiceLocator } from '../service-locator/SingleTenantServiceLocator';
import {
CustomResource,
KubernetesClustersSupplier,
KubernetesFetcher,
KubernetesObjectsProviderOptions,
KubernetesObjectTypes,
@@ -54,7 +53,10 @@ import {
ObjectsByEntityRequest,
ServiceLocatorMethod,
} from '../types/types';
import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node';
import {
KubernetesClustersSupplier,
KubernetesObjectsProvider,
} from '@backstage/plugin-kubernetes-node';
import {
DEFAULT_OBJECTS,
KubernetesFanOutHandler,
@@ -17,7 +17,6 @@
import { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
import {
ClusterDetails,
KubernetesFetcher,
KubernetesObjectsProviderOptions,
KubernetesServiceLocator,
@@ -26,7 +25,6 @@ import {
ObjectToFetch,
CustomResource,
} from '../types/types';
import { AuthenticationStrategy, KubernetesCredential } from '../auth/types';
import {
ClientContainerStatus,
ClientCurrentResourceUsage,
@@ -45,7 +43,10 @@ import {
PodStatus,
} from '@kubernetes/client-node';
import {
AuthenticationStrategy,
ClusterDetails,
CustomResourcesByEntity,
KubernetesCredential,
KubernetesObjectsByEntity,
} from '@backstage/plugin-kubernetes-node';
@@ -26,12 +26,10 @@ import {
import lodash, { Dictionary } from 'lodash';
import { Logger } from 'winston';
import {
ClusterDetails,
FetchResponseWrapper,
KubernetesFetcher,
ObjectFetchParams,
} from '../types/types';
import { KubernetesCredential } from '../auth/types';
import {
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
FetchResponse,
@@ -43,6 +41,10 @@ import fetch, { RequestInit, Response } from 'node-fetch';
import * as https from 'https';
import fs from 'fs-extra';
import { JsonObject } from '@backstage/types';
import {
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
export interface KubernetesClientBasedFetcherOptions {
logger: Logger;
@@ -15,9 +15,3 @@
*/
export * from './types';
export type {
CustomResourcesByEntity,
KubernetesObjectsByEntity,
KubernetesObjectsProvider,
} from '@backstage/plugin-kubernetes-node';
+76 -191
View File
@@ -14,131 +14,10 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
import type { JsonObject } from '@backstage/types';
import type {
CustomResourceMatcher,
FetchResponse,
KubernetesFetchError,
KubernetesRequestBody,
} from '@backstage/plugin-kubernetes-common';
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { Config } from '@backstage/config';
import { KubernetesCredential } from '../auth/types';
/**
*
* @public
*/
export interface ObjectFetchParams {
serviceId: string;
clusterDetails: ClusterDetails;
credential: KubernetesCredential;
objectTypesToFetch: Set<ObjectToFetch>;
labelSelector?: string;
customResources: CustomResource[];
namespace?: string;
}
/**
* Fetches information from a kubernetes cluster using the cluster details object to target a specific cluster
*
* @public
*/
export interface KubernetesFetcher {
fetchObjectsForService(
params: ObjectFetchParams,
): Promise<FetchResponseWrapper>;
fetchPodMetricsByNamespaces(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
namespaces: Set<string>,
labelSelector?: string,
): Promise<FetchResponseWrapper>;
}
/**
*
* @public
*/
export interface FetchResponseWrapper {
errors: KubernetesFetchError[];
responses: FetchResponse[];
}
/**
*
* @public
*/
export interface ObjectToFetch {
objectType: KubernetesObjectTypes;
group: string;
apiVersion: string;
plural: string;
}
/**
*
* @public
*/
export interface CustomResource extends ObjectToFetch {
objectType: 'customresources';
}
/**
*
* @public
*/
export type KubernetesObjectTypes =
| 'pods'
| 'services'
| 'configmaps'
| 'deployments'
| 'limitranges'
| 'resourcequotas'
| 'replicasets'
| 'horizontalpodautoscalers'
| 'jobs'
| 'cronjobs'
| 'ingresses'
| 'customresources'
| 'statefulsets'
| 'daemonsets';
// If updating this list, also make sure to update
// `objectTypes` and `apiVersionOverrides` in config.d.ts!
/**
* Used to load cluster details from different sources
* @public
*/
export interface KubernetesClustersSupplier {
/**
* Returns the cached list of clusters.
*
* Implementations _should_ cache the clusters and refresh them periodically,
* as getClusters is called whenever the list of clusters is needed.
*/
getClusters(): Promise<ClusterDetails[]>;
}
/**
* @public
*/
export interface ServiceLocatorRequestContext {
objectTypesToFetch: Set<ObjectToFetch>;
customResources: CustomResourceMatcher[];
}
/**
* Used to locate which cluster(s) a service is running on
* @public
*/
export interface KubernetesServiceLocator {
getClustersByEntity(
entity: Entity,
requestContext: ServiceLocatorRequestContext,
): Promise<{ clusters: ClusterDetails[] }>;
}
import * as k8sTypes from '@backstage/plugin-kubernetes-node';
/**
*
@@ -146,70 +25,6 @@ export interface KubernetesServiceLocator {
*/
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http'; // TODO implement http
/**
* Provider-specific authentication configuration
* @public
*/
export type AuthMetadata = Record<string, string>;
/**
*
* @public
*/
export interface ClusterDetails {
/**
* Specifies the name of the Kubernetes cluster.
*/
name: string;
url: string;
authMetadata: AuthMetadata;
skipTLSVerify?: boolean;
/**
* Whether to skip the lookup to the metrics server to retrieve pod resource usage.
* It is not guaranteed that the Kubernetes distro has the metrics server installed.
*/
skipMetricsLookup?: boolean;
caData?: string | undefined;
caFile?: string | undefined;
/**
* Specifies the link to the Kubernetes dashboard managing this cluster.
* @remarks
* Note that you should specify the app used for the dashboard
* using the dashboardApp property, in order to properly format
* links to kubernetes resources, otherwise it will assume that you're running the standard one.
* @see dashboardApp
* @see dashboardParameters
*/
dashboardUrl?: string;
/**
* Specifies the app that provides the Kubernetes dashboard.
* This will be used for formatting links to kubernetes objects inside the dashboard.
* @remarks
* The existing apps are: standard, rancher, openshift, gke, aks, eks
* Note that it will default to the regular dashboard provided by the Kubernetes project (standard).
* Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary.
* @defaultValue standard
* @see dashboardUrl
* @example
* ```ts
* import { clusterLinksFormatters } from '@backstage/plugin-kubernetes';
* clusterLinksFormatters.myDashboard = (options) => ...;
* ```
*/
dashboardApp?: string;
/**
* Specifies specific parameters used by some dashboard URL formatters.
* This is used by the GKE formatter which requires the project, region and cluster name.
* @see dashboardApp
*/
dashboardParameters?: JsonObject;
/**
* Specifies which custom resources to look for when returning an entity's
* Kubernetes resources.
*/
customResources?: CustomResourceMatcher[];
}
/**
*
* @public
@@ -217,10 +32,10 @@ export interface ClusterDetails {
export interface KubernetesObjectsProviderOptions {
logger: Logger;
config: Config;
fetcher: KubernetesFetcher;
serviceLocator: KubernetesServiceLocator;
customResources: CustomResource[];
objectTypesToFetch?: ObjectToFetch[];
fetcher: k8sTypes.KubernetesFetcher;
serviceLocator: k8sTypes.KubernetesServiceLocator;
customResources: k8sTypes.CustomResource[];
objectTypesToFetch?: k8sTypes.ObjectToFetch[];
}
/**
@@ -228,3 +43,73 @@ export interface KubernetesObjectsProviderOptions {
* @public
*/
export type ObjectsByEntityRequest = KubernetesRequestBody;
// TODO remove this re-export as a breaking change after a couple of releases
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesObjectsProvider = k8sTypes.KubernetesObjectsProvider;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type CustomResourcesByEntity = k8sTypes.CustomResourcesByEntity;
/**
* @public
* @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type AuthMetadata = k8sTypes.AuthMetadata;
/**
* @public
* @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type ClusterDetails = k8sTypes.ClusterDetails;
/**
* @public
* @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesClustersSupplier = k8sTypes.KubernetesClustersSupplier;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesObjectTypes = k8sTypes.KubernetesObjectTypes;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type ObjectToFetch = k8sTypes.ObjectToFetch;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type CustomResource = k8sTypes.CustomResource;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type ObjectFetchParams = k8sTypes.ObjectFetchParams;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type FetchResponseWrapper = k8sTypes.FetchResponseWrapper;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesFetcher = k8sTypes.KubernetesFetcher;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type ServiceLocatorRequestContext =
k8sTypes.ServiceLocatorRequestContext;
/**
* @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead
*/
export type KubernetesServiceLocator = k8sTypes.KubernetesServiceLocator;
+187
View File
@@ -3,19 +3,131 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node';
import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common';
import { Entity } from '@backstage/catalog-model';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { FetchResponse } from '@backstage/plugin-kubernetes-common';
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';
import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common';
import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backstage/plugin-kubernetes-node';
import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
// @public (undocumented)
export interface AuthenticationStrategy {
// (undocumented)
getCredential(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
// (undocumented)
validateCluster(authMetadata: AuthMetadata): Error[];
}
// @public
export type AuthMetadata = Record<string, string>;
// @public (undocumented)
export interface ClusterDetails {
// (undocumented)
authMetadata: AuthMetadata;
// (undocumented)
caData?: string | undefined;
// (undocumented)
caFile?: string | undefined;
customResources?: CustomResourceMatcher[];
dashboardApp?: string;
dashboardParameters?: JsonObject;
dashboardUrl?: string;
name: string;
skipMetricsLookup?: boolean;
// (undocumented)
skipTLSVerify?: boolean;
// (undocumented)
url: string;
}
// @public (undocumented)
export interface CustomResource extends ObjectToFetch {
// (undocumented)
objectType: 'customresources';
}
// @public (undocumented)
export interface CustomResourcesByEntity extends KubernetesObjectsByEntity {
// (undocumented)
customResources: CustomResourceMatcher[];
}
// @public (undocumented)
export interface FetchResponseWrapper {
// (undocumented)
errors: KubernetesFetchError[];
// (undocumented)
responses: FetchResponse[];
}
// @public
export interface KubernetesAuthStrategyExtensionPoint {
// (undocumented)
addAuthStrategy(key: string, strategy: AuthenticationStrategy_2): void;
}
// @public
export const kubernetesAuthStrategyExtensionPoint: ExtensionPoint<KubernetesAuthStrategyExtensionPoint>;
// @public
export interface KubernetesClustersSupplier {
getClusters(): Promise<ClusterDetails[]>;
}
// @public
export interface KubernetesClusterSupplierExtensionPoint {
// (undocumented)
addClusterSupplier(clusterSupplier: KubernetesClustersSupplier_2): void;
}
// @public
export const kubernetesClusterSupplierExtensionPoint: ExtensionPoint<KubernetesClusterSupplierExtensionPoint>;
// @public
export type KubernetesCredential =
| {
type: 'bearer token';
token: string;
}
| {
type: 'anonymous';
};
// @public
export interface KubernetesFetcher {
// (undocumented)
fetchObjectsForService(
params: ObjectFetchParams,
): Promise<FetchResponseWrapper>;
// (undocumented)
fetchPodMetricsByNamespaces(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
namespaces: Set<string>,
labelSelector?: string,
): Promise<FetchResponseWrapper>;
}
// @public
export interface KubernetesFetcherExtensionPoint {
// (undocumented)
addFetcher(fetcher: KubernetesFetcher_2): void;
}
// @public
export const kubernetesFetcherExtensionPoint: ExtensionPoint<KubernetesFetcherExtensionPoint>;
// @public (undocumented)
export interface KubernetesObjectsByEntity {
// (undocumented)
@@ -44,4 +156,79 @@ export interface KubernetesObjectsProviderExtensionPoint {
// @public
export const kubernetesObjectsProviderExtensionPoint: ExtensionPoint<KubernetesObjectsProviderExtensionPoint>;
// @public (undocumented)
export type KubernetesObjectTypes =
| 'pods'
| 'services'
| 'configmaps'
| 'deployments'
| 'limitranges'
| 'resourcequotas'
| 'replicasets'
| 'horizontalpodautoscalers'
| 'jobs'
| 'cronjobs'
| 'ingresses'
| 'customresources'
| 'statefulsets'
| 'daemonsets';
// @public
export interface KubernetesServiceLocator {
// (undocumented)
getClustersByEntity(
entity: Entity,
requestContext: ServiceLocatorRequestContext,
): Promise<{
clusters: ClusterDetails[];
}>;
}
// @public
export interface KubernetesServiceLocatorExtensionPoint {
// (undocumented)
addServiceLocator(serviceLocator: KubernetesServiceLocator_2): void;
}
// @public
export const kubernetesServiceLocatorExtensionPoint: ExtensionPoint<KubernetesServiceLocatorExtensionPoint>;
// @public (undocumented)
export interface ObjectFetchParams {
// (undocumented)
clusterDetails: ClusterDetails;
// (undocumented)
credential: KubernetesCredential;
// (undocumented)
customResources: CustomResource[];
// (undocumented)
labelSelector?: string;
// (undocumented)
namespace?: string;
// (undocumented)
objectTypesToFetch: Set<ObjectToFetch>;
// (undocumented)
serviceId: string;
}
// @public (undocumented)
export interface ObjectToFetch {
// (undocumented)
apiVersion: string;
// (undocumented)
group: string;
// (undocumented)
objectType: KubernetesObjectTypes;
// (undocumented)
plural: string;
}
// @public (undocumented)
export interface ServiceLocatorRequestContext {
// (undocumented)
customResources: CustomResourceMatcher[];
// (undocumented)
objectTypesToFetch: Set<ObjectToFetch>;
}
```
+2 -1
View File
@@ -30,6 +30,7 @@
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/plugin-kubernetes-common": "workspace:^"
"@backstage/plugin-kubernetes-common": "workspace:^",
"@backstage/types": "workspace:^"
}
}
+83 -1
View File
@@ -14,7 +14,13 @@
* limitations under the License.
*/
import { createExtensionPoint } from '@backstage/backend-plugin-api';
import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node';
import {
AuthenticationStrategy,
KubernetesClustersSupplier,
KubernetesFetcher,
KubernetesObjectsProvider,
KubernetesServiceLocator,
} from '@backstage/plugin-kubernetes-node';
/**
* The interface for {@link kubernetesObjectsProviderExtensionPoint}.
@@ -34,3 +40,79 @@ export const kubernetesObjectsProviderExtensionPoint =
createExtensionPoint<KubernetesObjectsProviderExtensionPoint>({
id: 'kubernetes.objects-provider',
});
/**
* The interface for {@link kubernetesClusterSupplierExtensionPoint}.
*
* @public
*/
export interface KubernetesClusterSupplierExtensionPoint {
addClusterSupplier(clusterSupplier: KubernetesClustersSupplier): void;
}
/**
* An extension point the exposes the ability to configure a cluster supplier.
*
* @public
*/
export const kubernetesClusterSupplierExtensionPoint =
createExtensionPoint<KubernetesClusterSupplierExtensionPoint>({
id: 'kubernetes.cluster-supplier',
});
/**
* The interface for {@link kubernetesAuthStrategyExtensionPoint}.
*
* @public
*/
export interface KubernetesAuthStrategyExtensionPoint {
addAuthStrategy(key: string, strategy: AuthenticationStrategy): void;
}
/**
* An extension point the exposes the ability to add an Auth Strategy.
*
* @public
*/
export const kubernetesAuthStrategyExtensionPoint =
createExtensionPoint<KubernetesAuthStrategyExtensionPoint>({
id: 'kubernetes.auth-strategy',
});
/**
* The interface for {@link kubernetesFetcherExtensionPoint}.
*
* @public
*/
export interface KubernetesFetcherExtensionPoint {
addFetcher(fetcher: KubernetesFetcher): void;
}
/**
* An extension point the exposes the ability to configure a kubernetes fetcher.
*
* @public
*/
export const kubernetesFetcherExtensionPoint =
createExtensionPoint<KubernetesFetcherExtensionPoint>({
id: 'kubernetes.fetcher',
});
/**
* The interface for {@link kubernetesServiceLocatorExtensionPoint}.
*
* @public
*/
export interface KubernetesServiceLocatorExtensionPoint {
addServiceLocator(serviceLocator: KubernetesServiceLocator): void;
}
/**
* An extension point the exposes the ability to configure a kubernetes service locator.
*
* @public
*/
export const kubernetesServiceLocatorExtensionPoint =
createExtensionPoint<KubernetesServiceLocatorExtensionPoint>({
id: 'kubernetes.service-locator',
});
+1 -5
View File
@@ -29,9 +29,5 @@
* @packageDocumentation
*/
export {
kubernetesObjectsProviderExtensionPoint,
type KubernetesObjectsProviderExtensionPoint,
} from './extensions';
export * from './extensions';
export * from './types';
+200
View File
@@ -16,9 +16,12 @@
import { Entity } from '@backstage/catalog-model';
import {
CustomResourceMatcher,
FetchResponse,
KubernetesFetchError,
KubernetesRequestAuth,
ObjectsByEntityResponse,
} from '@backstage/plugin-kubernetes-common';
import { JsonObject } from '@backstage/types';
/**
*
@@ -51,3 +54,200 @@ export interface KubernetesObjectsByEntity {
export interface CustomResourcesByEntity extends KubernetesObjectsByEntity {
customResources: CustomResourceMatcher[];
}
/**
* Provider-specific authentication configuration
* @public
*/
export type AuthMetadata = Record<string, string>;
/**
*
* @public
*/
export interface ClusterDetails {
/**
* Specifies the name of the Kubernetes cluster.
*/
name: string;
url: string;
authMetadata: AuthMetadata;
skipTLSVerify?: boolean;
/**
* Whether to skip the lookup to the metrics server to retrieve pod resource usage.
* It is not guaranteed that the Kubernetes distro has the metrics server installed.
*/
skipMetricsLookup?: boolean;
caData?: string | undefined;
caFile?: string | undefined;
/**
* Specifies the link to the Kubernetes dashboard managing this cluster.
* @remarks
* Note that you should specify the app used for the dashboard
* using the dashboardApp property, in order to properly format
* links to kubernetes resources, otherwise it will assume that you're running the standard one.
* @see dashboardApp
* @see dashboardParameters
*/
dashboardUrl?: string;
/**
* Specifies the app that provides the Kubernetes dashboard.
* This will be used for formatting links to kubernetes objects inside the dashboard.
* @remarks
* The existing apps are: standard, rancher, openshift, gke, aks, eks
* Note that it will default to the regular dashboard provided by the Kubernetes project (standard).
* Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary.
* @defaultValue standard
* @see dashboardUrl
* @example
* ```ts
* import { clusterLinksFormatters } from '@backstage/plugin-kubernetes';
* clusterLinksFormatters.myDashboard = (options) => ...;
* ```
*/
dashboardApp?: string;
/**
* Specifies specific parameters used by some dashboard URL formatters.
* This is used by the GKE formatter which requires the project, region and cluster name.
* @see dashboardApp
*/
dashboardParameters?: JsonObject;
/**
* Specifies which custom resources to look for when returning an entity's
* Kubernetes resources.
*/
customResources?: CustomResourceMatcher[];
}
/**
* Used to load cluster details from different sources
* @public
*/
export interface KubernetesClustersSupplier {
/**
* Returns the cached list of clusters.
*
* Implementations _should_ cache the clusters and refresh them periodically,
* as getClusters is called whenever the list of clusters is needed.
*/
getClusters(): Promise<ClusterDetails[]>;
}
/**
* Authentication data used to make a request to Kubernetes
* @public
*/
export type KubernetesCredential =
| { type: 'bearer token'; token: string }
| { type: 'anonymous' };
/**
*
* @public
*/
export interface AuthenticationStrategy {
getCredential(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
validateCluster(authMetadata: AuthMetadata): Error[];
}
/**
*
* @public
*/
export type KubernetesObjectTypes =
| 'pods'
| 'services'
| 'configmaps'
| 'deployments'
| 'limitranges'
| 'resourcequotas'
| 'replicasets'
| 'horizontalpodautoscalers'
| 'jobs'
| 'cronjobs'
| 'ingresses'
| 'customresources'
| 'statefulsets'
| 'daemonsets';
// If updating this list, also make sure to update
// `objectTypes` and `apiVersionOverrides` in config.d.ts on @backstage/plugin-kubernetes-backend!
/**
*
* @public
*/
export interface ObjectToFetch {
objectType: KubernetesObjectTypes; // TODO - Review
group: string;
apiVersion: string;
plural: string;
}
/**
*
* @public
*/
export interface CustomResource extends ObjectToFetch {
objectType: 'customresources';
}
/**
*
* @public
*/
export interface ObjectFetchParams {
serviceId: string;
clusterDetails: ClusterDetails;
credential: KubernetesCredential;
objectTypesToFetch: Set<ObjectToFetch>;
labelSelector?: string;
customResources: CustomResource[];
namespace?: string;
}
/**
*
* @public
*/
export interface FetchResponseWrapper {
errors: KubernetesFetchError[];
responses: FetchResponse[];
}
/**
* Fetches information from a kubernetes cluster using the cluster details object to target a specific cluster
*
* @public
*/
export interface KubernetesFetcher {
fetchObjectsForService(
params: ObjectFetchParams,
): Promise<FetchResponseWrapper>;
fetchPodMetricsByNamespaces(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
namespaces: Set<string>,
labelSelector?: string,
): Promise<FetchResponseWrapper>;
}
/**
* @public
*/
export interface ServiceLocatorRequestContext {
objectTypesToFetch: Set<ObjectToFetch>;
customResources: CustomResourceMatcher[];
}
/**
* Used to locate which cluster(s) a service is running on
* @public
*/
export interface KubernetesServiceLocator {
getClustersByEntity(
entity: Entity,
requestContext: ServiceLocatorRequestContext,
): Promise<{ clusters: ClusterDetails[] }>;
}
+3
View File
@@ -7576,6 +7576,8 @@ __metadata:
"@backstage/plugin-catalog-node": "workspace:^"
"@backstage/plugin-kubernetes-common": "workspace:^"
"@backstage/plugin-kubernetes-node": "workspace:^"
"@backstage/plugin-permission-backend": "workspace:^"
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/types": "workspace:^"
@@ -7675,6 +7677,7 @@ __metadata:
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/plugin-kubernetes-common": "workspace:^"
"@backstage/types": "workspace:^"
languageName: unknown
linkType: soft