refactor kubernetes fetcher (#7504)
* refactor kubernetes fetcher Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * typo Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * tsc errors Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * Create unlucky-laws-doubt.md Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * missed prettier Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * api report Signed-off-by: mclarke47 <matthewclarke47@gmail.com> * fix tests Signed-off-by: mclarke47 <matthewclarke47@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Refactor kubernetes fetcher to reduce boilerplate code
|
||||
@@ -46,19 +46,15 @@ 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)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface CustomResource {
|
||||
export interface CustomResource extends ObjectToFetch {
|
||||
// (undocumented)
|
||||
apiVersion: string;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
// (undocumented)
|
||||
plural: string;
|
||||
objectType: 'customresources';
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DEFAULT_OBJECTS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const DEFAULT_OBJECTS: KubernetesObjectTypes[];
|
||||
export const DEFAULT_OBJECTS: ObjectToFetch[];
|
||||
|
||||
// Warning: (ae-missing-release-tag) "FetchResponseWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -139,11 +135,25 @@ export interface ObjectFetchParams {
|
||||
// (undocumented)
|
||||
labelSelector: string;
|
||||
// (undocumented)
|
||||
objectTypesToFetch: Set<KubernetesObjectTypes>;
|
||||
objectTypesToFetch: Set<ObjectToFetch>;
|
||||
// (undocumented)
|
||||
serviceId: string;
|
||||
}
|
||||
|
||||
// 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)
|
||||
export interface ObjectToFetch {
|
||||
// (undocumented)
|
||||
apiVersion: string;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
// (undocumented)
|
||||
objectType: KubernetesObjectTypes;
|
||||
// (undocumented)
|
||||
plural: string;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
ClusterDetails,
|
||||
CustomResource,
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectTypes,
|
||||
KubernetesServiceLocator,
|
||||
ObjectToFetch,
|
||||
} from '../types/types';
|
||||
import {
|
||||
ClusterObjects,
|
||||
@@ -29,14 +29,49 @@ import {
|
||||
import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';
|
||||
import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator';
|
||||
|
||||
export const DEFAULT_OBJECTS: KubernetesObjectTypes[] = [
|
||||
'pods',
|
||||
'services',
|
||||
'configmaps',
|
||||
'deployments',
|
||||
'replicasets',
|
||||
'horizontalpodautoscalers',
|
||||
'ingresses',
|
||||
export const DEFAULT_OBJECTS: ObjectToFetch[] = [
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'pods',
|
||||
objectType: 'pods',
|
||||
},
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'services',
|
||||
objectType: 'services',
|
||||
},
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'configmaps',
|
||||
objectType: 'configmaps',
|
||||
},
|
||||
{
|
||||
group: 'apps',
|
||||
apiVersion: 'v1',
|
||||
plural: 'deployments',
|
||||
objectType: 'deployments',
|
||||
},
|
||||
{
|
||||
group: 'apps',
|
||||
apiVersion: 'v1',
|
||||
plural: 'replicasets',
|
||||
objectType: 'replicasets',
|
||||
},
|
||||
{
|
||||
group: 'autoscaling',
|
||||
apiVersion: 'v1',
|
||||
plural: 'horizontalpodautoscalers',
|
||||
objectType: 'horizontalpodautoscalers',
|
||||
},
|
||||
{
|
||||
group: 'networking.k8s.io',
|
||||
apiVersion: 'v1',
|
||||
plural: 'ingresses',
|
||||
objectType: 'ingresses',
|
||||
},
|
||||
];
|
||||
|
||||
export interface KubernetesFanOutHandlerOptions {
|
||||
@@ -44,7 +79,7 @@ export interface KubernetesFanOutHandlerOptions {
|
||||
fetcher: KubernetesFetcher;
|
||||
serviceLocator: KubernetesServiceLocator;
|
||||
customResources: CustomResource[];
|
||||
objectTypesToFetch?: KubernetesObjectTypes[];
|
||||
objectTypesToFetch?: ObjectToFetch[];
|
||||
}
|
||||
|
||||
export class KubernetesFanOutHandler {
|
||||
@@ -52,7 +87,7 @@ export class KubernetesFanOutHandler {
|
||||
private readonly fetcher: KubernetesFetcher;
|
||||
private readonly serviceLocator: KubernetesServiceLocator;
|
||||
private readonly customResources: CustomResource[];
|
||||
private readonly objectTypesToFetch: KubernetesObjectTypes[];
|
||||
private readonly objectTypesToFetch: Set<ObjectToFetch>;
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
@@ -65,7 +100,7 @@ export class KubernetesFanOutHandler {
|
||||
this.fetcher = fetcher;
|
||||
this.serviceLocator = serviceLocator;
|
||||
this.customResources = customResources;
|
||||
this.objectTypesToFetch = objectTypesToFetch;
|
||||
this.objectTypesToFetch = new Set(objectTypesToFetch);
|
||||
}
|
||||
|
||||
async getKubernetesObjectsByEntity(requestBody: KubernetesRequestBody) {
|
||||
@@ -109,7 +144,7 @@ export class KubernetesFanOutHandler {
|
||||
.fetchObjectsForService({
|
||||
serviceId: entityName,
|
||||
clusterDetails: clusterDetailsItem,
|
||||
objectTypesToFetch: new Set(this.objectTypesToFetch),
|
||||
objectTypesToFetch: this.objectTypesToFetch,
|
||||
labelSelector,
|
||||
customResources: this.customResources,
|
||||
})
|
||||
|
||||
@@ -16,6 +16,22 @@
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { KubernetesClientBasedFetcher } from './KubernetesFetcher';
|
||||
import { ObjectToFetch } from '../types/types';
|
||||
|
||||
const OBJECTS_TO_FETCH = new Set<ObjectToFetch>([
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'pods',
|
||||
objectType: 'pods',
|
||||
},
|
||||
{
|
||||
group: '',
|
||||
apiVersion: 'v1',
|
||||
plural: 'services',
|
||||
objectType: 'services',
|
||||
},
|
||||
]);
|
||||
|
||||
describe('KubernetesFetcher', () => {
|
||||
let clientMock: any;
|
||||
@@ -25,15 +41,11 @@ describe('KubernetesFetcher', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
clientMock = {
|
||||
listPodForAllNamespaces: jest.fn(),
|
||||
listServiceForAllNamespaces: jest.fn(),
|
||||
listClusterCustomObject: jest.fn(),
|
||||
addInterceptor: jest.fn(),
|
||||
};
|
||||
|
||||
kubernetesClientProvider = {
|
||||
getCoreClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getAppsClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getAutoscalingClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getNetworkingBeta1Client: jest.fn(() => clientMock),
|
||||
getCustomObjectsClient: jest.fn(() => clientMock),
|
||||
};
|
||||
|
||||
@@ -44,7 +56,7 @@ describe('KubernetesFetcher', () => {
|
||||
});
|
||||
|
||||
const testErrorResponse = async (errorResponse: any, expectedResult: any) => {
|
||||
clientMock.listPodForAllNamespaces.mockResolvedValueOnce({
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
@@ -56,7 +68,7 @@ describe('KubernetesFetcher', () => {
|
||||
},
|
||||
});
|
||||
|
||||
clientMock.listServiceForAllNamespaces.mockRejectedValue(errorResponse);
|
||||
clientMock.listClusterCustomObject.mockRejectedValue(errorResponse);
|
||||
|
||||
const result = await sut.fetchObjectsForService({
|
||||
serviceId: 'some-service',
|
||||
@@ -66,7 +78,7 @@ describe('KubernetesFetcher', () => {
|
||||
serviceAccountToken: 'token',
|
||||
authProvider: 'serviceAccount',
|
||||
},
|
||||
objectTypesToFetch: new Set(['pods', 'services']),
|
||||
objectTypesToFetch: OBJECTS_TO_FETCH,
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
});
|
||||
@@ -87,19 +99,35 @@ describe('KubernetesFetcher', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(clientMock.listPodForAllNamespaces.mock.calls.length).toBe(1);
|
||||
expect(clientMock.listServiceForAllNamespaces.mock.calls.length).toBe(1);
|
||||
expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'pods',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'services',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(
|
||||
kubernetesClientProvider.getAppsClientByClusterDetails.mock.calls.length,
|
||||
).toBe(2);
|
||||
expect(
|
||||
kubernetesClientProvider.getCoreClientByClusterDetails.mock.calls.length,
|
||||
kubernetesClientProvider.getCustomObjectsClient.mock.calls.length,
|
||||
).toBe(2);
|
||||
};
|
||||
|
||||
it('should return pods, services', async () => {
|
||||
clientMock.listPodForAllNamespaces.mockResolvedValueOnce({
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
@@ -111,7 +139,7 @@ describe('KubernetesFetcher', () => {
|
||||
},
|
||||
});
|
||||
|
||||
clientMock.listServiceForAllNamespaces.mockResolvedValueOnce({
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
@@ -131,7 +159,7 @@ describe('KubernetesFetcher', () => {
|
||||
serviceAccountToken: 'token',
|
||||
authProvider: 'serviceAccount',
|
||||
},
|
||||
objectTypesToFetch: new Set(['pods', 'services']),
|
||||
objectTypesToFetch: OBJECTS_TO_FETCH,
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
});
|
||||
@@ -162,41 +190,160 @@ describe('KubernetesFetcher', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(clientMock.listPodForAllNamespaces.mock.calls.length).toBe(1);
|
||||
expect(clientMock.listServiceForAllNamespaces.mock.calls.length).toBe(1);
|
||||
expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'pods',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'services',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(
|
||||
kubernetesClientProvider.getAppsClientByClusterDetails.mock.calls.length,
|
||||
).toBe(2);
|
||||
expect(
|
||||
kubernetesClientProvider.getCoreClientByClusterDetails.mock.calls.length,
|
||||
kubernetesClientProvider.getCustomObjectsClient.mock.calls.length,
|
||||
).toBe(2);
|
||||
});
|
||||
it('should throw error on unknown type', () => {
|
||||
expect(() =>
|
||||
sut.fetchObjectsForService({
|
||||
serviceId: 'some-service',
|
||||
clusterDetails: {
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:9999',
|
||||
serviceAccountToken: 'token',
|
||||
authProvider: 'serviceAccount',
|
||||
it('should return pods, services and customobjects', async () => {
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'pod-name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'service-name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'something-else',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await sut.fetchObjectsForService({
|
||||
serviceId: 'some-service',
|
||||
clusterDetails: {
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:9999',
|
||||
serviceAccountToken: 'token',
|
||||
authProvider: 'serviceAccount',
|
||||
},
|
||||
objectTypesToFetch: OBJECTS_TO_FETCH,
|
||||
labelSelector: '',
|
||||
customResources: [
|
||||
{
|
||||
objectType: 'customresources',
|
||||
group: 'some-group',
|
||||
apiVersion: 'v2',
|
||||
plural: 'things',
|
||||
},
|
||||
objectTypesToFetch: new Set<any>(['foo']),
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
}),
|
||||
).toThrow('unrecognised type=foo');
|
||||
],
|
||||
});
|
||||
|
||||
expect(clientMock.listPodForAllNamespaces.mock.calls.length).toBe(0);
|
||||
expect(clientMock.listServiceForAllNamespaces.mock.calls.length).toBe(0);
|
||||
expect(result).toStrictEqual({
|
||||
errors: [],
|
||||
responses: [
|
||||
{
|
||||
type: 'pods',
|
||||
resources: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'pod-name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'services',
|
||||
resources: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'service-name',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'customresources',
|
||||
resources: [
|
||||
{
|
||||
metadata: {
|
||||
name: 'something-else',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'pods',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([
|
||||
'',
|
||||
'v1',
|
||||
'services',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([
|
||||
'some-group',
|
||||
'v2',
|
||||
'things',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'backstage.io/kubernetes-id=some-service',
|
||||
]);
|
||||
|
||||
expect(
|
||||
kubernetesClientProvider.getAppsClientByClusterDetails.mock.calls.length,
|
||||
).toBe(0);
|
||||
expect(
|
||||
kubernetesClientProvider.getCoreClientByClusterDetails.mock.calls.length,
|
||||
).toBe(0);
|
||||
kubernetesClientProvider.getCustomObjectsClient.mock.calls.length,
|
||||
).toBe(3);
|
||||
});
|
||||
// they're in testErrorResponse
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
@@ -283,7 +430,7 @@ describe('KubernetesFetcher', () => {
|
||||
);
|
||||
});
|
||||
it('should always add a labelSelector query', async () => {
|
||||
clientMock.listPodForAllNamespaces.mockResolvedValueOnce({
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
@@ -295,7 +442,7 @@ describe('KubernetesFetcher', () => {
|
||||
},
|
||||
});
|
||||
|
||||
clientMock.listServiceForAllNamespaces.mockResolvedValueOnce({
|
||||
clientMock.listClusterCustomObject.mockResolvedValueOnce({
|
||||
body: {
|
||||
items: [
|
||||
{
|
||||
@@ -315,12 +462,12 @@ describe('KubernetesFetcher', () => {
|
||||
serviceAccountToken: 'token',
|
||||
authProvider: 'serviceAccount',
|
||||
},
|
||||
objectTypesToFetch: new Set(['pods', 'services']),
|
||||
objectTypesToFetch: OBJECTS_TO_FETCH,
|
||||
labelSelector: '',
|
||||
customResources: [],
|
||||
});
|
||||
|
||||
const mockCall = clientMock.listPodForAllNamespaces.mock.calls[0];
|
||||
const mockCall = clientMock.listClusterCustomObject.mock.calls[0];
|
||||
const actualSelector = mockCall[mockCall.length - 1];
|
||||
const expectedSelector = 'backstage.io/kubernetes-id=some-service';
|
||||
expect(actualSelector).toBe(expectedSelector);
|
||||
|
||||
@@ -18,16 +18,8 @@ import {
|
||||
AppsV1Api,
|
||||
AutoscalingV1Api,
|
||||
CoreV1Api,
|
||||
ExtensionsV1beta1Ingress,
|
||||
NetworkingV1beta1Api,
|
||||
V1ConfigMap,
|
||||
V1Deployment,
|
||||
V1HorizontalPodAutoscaler,
|
||||
V1Pod,
|
||||
V1ReplicaSet,
|
||||
} from '@kubernetes/client-node';
|
||||
import { V1Service } from '@kubernetes/client-node/dist/gen/model/v1Service';
|
||||
import http from 'http';
|
||||
import lodash, { Dictionary } from 'lodash';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
@@ -36,7 +28,7 @@ import {
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectTypes,
|
||||
ObjectFetchParams,
|
||||
CustomResource,
|
||||
ObjectToFetch,
|
||||
} from '../types/types';
|
||||
import {
|
||||
FetchResponse,
|
||||
@@ -88,17 +80,6 @@ const statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => {
|
||||
}
|
||||
};
|
||||
|
||||
const captureKubernetesErrorsRethrowOthers = (e: any): KubernetesFetchError => {
|
||||
if (e.response && e.response.statusCode) {
|
||||
return {
|
||||
errorType: statusCodeToErrorType(e.response.statusCode),
|
||||
statusCode: e.response.statusCode,
|
||||
resourcePath: e.response.request.uri.pathname,
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
};
|
||||
|
||||
export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
private readonly kubernetesClientProvider: KubernetesClientProvider;
|
||||
private readonly logger: Logger;
|
||||
@@ -114,202 +95,60 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
fetchObjectsForService(
|
||||
params: ObjectFetchParams,
|
||||
): Promise<FetchResponseWrapper> {
|
||||
const fetchResults = Array.from(params.objectTypesToFetch).map(type => {
|
||||
return this.fetchByObjectType(
|
||||
params.clusterDetails,
|
||||
type,
|
||||
params.labelSelector ||
|
||||
`backstage.io/kubernetes-id=${params.serviceId}`,
|
||||
).catch(captureKubernetesErrorsRethrowOthers);
|
||||
});
|
||||
const fetchResults = Array.from(params.objectTypesToFetch)
|
||||
.concat(params.customResources)
|
||||
.map(toFetch => {
|
||||
return this.fetchResource(
|
||||
params.clusterDetails,
|
||||
toFetch,
|
||||
params.labelSelector ||
|
||||
`backstage.io/kubernetes-id=${params.serviceId}`,
|
||||
toFetch.objectType,
|
||||
).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));
|
||||
});
|
||||
|
||||
const customObjectsFetchResults = params.customResources.map(cr => {
|
||||
return this.fetchCustomResource(
|
||||
params.clusterDetails,
|
||||
cr,
|
||||
params.labelSelector ||
|
||||
`backstage.io/kubernetes-id=${params.serviceId}`,
|
||||
).catch(captureKubernetesErrorsRethrowOthers);
|
||||
});
|
||||
|
||||
return Promise.all(fetchResults.concat(customObjectsFetchResults)).then(
|
||||
fetchResultsToResponseWrapper,
|
||||
);
|
||||
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
|
||||
}
|
||||
|
||||
// TODO could probably do with a tidy up
|
||||
private fetchByObjectType(
|
||||
clusterDetails: ClusterDetails,
|
||||
type: KubernetesObjectTypes,
|
||||
labelSelector: string,
|
||||
): Promise<FetchResponse> {
|
||||
switch (type) {
|
||||
case 'pods':
|
||||
return this.fetchPodsForService(clusterDetails, labelSelector).then(
|
||||
r => ({
|
||||
type: type,
|
||||
resources: r,
|
||||
}),
|
||||
);
|
||||
case 'configmaps':
|
||||
return this.fetchConfigMapsForService(
|
||||
clusterDetails,
|
||||
labelSelector,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'deployments':
|
||||
return this.fetchDeploymentsForService(
|
||||
clusterDetails,
|
||||
labelSelector,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'replicasets':
|
||||
return this.fetchReplicaSetsForService(
|
||||
clusterDetails,
|
||||
labelSelector,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'services':
|
||||
return this.fetchServicesForService(clusterDetails, labelSelector).then(
|
||||
r => ({ type: type, resources: r }),
|
||||
);
|
||||
case 'horizontalpodautoscalers':
|
||||
return this.fetchHorizontalPodAutoscalersForService(
|
||||
clusterDetails,
|
||||
labelSelector,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'ingresses':
|
||||
return this.fetchIngressesForService(
|
||||
clusterDetails,
|
||||
labelSelector,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
default:
|
||||
// unrecognised type
|
||||
throw new Error(`unrecognised type=${type}`);
|
||||
private captureKubernetesErrorsRethrowOthers(e: any): KubernetesFetchError {
|
||||
if (e.response && e.response.statusCode) {
|
||||
this.logger.info(
|
||||
`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname}`,
|
||||
);
|
||||
return {
|
||||
errorType: statusCodeToErrorType(e.response.statusCode),
|
||||
statusCode: e.response.statusCode,
|
||||
resourcePath: e.response.request.uri.pathname,
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
private fetchCustomResource(
|
||||
private fetchResource(
|
||||
clusterDetails: ClusterDetails,
|
||||
customResource: CustomResource,
|
||||
resource: ObjectToFetch,
|
||||
labelSelector: string,
|
||||
objectType: KubernetesObjectTypes,
|
||||
): Promise<FetchResponse> {
|
||||
const customObjects =
|
||||
this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails);
|
||||
|
||||
customObjects.addInterceptor((requestOptions: any) => {
|
||||
requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/');
|
||||
});
|
||||
|
||||
return customObjects
|
||||
.listClusterCustomObject(
|
||||
customResource.group,
|
||||
customResource.apiVersion,
|
||||
customResource.plural,
|
||||
resource.group,
|
||||
resource.apiVersion,
|
||||
resource.plural,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
labelSelector,
|
||||
)
|
||||
.then(r => {
|
||||
return { type: 'customresources', resources: (r.body as any).items };
|
||||
return { type: objectType, resources: (r.body as any).items };
|
||||
});
|
||||
}
|
||||
|
||||
private singleClusterFetch<T>(
|
||||
clusterDetails: ClusterDetails,
|
||||
fn: (
|
||||
client: Clients,
|
||||
) => Promise<{ body: { items: Array<T> }; response: http.IncomingMessage }>,
|
||||
): Promise<Array<T>> {
|
||||
const core =
|
||||
this.kubernetesClientProvider.getCoreClientByClusterDetails(
|
||||
clusterDetails,
|
||||
);
|
||||
const apps =
|
||||
this.kubernetesClientProvider.getAppsClientByClusterDetails(
|
||||
clusterDetails,
|
||||
);
|
||||
const autoscaling =
|
||||
this.kubernetesClientProvider.getAutoscalingClientByClusterDetails(
|
||||
clusterDetails,
|
||||
);
|
||||
const networkingBeta1 =
|
||||
this.kubernetesClientProvider.getNetworkingBeta1Client(clusterDetails);
|
||||
|
||||
this.logger.debug(`calling cluster=${clusterDetails.name}`);
|
||||
return fn({ core, apps, autoscaling, networkingBeta1 }).then(({ body }) => {
|
||||
return body.items;
|
||||
});
|
||||
}
|
||||
|
||||
private fetchServicesForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1Service>> {
|
||||
return this.singleClusterFetch<V1Service>(clusterDetails, ({ core }) =>
|
||||
core.listServiceForAllNamespaces(false, '', '', labelSelector),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchPodsForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1Pod>> {
|
||||
return this.singleClusterFetch<V1Pod>(clusterDetails, ({ core }) =>
|
||||
core.listPodForAllNamespaces(false, '', '', labelSelector),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchConfigMapsForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1ConfigMap>> {
|
||||
return this.singleClusterFetch<V1Pod>(clusterDetails, ({ core }) =>
|
||||
core.listConfigMapForAllNamespaces(false, '', '', labelSelector),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchDeploymentsForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1Deployment>> {
|
||||
return this.singleClusterFetch<V1Deployment>(clusterDetails, ({ apps }) =>
|
||||
apps.listDeploymentForAllNamespaces(false, '', '', labelSelector),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchReplicaSetsForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1ReplicaSet>> {
|
||||
return this.singleClusterFetch<V1ReplicaSet>(clusterDetails, ({ apps }) =>
|
||||
apps.listReplicaSetForAllNamespaces(false, '', '', labelSelector),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchHorizontalPodAutoscalersForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<V1HorizontalPodAutoscaler>> {
|
||||
return this.singleClusterFetch<V1HorizontalPodAutoscaler>(
|
||||
clusterDetails,
|
||||
({ autoscaling }) =>
|
||||
autoscaling.listHorizontalPodAutoscalerForAllNamespaces(
|
||||
false,
|
||||
'',
|
||||
'',
|
||||
labelSelector,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchIngressesForService(
|
||||
clusterDetails: ClusterDetails,
|
||||
labelSelector: string,
|
||||
): Promise<Array<ExtensionsV1beta1Ingress>> {
|
||||
return this.singleClusterFetch<ExtensionsV1beta1Ingress>(
|
||||
clusterDetails,
|
||||
({ networkingBeta1 }) =>
|
||||
networkingBeta1.listIngressForAllNamespaces(
|
||||
false,
|
||||
'',
|
||||
'',
|
||||
labelSelector,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@ import {
|
||||
} from '../types/types';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { KubernetesClientProvider } from './KubernetesClientProvider';
|
||||
import { KubernetesFanOutHandler } from './KubernetesFanOutHandler';
|
||||
import {
|
||||
KubernetesFanOutHandler,
|
||||
DEFAULT_OBJECTS,
|
||||
} from './KubernetesFanOutHandler';
|
||||
import { KubernetesClientBasedFetcher } from './KubernetesFetcher';
|
||||
|
||||
export interface RouterOptions {
|
||||
@@ -109,6 +112,7 @@ export async function createRouter(
|
||||
group: c.getString('group'),
|
||||
apiVersion: c.getString('apiVersion'),
|
||||
plural: c.getString('plural'),
|
||||
objectType: 'customresources',
|
||||
} as CustomResource),
|
||||
);
|
||||
|
||||
@@ -134,10 +138,18 @@ export async function createRouter(
|
||||
);
|
||||
|
||||
const serviceLocator = getServiceLocator(options.config, clusterDetails);
|
||||
const objectTypesToFetch = options.config.getOptionalStringArray(
|
||||
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,
|
||||
|
||||
@@ -19,12 +19,6 @@ import type {
|
||||
KubernetesFetchError,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
export interface CustomResource {
|
||||
group: string;
|
||||
apiVersion: string;
|
||||
plural: string;
|
||||
}
|
||||
|
||||
export interface ObjectFetchParams {
|
||||
serviceId: string;
|
||||
clusterDetails:
|
||||
@@ -32,7 +26,7 @@ export interface ObjectFetchParams {
|
||||
| GKEClusterDetails
|
||||
| ServiceAccountClusterDetails
|
||||
| ClusterDetails;
|
||||
objectTypesToFetch: Set<KubernetesObjectTypes>;
|
||||
objectTypesToFetch: Set<ObjectToFetch>;
|
||||
labelSelector: string;
|
||||
customResources: CustomResource[];
|
||||
}
|
||||
@@ -52,6 +46,17 @@ export interface FetchResponseWrapper {
|
||||
|
||||
// TODO fairly sure there's a easier way to do this
|
||||
|
||||
export interface ObjectToFetch {
|
||||
objectType: KubernetesObjectTypes;
|
||||
group: string;
|
||||
apiVersion: string;
|
||||
plural: string;
|
||||
}
|
||||
|
||||
export interface CustomResource extends ObjectToFetch {
|
||||
objectType: 'customresources';
|
||||
}
|
||||
|
||||
export type KubernetesObjectTypes =
|
||||
| 'pods'
|
||||
| 'services'
|
||||
|
||||
Reference in New Issue
Block a user