From e2085293a635a4c4e356f54a7bf213d0958b2447 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 19 Oct 2020 23:22:08 -0400 Subject: [PATCH 01/12] feat(kubernetes): update k8s-backend plugin to use label selectors update plugin to use label selectors when querying backend --- .../src/service/KubernetesFetcher.test.ts | 6 +- .../src/service/KubernetesFetcher.ts | 97 +++++++++---------- ...ubernetesObjectsByServiceIdHandler.test.ts | 10 +- ... getKubernetesObjectsForServiceHandler.ts} | 8 +- .../src/service/router.test.ts | 2 +- .../kubernetes-backend/src/service/router.ts | 12 ++- plugins/kubernetes-backend/src/types/types.ts | 3 +- plugins/kubernetes/README.md | 25 ++++- .../src/api/KubernetesBackendClient.ts | 18 +++- plugins/kubernetes/src/api/types.ts | 4 +- .../KubernetesContent/KubernetesContent.tsx | 21 +++- 11 files changed, 132 insertions(+), 74 deletions(-) rename plugins/kubernetes-backend/src/service/{getKubernetesObjectsByServiceIdHandler.ts => getKubernetesObjectsForServiceHandler.ts} (90%) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 6294b3a7ed..8c562b5200 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -57,7 +57,7 @@ describe('KubernetesClientProvider', () => { clientMock.listServiceForAllNamespaces.mockRejectedValue(errorResponse); - const result = await sut.fetchObjectsByServiceId( + const result = await sut.fetchObjectsForService( 'some-service', { name: 'cluster1', @@ -120,7 +120,7 @@ describe('KubernetesClientProvider', () => { }, }); - const result = await sut.fetchObjectsByServiceId( + const result = await sut.fetchObjectsForService( 'some-service', { name: 'cluster1', @@ -169,7 +169,7 @@ describe('KubernetesClientProvider', () => { }); it('should throw error on unknown type', () => { expect(() => - sut.fetchObjectsByServiceId( + sut.fetchObjectsForService( 'some-service', { name: 'cluster1', diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index f66a45816a..3bfb651d52 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -105,15 +105,19 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { this.logger = logger; } - fetchObjectsByServiceId( + fetchObjectsForService( serviceId: string, clusterDetails: ClusterDetails, objectTypesToFetch: Set, + labelSelector: string, ): Promise { const fetchResults = Array.from(objectTypesToFetch).map(type => { - return this.fetchByObjectType(serviceId, clusterDetails, type).catch( - captureKubernetesErrorsRethrowOthers, - ); + return this.fetchByObjectType( + serviceId, + clusterDetails, + type, + labelSelector, + ).catch(captureKubernetesErrorsRethrowOthers); }); return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); @@ -124,42 +128,53 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { serviceId: string, clusterDetails: ClusterDetails, type: KubernetesObjectTypes, + labelSelector: string, ): Promise { switch (type) { case 'pods': - return this.fetchPodsByServiceId(serviceId, clusterDetails).then(r => ({ + return this.fetchPodsForService( + serviceId, + clusterDetails, + labelSelector, + ).then(r => ({ type: type, resources: r, })); case 'configmaps': - return this.fetchConfigMapsByServiceId( + return this.fetchConfigMapsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'deployments': - return this.fetchDeploymentsByServiceId( + return this.fetchDeploymentsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'replicasets': - return this.fetchReplicaSetsByServiceId( + return this.fetchReplicaSetsForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': - return this.fetchServicesByServiceId( + return this.fetchServicesForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'horizontalpodautoscalers': - return this.fetchHorizontalPodAutoscalersByServiceId( + return this.fetchHorizontalPodAutoscalersForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); case 'ingresses': - return this.fetchIngressesByServiceId( + return this.fetchIngressesForService( serviceId, clusterDetails, + labelSelector, ).then(r => ({ type: type, resources: r })); default: // unrecognised type @@ -192,79 +207,60 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { }); } - private fetchServicesByServiceId( + private fetchServicesForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listServiceForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listServiceForAllNamespaces(false, '', '', labelSelector), ); } - private fetchPodsByServiceId( + private fetchPodsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listPodForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listPodForAllNamespaces(false, '', '', labelSelector), ); } - private fetchConfigMapsByServiceId( + private fetchConfigMapsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ core }) => - core.listConfigMapForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + core.listConfigMapForAllNamespaces(false, '', '', labelSelector), ); } - private fetchDeploymentsByServiceId( + private fetchDeploymentsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ apps }) => - apps.listDeploymentForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + apps.listDeploymentForAllNamespaces(false, '', '', labelSelector), ); } - private fetchReplicaSetsByServiceId( + private fetchReplicaSetsForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch(clusterDetails, ({ apps }) => - apps.listReplicaSetForAllNamespaces( - false, - '', - '', - `backstage.io/kubernetes-id=${serviceId}`, - ), + apps.listReplicaSetForAllNamespaces(false, '', '', labelSelector), ); } - private fetchHorizontalPodAutoscalersByServiceId( + private fetchHorizontalPodAutoscalersForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch( clusterDetails, @@ -273,14 +269,15 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { false, '', '', - `backstage.io/kubernetes-id=${serviceId}`, + labelSelector, ), ); } - private fetchIngressesByServiceId( + private fetchIngressesForService( serviceId: string, clusterDetails: ClusterDetails, + labelSelector: string, ): Promise> { return this.singleClusterFetch( clusterDetails, @@ -289,7 +286,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { false, '', '', - `backstage.io/kubernetes-id=${serviceId}`, + labelSelector, ), ); } diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 8c26ded2d4..f413d080c6 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { handleGetKubernetesObjectsByServiceId } from './getKubernetesObjectsByServiceIdHandler'; +import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ClusterDetails } from '..'; @@ -81,10 +81,10 @@ describe('handleGetKubernetesObjectsByServiceId', () => { mockFetch(fetchObjectsByServiceId); - const result = await handleGetKubernetesObjectsByServiceId( + const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsByServiceId, }, { getClustersByServiceId, @@ -155,10 +155,10 @@ describe('handleGetKubernetesObjectsByServiceId', () => { mockFetch(fetchObjectsByServiceId); - const result = await handleGetKubernetesObjectsByServiceId( + const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsByServiceId, }, { getClustersByServiceId, diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts similarity index 90% rename from plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts rename to plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 6fc6092bd6..5f31248790 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -26,12 +26,13 @@ import { import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; -export type GetKubernetesObjectsByServiceIdHandler = ( +export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, logger: Logger, requestBody: AuthRequestBody, + labelSelector: string, objectsToFetch?: Set, ) => Promise; @@ -46,12 +47,13 @@ const DEFAULT_OBJECTS = new Set([ ]); // Fans out the request to all clusters that the service lives in, aggregates their responses together -export const handleGetKubernetesObjectsByServiceId: GetKubernetesObjectsByServiceIdHandler = async ( +export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async ( serviceId, fetcher, serviceLocator, logger, requestBody, + labelSelector: string, objectsToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( @@ -81,7 +83,7 @@ export const handleGetKubernetesObjectsByServiceId: GetKubernetesObjectsByServic return Promise.all( clusterDetailsDecoratedForAuth.map(cd => { return fetcher - .fetchObjectsByServiceId(serviceId, cd, objectsToFetch) + .fetchObjectsForService(serviceId, cd, objectsToFetch, labelSelector) .then(result => { return { cluster: { diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/router.test.ts index 6f9fbbf70f..b63b63ead6 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/router.test.ts @@ -32,7 +32,7 @@ describe('router', () => { beforeAll(async () => { kubernetesFetcher = { - fetchObjectsByServiceId: jest.fn(), + fetchObjectsForService: jest.fn(), }; kubernetesServiceLocator = { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index b73115e1e4..5462b5c9fb 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -22,9 +22,9 @@ import { MultiTenantServiceLocator } from '../service-locator/MultiTenantService import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { KubernetesClientProvider } from './KubernetesClientProvider'; import { - GetKubernetesObjectsByServiceIdHandler, - handleGetKubernetesObjectsByServiceId, -} from './getKubernetesObjectsByServiceIdHandler'; + GetKubernetesObjectsForServiceHandler, + handleGetKubernetesObjectsForService, +} from './getKubernetesObjectsForServiceHandler'; import { AuthRequestBody, KubernetesServiceLocator, @@ -64,13 +64,14 @@ export const makeRouter = ( logger: Logger, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, - handleGetByServiceId: GetKubernetesObjectsByServiceIdHandler, + handleGetByServiceId: GetKubernetesObjectsForServiceHandler, ): express.Router => { const router = Router(); router.use(express.json()); router.post('/services/:serviceId', async (req, res) => { const serviceId = req.params.serviceId; + const labelSelector = req.query.labelSelector; const requestBody: AuthRequestBody = req.body; try { const response = await handleGetByServiceId( @@ -79,6 +80,7 @@ export const makeRouter = ( serviceLocator, logger, requestBody, + labelSelector, ); res.send(response); } catch (e) { @@ -119,6 +121,6 @@ export async function createRouter( logger, fetcher, serviceLocator, - handleGetKubernetesObjectsByServiceId, + handleGetKubernetesObjectsForService, ); } diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 1422511c21..0425687f48 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -110,10 +110,11 @@ export interface IngressesFetchResponse { // Fetches information from a kubernetes cluster using the cluster details object // to target a specific cluster export interface KubernetesFetcher { - fetchObjectsByServiceId( + fetchObjectsForService( serviceId: string, clusterDetails: ClusterDetails, objectTypesToFetch: Set, + labelSelector: string, ): Promise; } diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index 1ad478bd89..e6f1f2355a 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -14,7 +14,12 @@ It is only meant for local development, and the setup for it can be found inside ## Surfacing your Kubernetes components as part of an entity -### Adding the entity annotation +There are 2 ways to surface your kubernetes components as part of an entity. +The label selector takes precedence over the annotation/service id. + +### Common `backstage.io/kubernetes-id` label + +#### Adding the entity annotation In order for Backstage to detect that an entity has Kubernetes components, the following annotation should be added to the entity. @@ -24,7 +29,7 @@ annotations: 'backstage.io/kubernetes-id': dice-roller ``` -### Labeling Kubernetes components +#### Labeling Kubernetes components In order for Kubernetes components to show up in the service catalog as a part of an entity, Kubernetes components must be labeled with the following label: @@ -32,3 +37,19 @@ as a part of an entity, Kubernetes components must be labeled with the following ```yaml 'backstage.io/kubernetes-id': ``` + +### Full label selector + +#### Adding the entity label selector to the spec + +In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) + +```yaml +spec: + kubernetes: + selector: + matchLabels: + someKey: someValue + other-key: other-value + app.kubernetes.io/name: dice-roller +``` diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 86bb830046..faf9aaf024 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -20,6 +20,7 @@ import { AuthRequestBody, ObjectsByServiceIdResponse, } from '@backstage/plugin-kubernetes-backend'; +import { V1LabelSelector } from '@kubernetes/client-node'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; @@ -50,10 +51,23 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - async getObjectsByServiceId( + private parseLabelSelector(params: V1LabelSelector): string { + // TODO: figure out how to convert the selector to the full query param from the yaml + // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) + return Object.keys(params.matchLabels) + .map(key => `${key}=${params[key]}`) + .join(','); + } + + async getObjectsByLabelSelector( serviceId: String, + labelSelector: V1LabelSelector, requestBody: AuthRequestBody, ): Promise { - return await this.getRequired(`/services/${serviceId}`, requestBody); + const labelSelectorQueryParams = this.parseLabelSelector(labelSelector); + return await this.getRequired( + `/services/${serviceId}?labelSelector=${labelSelectorQueryParams}`, + requestBody, + ); } } diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 8e44d58e6a..2f13dcb02b 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -19,6 +19,7 @@ import { AuthRequestBody, ObjectsByServiceIdResponse, } from '@backstage/plugin-kubernetes-backend'; +import { V1LabelSelector } from '@kubernetes/client-node'; export const kubernetesApiRef = createApiRef({ id: 'plugin.kubernetes.service', @@ -27,8 +28,9 @@ export const kubernetesApiRef = createApiRef({ }); export interface KubernetesApi { - getObjectsByServiceId( + getObjectsByLabelSelector( serviceId: String, + labelSelector: V1LabelSelector, requestBody: AuthRequestBody, ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index a3e82e79a4..d46a888b39 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -41,6 +41,7 @@ import { ExtensionsV1beta1Ingress, V1ConfigMap, V1HorizontalPodAutoscaler, + V1LabelSelector, V1Service, } from '@kubernetes/client-node'; import { Services } from '../Services'; @@ -129,9 +130,27 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { ); } + // decide label selector to search by defaulting to this label + let labelSelector: V1LabelSelector = { + matchLabels: { + 'backstage.io/kubernetes-id': entity.metadata.name, + }, + }; + + if ( + entity.spec.kubernetes && + (entity.spec.kubernetes.selector as V1LabelSelector) + ) { + labelSelector = entity.spec.kubernetes.selector; + } + // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByServiceId(entity.metadata.name, requestBody) + .getObjectsByLabelSelector( + entity.metadata.name, + labelSelector, + requestBody, + ) .then(result => { setKubernetesObjects(result); }) From beaa0a82dbd267193fe7877cf806e977a8b1241e Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Tue, 20 Oct 2020 00:22:44 -0400 Subject: [PATCH 02/12] fix tests, types and cleanup --- .../src/service/KubernetesFetcher.test.ts | 45 +++++++++++++++++++ .../src/service/KubernetesFetcher.ts | 4 +- ...ubernetesObjectsByServiceIdHandler.test.ts | 18 ++++---- .../kubernetes-backend/src/service/router.ts | 2 +- .../src/api/KubernetesBackendClient.ts | 5 ++- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 8c562b5200..f37a81f9f8 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -66,6 +66,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['pods', 'services']), + '', ); expect(result).toStrictEqual({ @@ -129,6 +130,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['pods', 'services']), + '', ); expect(result).toStrictEqual({ @@ -178,6 +180,7 @@ describe('KubernetesClientProvider', () => { authProvider: 'serviceAccount', }, new Set(['foo']), + '', ), ).toThrow('unrecognised type=foo'); @@ -254,4 +257,46 @@ describe('KubernetesClientProvider', () => { }, ); }); + it('should always add a labelSelector query', async () => { + clientMock.listPodForAllNamespaces.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listServiceForAllNamespaces.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService( + 'some-service', + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['pods', 'services']), + '', + ); + + const mockCall = clientMock.listPodForAllNamespaces.mock.calls[0]; + const actualSelector = mockCall[mockCall.length - 1]; + const expectedSelector = 'backstage.io/kubernetes-id=some-service'; + expect(actualSelector).toBe(expectedSelector); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 3bfb651d52..a1ed21552b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -116,7 +116,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { serviceId, clusterDetails, type, - labelSelector, + labelSelector.length !== 0 + ? labelSelector + : `backstage.io/kubernetes-id=${serviceId}`, ).catch(captureKubernetesErrorsRethrowOthers); }); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index f413d080c6..2f779adfe8 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -20,7 +20,7 @@ import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; -const fetchObjectsByServiceId = jest.fn(); +const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); @@ -64,7 +64,7 @@ const mockFetch = (mock: jest.Mock) => { ); }; -describe('handleGetKubernetesObjectsByServiceId', () => { +describe('handleGetKubernetesObjectsForService', () => { beforeEach(() => { jest.resetAllMocks(); }); @@ -79,22 +79,23 @@ describe('handleGetKubernetesObjectsByServiceId', () => { ]), ); - mockFetch(fetchObjectsByServiceId); + mockFetch(fetchObjectsForService); const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsForService: fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsForService, }, { getClustersByServiceId, }, getVoidLogger(), {}, + '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); - expect(fetchObjectsByServiceId.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(1); expect(result).toStrictEqual({ items: [ { @@ -153,12 +154,12 @@ describe('handleGetKubernetesObjectsByServiceId', () => { ]), ); - mockFetch(fetchObjectsByServiceId); + mockFetch(fetchObjectsForService); const result = await handleGetKubernetesObjectsForService( TEST_SERVICE_ID, { - fetchObjectsForService: fetchObjectsByServiceId, + fetchObjectsForService: fetchObjectsForService, }, { getClustersByServiceId, @@ -169,10 +170,11 @@ describe('handleGetKubernetesObjectsByServiceId', () => { google: 'google_token_123', }, }, + '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); - expect(fetchObjectsByServiceId.mock.calls.length).toBe(2); + expect(fetchObjectsForService.mock.calls.length).toBe(2); expect(result).toStrictEqual({ items: [ { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 5462b5c9fb..fada4c7888 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -80,7 +80,7 @@ export const makeRouter = ( serviceLocator, logger, requestBody, - labelSelector, + labelSelector ? labelSelector.toString() : '', ); res.send(response); } catch (e) { diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index faf9aaf024..c6d0153538 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -54,8 +54,9 @@ export class KubernetesBackendClient implements KubernetesApi { private parseLabelSelector(params: V1LabelSelector): string { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - return Object.keys(params.matchLabels) - .map(key => `${key}=${params[key]}`) + const { matchLabels } = params; + return Object.keys(matchLabels) + .map(key => `${key}=${matchLabels[key]}`) .join(','); } From 61a552fac6133af81be6ee1d59aeec29240ef53e Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Tue, 20 Oct 2020 00:49:23 -0400 Subject: [PATCH 03/12] fix type checks --- .../src/service/KubernetesFetcher.ts | 29 ++++--------------- .../src/api/KubernetesBackendClient.ts | 5 +++- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index a1ed21552b..11917efc52 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -113,7 +113,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { ): Promise { const fetchResults = Array.from(objectTypesToFetch).map(type => { return this.fetchByObjectType( - serviceId, clusterDetails, type, labelSelector.length !== 0 @@ -127,54 +126,45 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { // TODO could probably do with a tidy up private fetchByObjectType( - serviceId: string, clusterDetails: ClusterDetails, type: KubernetesObjectTypes, labelSelector: string, ): Promise { switch (type) { case 'pods': - return this.fetchPodsForService( - serviceId, - clusterDetails, - labelSelector, - ).then(r => ({ - type: type, - resources: r, - })); + return this.fetchPodsForService(clusterDetails, labelSelector).then( + r => ({ + type: type, + resources: r, + }), + ); case 'configmaps': return this.fetchConfigMapsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'deployments': return this.fetchDeploymentsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'replicasets': return this.fetchReplicaSetsForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': return this.fetchServicesForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'horizontalpodautoscalers': return this.fetchHorizontalPodAutoscalersForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); case 'ingresses': return this.fetchIngressesForService( - serviceId, clusterDetails, labelSelector, ).then(r => ({ type: type, resources: r })); @@ -210,7 +200,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchServicesForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -220,7 +209,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchPodsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -230,7 +218,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchConfigMapsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -240,7 +227,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchDeploymentsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -250,7 +236,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchReplicaSetsForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -260,7 +245,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchHorizontalPodAutoscalersForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { @@ -277,7 +261,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } private fetchIngressesForService( - serviceId: string, clusterDetails: ClusterDetails, labelSelector: string, ): Promise> { diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index c6d0153538..088196b0c8 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -55,8 +55,11 @@ export class KubernetesBackendClient implements KubernetesApi { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) const { matchLabels } = params; + if (!matchLabels) { + return ''; + } return Object.keys(matchLabels) - .map(key => `${key}=${matchLabels[key]}`) + .map(key => `${key}=${matchLabels[key.toString()]}`) .join(','); } From 99fffed216b5f2f0030627b45cea7c20e9c72fbd Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Sat, 7 Nov 2020 19:44:19 -0500 Subject: [PATCH 04/12] fix types --- .../src/kinds/ComponentEntityV1alpha1.ts | 16 ++++++++++++++++ .../KubernetesContent/KubernetesContent.tsx | 10 ++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 9d46dea03f..0e1e369e44 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -30,6 +30,15 @@ const schema = yup.object>({ lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), implementsApis: yup.array(yup.string().required()).notRequired(), + kubernetes: yup + .object({ + selector: yup + .object({ + matchLabels: yup.object().required(), + }) + .required(), + }) + .notRequired(), }) .required(), }); @@ -42,6 +51,13 @@ export interface ComponentEntityV1alpha1 extends Entity { lifecycle: string; owner: string; implementsApis?: string[]; + kubernetes?: { + selector: { + matchLabels: { + [key: string]: string; + }; + }; + }; }; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index d46a888b39..3f9c1a866f 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -26,7 +26,7 @@ import { TabbedCard, useApi, } from '@backstage/core'; -import { Entity } from '@backstage/catalog-model'; +import { ComponentEntityV1alpha1, Entity } from '@backstage/catalog-model'; import { kubernetesApiRef } from '../../api/types'; import { AuthRequestBody, @@ -137,11 +137,13 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { }, }; + const componentEntity = entity as ComponentEntityV1alpha1; if ( - entity.spec.kubernetes && - (entity.spec.kubernetes.selector as V1LabelSelector) + componentEntity.spec && + componentEntity.spec.kubernetes && + (componentEntity.spec.kubernetes.selector as V1LabelSelector) ) { - labelSelector = entity.spec.kubernetes.selector; + labelSelector = componentEntity.spec.kubernetes.selector; } // TODO: Add validation on contents/format of requestBody From 4639d20720b0ba3c4fa48d0f1fbc2b417b90e6bf Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:42:08 -0500 Subject: [PATCH 05/12] modify approach to post entire entity to k8s backend --- .../GoogleKubernetesAuthTranslator.ts | 4 +-- .../ServiceAccountKubernetesAuthTranslator.ts | 4 +-- .../src/kubernetes-auth-translator/types.ts | 4 +-- ...ubernetesObjectsByServiceIdHandler.test.ts | 1 - .../getKubernetesObjectsForServiceHandler.ts | 33 +++++++++++++++---- .../src/service/router.test.ts | 4 +-- .../kubernetes-backend/src/service/router.ts | 10 +++--- plugins/kubernetes-backend/src/types/types.ts | 6 ++-- .../src/api/KubernetesBackendClient.ts | 28 ++++------------ plugins/kubernetes/src/api/types.ts | 10 +++--- .../KubernetesContent/KubernetesContent.tsx | 32 ++++-------------- .../GoogleKubernetesAuthProvider.ts | 6 ++-- .../KubernetesAuthProviders.ts | 6 ++-- .../ServiceAccountKubernetesAuthProvider.ts | 6 ++-- .../src/kubernetes-auth-provider/types.ts | 10 +++--- 15 files changed, 74 insertions(+), 90 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts index 7f9b2f0bae..0dd2a4bcc2 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts @@ -15,13 +15,13 @@ */ import { KubernetesAuthTranslator } from './types'; -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export class GoogleKubernetesAuthTranslator implements KubernetesAuthTranslator { async decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise { const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( {}, diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts index ecf2f12b72..6433e41546 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts @@ -15,7 +15,7 @@ */ import { KubernetesAuthTranslator } from './types'; -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export class ServiceAccountKubernetesAuthTranslator implements KubernetesAuthTranslator { @@ -23,7 +23,7 @@ export class ServiceAccountKubernetesAuthTranslator clusterDetails: ClusterDetails, // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read. // @ts-ignore-start - requestBody: AuthRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars + requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars // @ts-ignore-end ): Promise { return clusterDetails; diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts index f89e04456f..c01a57889c 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { AuthRequestBody, ClusterDetails } from '../types/types'; +import { KubernetesRequestBody, ClusterDetails } from '../types/types'; export interface KubernetesAuthTranslator { decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise; } diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 2f779adfe8..f95b9a7242 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -91,7 +91,6 @@ describe('handleGetKubernetesObjectsForService', () => { }, getVoidLogger(), {}, - '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 5f31248790..46f3f27c73 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -16,25 +16,25 @@ import { Logger } from 'winston'; import { - AuthRequestBody, + KubernetesRequestBody, ClusterDetails, KubernetesServiceLocator, KubernetesFetcher, KubernetesObjectTypes, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '../types/types'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, logger: Logger, - requestBody: AuthRequestBody, - labelSelector: string, + requestBody: KubernetesRequestBody, objectsToFetch?: Set, -) => Promise; +) => Promise; const DEFAULT_OBJECTS = new Set([ 'pods', @@ -46,6 +46,26 @@ const DEFAULT_OBJECTS = new Set([ 'ingresses', ]); +function parseLabelSelector(entity: ComponentEntityV1alpha1): string { + if ( + entity && + entity.spec && + entity.spec.kubernetes && + entity.spec.kubernetes.selector + ) { + // TODO: figure out how to convert the selector to the full query param from the yaml + // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) + const { matchLabels } = entity.spec.kubernetes.selector; + if (!matchLabels) { + return ''; + } + return Object.keys(matchLabels) + .map(key => `${key}=${matchLabels[key.toString()]}`) + .join(','); + } + return ''; +} + // Fans out the request to all clusters that the service lives in, aggregates their responses together export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServiceHandler = async ( serviceId, @@ -53,7 +73,6 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic serviceLocator, logger, requestBody, - labelSelector: string, objectsToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( @@ -80,6 +99,8 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic .join(', ')}]`, ); + const labelSelector = parseLabelSelector(requestBody.entity); + return Promise.all( clusterDetailsDecoratedForAuth.map(cd => { return fetcher diff --git a/plugins/kubernetes-backend/src/service/router.test.ts b/plugins/kubernetes-backend/src/service/router.test.ts index b63b63ead6..a407ce9693 100644 --- a/plugins/kubernetes-backend/src/service/router.test.ts +++ b/plugins/kubernetes-backend/src/service/router.test.ts @@ -21,14 +21,14 @@ import { makeRouter } from './router'; import { KubernetesServiceLocator, KubernetesFetcher, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '..'; describe('router', () => { let app: express.Express; let kubernetesFetcher: jest.Mocked; let kubernetesServiceLocator: jest.Mocked; - let handleGetByServiceId: jest.Mock>; + let handleGetByServiceId: jest.Mock>; beforeAll(async () => { kubernetesFetcher = { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index fada4c7888..38a02ad970 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -26,7 +26,7 @@ import { handleGetKubernetesObjectsForService, } from './getKubernetesObjectsForServiceHandler'; import { - AuthRequestBody, + KubernetesRequestBody, KubernetesServiceLocator, KubernetesFetcher, ServiceLocatorMethod, @@ -64,23 +64,21 @@ export const makeRouter = ( logger: Logger, fetcher: KubernetesFetcher, serviceLocator: KubernetesServiceLocator, - handleGetByServiceId: GetKubernetesObjectsForServiceHandler, + handleGetByEntity: GetKubernetesObjectsForServiceHandler, ): express.Router => { const router = Router(); router.use(express.json()); router.post('/services/:serviceId', async (req, res) => { const serviceId = req.params.serviceId; - const labelSelector = req.query.labelSelector; - const requestBody: AuthRequestBody = req.body; + const requestBody: KubernetesRequestBody = req.body; try { - const response = await handleGetByServiceId( + const response = await handleGetByEntity( serviceId, fetcher, serviceLocator, logger, requestBody, - labelSelector ? labelSelector.toString() : '', ); res.send(response); } catch (e) { diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 0425687f48..738bc89b21 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -23,6 +23,7 @@ import { V1ReplicaSet, V1Service, } from '@kubernetes/client-node'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export interface ClusterDetails { name: string; @@ -31,10 +32,11 @@ export interface ClusterDetails { serviceAccountToken?: string | undefined; } -export interface AuthRequestBody { +export interface KubernetesRequestBody { auth?: { google?: string; }; + entity: ComponentEntityV1alpha1; } export interface ClusterObjects { @@ -43,7 +45,7 @@ export interface ClusterObjects { errors: KubernetesFetchError[]; } -export interface ObjectsByServiceIdResponse { +export interface ObjectsByEntityResponse { items: ClusterObjects[]; } diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 088196b0c8..d8fa800ab6 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -17,10 +17,9 @@ import { DiscoveryApi } from '@backstage/core'; import { KubernetesApi } from './types'; import { - AuthRequestBody, - ObjectsByServiceIdResponse, + KubernetesRequestBody, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; -import { V1LabelSelector } from '@kubernetes/client-node'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; @@ -31,7 +30,7 @@ export class KubernetesBackendClient implements KubernetesApi { private async getRequired( path: string, - requestBody: AuthRequestBody, + requestBody: KubernetesRequestBody, ): Promise { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`; const response = await fetch(url, { @@ -51,26 +50,11 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - private parseLabelSelector(params: V1LabelSelector): string { - // TODO: figure out how to convert the selector to the full query param from the yaml - // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - const { matchLabels } = params; - if (!matchLabels) { - return ''; - } - return Object.keys(matchLabels) - .map(key => `${key}=${matchLabels[key.toString()]}`) - .join(','); - } - async getObjectsByLabelSelector( - serviceId: String, - labelSelector: V1LabelSelector, - requestBody: AuthRequestBody, - ): Promise { - const labelSelectorQueryParams = this.parseLabelSelector(labelSelector); + requestBody: KubernetesRequestBody, + ): Promise { return await this.getRequired( - `/services/${serviceId}?labelSelector=${labelSelectorQueryParams}`, + `/services/${requestBody.entity.metadata.name}`, requestBody, ); } diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 2f13dcb02b..663841ddf9 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -16,8 +16,8 @@ import { createApiRef } from '@backstage/core'; import { - AuthRequestBody, - ObjectsByServiceIdResponse, + KubernetesRequestBody, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; import { V1LabelSelector } from '@kubernetes/client-node'; @@ -29,8 +29,6 @@ export const kubernetesApiRef = createApiRef({ export interface KubernetesApi { getObjectsByLabelSelector( - serviceId: String, - labelSelector: V1LabelSelector, - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 3f9c1a866f..4e1560be70 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -29,10 +29,10 @@ import { import { ComponentEntityV1alpha1, Entity } from '@backstage/catalog-model'; import { kubernetesApiRef } from '../../api/types'; import { - AuthRequestBody, + KubernetesRequestBody, ClusterObjects, FetchResponse, - ObjectsByServiceIdResponse, + ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; import { kubernetesAuthProvidersApiRef } from '../../kubernetes-auth-provider/types'; import { DeploymentTables } from '../DeploymentTables'; @@ -105,7 +105,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { const kubernetesApi = useApi(kubernetesApiRef); const [kubernetesObjects, setKubernetesObjects] = useState< - ObjectsByServiceIdResponse | undefined + ObjectsByEntityResponse | undefined >(undefined); const [error, setError] = useState(undefined); @@ -121,7 +121,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { useEffect(() => { (async () => { // For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider - let requestBody: AuthRequestBody = {}; + let requestBody: KubernetesRequestBody = { + entity: entity as ComponentEntityV1alpha1, + }; for (const authProviderStr of authProviders) { // Multiple asyncs done sequentially instead of all at once to prevent same requestBody from being modified simultaneously requestBody = await kubernetesAuthProvidersApi.decorateRequestBodyForAuth( @@ -130,29 +132,9 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { ); } - // decide label selector to search by defaulting to this label - let labelSelector: V1LabelSelector = { - matchLabels: { - 'backstage.io/kubernetes-id': entity.metadata.name, - }, - }; - - const componentEntity = entity as ComponentEntityV1alpha1; - if ( - componentEntity.spec && - componentEntity.spec.kubernetes && - (componentEntity.spec.kubernetes.selector as V1LabelSelector) - ) { - labelSelector = componentEntity.spec.kubernetes.selector; - } - // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByLabelSelector( - entity.metadata.name, - labelSelector, - requestBody, - ) + .getObjectsByLabelSelector(requestBody) .then(result => { setKubernetesObjects(result); }) diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts index 7de5c7c0a9..570806a1a4 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts @@ -16,7 +16,7 @@ import { OAuthApi } from '@backstage/core'; import { KubernetesAuthProvider } from './types'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { authProvider: OAuthApi; @@ -26,8 +26,8 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { } async decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { const googleAuthToken: string = await this.authProvider.getAccessToken( 'https://www.googleapis.com/auth/cloud-platform', ); diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index ac909d6695..9f64af9c2e 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -15,7 +15,7 @@ */ import { OAuthApi } from '@backstage/core'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; @@ -40,8 +40,8 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { async decorateRequestBodyForAuth( authProvider: string, - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { const kubernetesAuthProvider: | KubernetesAuthProvider | undefined = this.kubernetesAuthProviderMap.get(authProvider); diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts index 3ac5a9494b..5d33521ae4 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts @@ -15,13 +15,13 @@ */ import { KubernetesAuthProvider } from './types'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export class ServiceAccountKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise { + requestBody: KubernetesRequestBody, + ): Promise { // No-op, with service account for auth, cluster config/details should already have serviceAccountToken return requestBody; } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts index 24fee0f94c..5bd253d2c1 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts @@ -15,12 +15,12 @@ */ import { createApiRef } from '@backstage/core'; -import { AuthRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; export interface KubernetesAuthProvider { decorateRequestBodyForAuth( - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } export const kubernetesAuthProvidersApiRef = createApiRef< @@ -33,6 +33,6 @@ export const kubernetesAuthProvidersApiRef = createApiRef< export interface KubernetesAuthProvidersApi { decorateRequestBodyForAuth( authProvider: string, - requestBody: AuthRequestBody, - ): Promise; + requestBody: KubernetesRequestBody, + ): Promise; } From 1166fcc36e22c7e4de184ca9e02479d84a14d72c Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:44:15 -0500 Subject: [PATCH 06/12] add changeset --- .changeset/chatty-radios-beam.md | 7 +++++++ plugins/kubernetes-backend/package.json | 1 + plugins/kubernetes/src/api/types.ts | 1 - .../src/components/KubernetesContent/KubernetesContent.tsx | 1 - 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/chatty-radios-beam.md diff --git a/.changeset/chatty-radios-beam.md b/.changeset/chatty-radios-beam.md new file mode 100644 index 0000000000..91ad76ee88 --- /dev/null +++ b/.changeset/chatty-radios-beam.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-kubernetes-backend': minor +--- + +add kubernetes selector to component model diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index e58bb4f047..be7b86dd52 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@backstage/backend-common": "^0.3.0", + "@backstage/catalog-model": "^0.2.0", "@backstage/config": "^0.1.1", "@kubernetes/client-node": "^0.12.1", "@types/express": "^4.17.6", diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 663841ddf9..90c9e33504 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -19,7 +19,6 @@ import { KubernetesRequestBody, ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-backend'; -import { V1LabelSelector } from '@kubernetes/client-node'; export const kubernetesApiRef = createApiRef({ id: 'plugin.kubernetes.service', diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index 4e1560be70..f87ab04ece 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -41,7 +41,6 @@ import { ExtensionsV1beta1Ingress, V1ConfigMap, V1HorizontalPodAutoscaler, - V1LabelSelector, V1Service, } from '@kubernetes/client-node'; import { Services } from '../Services'; From a2f791836e80386fca7fb7613cb3e073fb8e6ab5 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:45:54 -0500 Subject: [PATCH 07/12] refactor name of api method --- plugins/kubernetes/src/api/KubernetesBackendClient.ts | 2 +- plugins/kubernetes/src/api/types.ts | 2 +- .../src/components/KubernetesContent/KubernetesContent.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index d8fa800ab6..b81a799d6b 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -50,7 +50,7 @@ export class KubernetesBackendClient implements KubernetesApi { return await response.json(); } - async getObjectsByLabelSelector( + async getObjectsByEntity( requestBody: KubernetesRequestBody, ): Promise { return await this.getRequired( diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 90c9e33504..498e74a626 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -27,7 +27,7 @@ export const kubernetesApiRef = createApiRef({ }); export interface KubernetesApi { - getObjectsByLabelSelector( + getObjectsByEntity( requestBody: KubernetesRequestBody, ): Promise; } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index f87ab04ece..14cda3b5f7 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -133,7 +133,7 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { // TODO: Add validation on contents/format of requestBody kubernetesApi - .getObjectsByLabelSelector(requestBody) + .getObjectsByEntity(requestBody) .then(result => { setKubernetesObjects(result); }) From 75d63ee0a08cd07169dae07eae4bda871de4d718 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 01:58:33 -0500 Subject: [PATCH 08/12] fix test --- ...ubernetesObjectsByServiceIdHandler.test.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index f95b9a7242..4fdd65f474 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -16,6 +16,7 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; @@ -24,6 +25,26 @@ const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); +const goodEntity: ComponentEntityV1alpha1 = { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', + kubernetes: { + selector: { + matchLabels: { + 'backstage.io/test-label': 'test-component', + }, + }, + }, + }, +}; + const mockFetch = (mock: jest.Mock) => { mock.mockImplementation((serviceId: string, clusterDetails: ClusterDetails) => Promise.resolve({ @@ -90,7 +111,7 @@ describe('handleGetKubernetesObjectsForService', () => { getClustersByServiceId, }, getVoidLogger(), - {}, + { entity: goodEntity }, ); expect(getClustersByServiceId.mock.calls.length).toBe(1); @@ -165,11 +186,11 @@ describe('handleGetKubernetesObjectsForService', () => { }, getVoidLogger(), { + entity: goodEntity, auth: { google: 'google_token_123', }, }, - '', ); expect(getClustersByServiceId.mock.calls.length).toBe(1); From 728767e3d39a0fd0c149357ecf96da2275f91809 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:00:13 -0500 Subject: [PATCH 09/12] cleanup --- .../src/service/KubernetesFetcher.test.ts | 49 ++++++++++--------- .../src/service/KubernetesFetcher.ts | 15 +++--- ...ubernetesObjectsByServiceIdHandler.test.ts | 2 +- .../getKubernetesObjectsForServiceHandler.ts | 28 +++++------ plugins/kubernetes-backend/src/types/types.ts | 12 +++-- plugins/kubernetes/README.md | 36 +++++++------- 6 files changed, 71 insertions(+), 71 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index f37a81f9f8..29233cf7fd 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -16,6 +16,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; +import { ObjectFetchParams } from '..'; describe('KubernetesClientProvider', () => { let clientMock: any; @@ -57,17 +58,17 @@ describe('KubernetesClientProvider', () => { clientMock.listServiceForAllNamespaces.mockRejectedValue(errorResponse); - const result = await sut.fetchObjectsForService( - 'some-service', - { + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); expect(result).toStrictEqual({ errors: [expectedResult], @@ -121,17 +122,17 @@ describe('KubernetesClientProvider', () => { }, }); - const result = await sut.fetchObjectsForService( - 'some-service', - { + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); expect(result).toStrictEqual({ errors: [], @@ -171,17 +172,17 @@ describe('KubernetesClientProvider', () => { }); it('should throw error on unknown type', () => { expect(() => - sut.fetchObjectsForService( - 'some-service', - { + sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['foo']), - '', - ), + objectTypesToFetch: new Set(['foo']), + labelSelector: '', + }), ).toThrow('unrecognised type=foo'); expect(clientMock.listPodForAllNamespaces.mock.calls.length).toBe(0); @@ -282,17 +283,17 @@ describe('KubernetesClientProvider', () => { }, }); - await sut.fetchObjectsForService( - 'some-service', - { + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', serviceAccountToken: 'token', authProvider: 'serviceAccount', }, - new Set(['pods', 'services']), - '', - ); + objectTypesToFetch: new Set(['pods', 'services']), + labelSelector: '', + }); const mockCall = clientMock.listPodForAllNamespaces.mock.calls[0]; const actualSelector = mockCall[mockCall.length - 1]; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 11917efc52..d9c6ee6d1a 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -38,6 +38,7 @@ import { FetchResponseWrapper, KubernetesFetchError, KubernetesErrorTypes, + ObjectFetchParams, } from '..'; import lodash, { Dictionary } from 'lodash'; @@ -106,18 +107,14 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } fetchObjectsForService( - serviceId: string, - clusterDetails: ClusterDetails, - objectTypesToFetch: Set, - labelSelector: string, + params: ObjectFetchParams, ): Promise { - const fetchResults = Array.from(objectTypesToFetch).map(type => { + const fetchResults = Array.from(params.objectTypesToFetch).map(type => { return this.fetchByObjectType( - clusterDetails, + params.clusterDetails, type, - labelSelector.length !== 0 - ? labelSelector - : `backstage.io/kubernetes-id=${serviceId}`, + params.labelSelector || + `backstage.io/kubernetes-id=${params.serviceId}`, ).catch(captureKubernetesErrorsRethrowOthers); }); diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 4fdd65f474..789c7062b9 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -25,7 +25,7 @@ const fetchObjectsForService = jest.fn(); const getClustersByServiceId = jest.fn(); -const goodEntity: ComponentEntityV1alpha1 = { +const goodEntity: ComponentEntityV1alpha1 = { apiVersion: 'backstage.io/v1beta1', kind: 'Component', metadata: { diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 46f3f27c73..cec4fa8b66 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -15,6 +15,7 @@ */ import { Logger } from 'winston'; +import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; import { KubernetesRequestBody, ClusterDetails, @@ -22,10 +23,10 @@ import { KubernetesFetcher, KubernetesObjectTypes, ObjectsByEntityResponse, + ObjectFetchParams, } from '../types/types'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; -import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; export type GetKubernetesObjectsForServiceHandler = ( serviceId: string, @@ -47,18 +48,10 @@ const DEFAULT_OBJECTS = new Set([ ]); function parseLabelSelector(entity: ComponentEntityV1alpha1): string { - if ( - entity && - entity.spec && - entity.spec.kubernetes && - entity.spec.kubernetes.selector - ) { + const matchLabels = entity?.spec?.kubernetes?.selector?.matchLabels; + if (matchLabels) { // TODO: figure out how to convert the selector to the full query param from the yaml // (as shown here https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go) - const { matchLabels } = entity.spec.kubernetes.selector; - if (!matchLabels) { - return ''; - } return Object.keys(matchLabels) .map(key => `${key}=${matchLabels[key.toString()]}`) .join(','); @@ -73,7 +66,7 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic serviceLocator, logger, requestBody, - objectsToFetch = DEFAULT_OBJECTS, + objectTypesToFetch = DEFAULT_OBJECTS, ) => { const clusterDetails: ClusterDetails[] = await serviceLocator.getClustersByServiceId( serviceId, @@ -102,13 +95,18 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic const labelSelector = parseLabelSelector(requestBody.entity); return Promise.all( - clusterDetailsDecoratedForAuth.map(cd => { + clusterDetailsDecoratedForAuth.map(clusterDetails => { return fetcher - .fetchObjectsForService(serviceId, cd, objectsToFetch, labelSelector) + .fetchObjectsForService({ + serviceId, + clusterDetails, + objectTypesToFetch, + labelSelector, + }) .then(result => { return { cluster: { - name: cd.name, + name: clusterDetails.name, }, resources: result.responses, errors: result.errors, diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 738bc89b21..b2a5ef432c 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -109,14 +109,18 @@ export interface IngressesFetchResponse { resources: Array; } +export interface ObjectFetchParams { + serviceId: string; + clusterDetails: ClusterDetails; + objectTypesToFetch: Set; + labelSelector: string; +} + // Fetches information from a kubernetes cluster using the cluster details object // to target a specific cluster export interface KubernetesFetcher { fetchObjectsForService( - serviceId: string, - clusterDetails: ClusterDetails, - objectTypesToFetch: Set, - labelSelector: string, + params: ObjectFetchParams, ): Promise; } diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index e6f1f2355a..8f3542a46e 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -14,10 +14,26 @@ It is only meant for local development, and the setup for it can be found inside ## Surfacing your Kubernetes components as part of an entity -There are 2 ways to surface your kubernetes components as part of an entity. +There are two ways to surface your kubernetes components as part of an entity. The label selector takes precedence over the annotation/service id. -### Common `backstage.io/kubernetes-id` label +### Full label selector + +#### Adding the entity label selector to the spec + +In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) + +```yaml +spec: + kubernetes: + selector: + matchLabels: + someKey: someValue + other-key: other-value + app.kubernetes.io/name: dice-roller +``` + +### Common `backstage.io/kubernetes-id` label on objects #### Adding the entity annotation @@ -37,19 +53,3 @@ as a part of an entity, Kubernetes components must be labeled with the following ```yaml 'backstage.io/kubernetes-id': ``` - -### Full label selector - -#### Adding the entity label selector to the spec - -In order for Backstage to detect that an entity has kubernetes components the `kubernetes.selector` must have a valid selector. (Currently only matchLabels is supported) - -```yaml -spec: - kubernetes: - selector: - matchLabels: - someKey: someValue - other-key: other-value - app.kubernetes.io/name: dice-roller -``` From 74102d917635bf59c3062d64b846151844c0d8cd Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:20:32 -0500 Subject: [PATCH 10/12] fix test after refactor --- .../getKubernetesObjectsByServiceIdHandler.test.ts | 8 ++++---- .../src/service/getKubernetesObjectsForServiceHandler.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index 789c7062b9..fd76cab662 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -46,7 +46,7 @@ const goodEntity: ComponentEntityV1alpha1 = { }; const mockFetch = (mock: jest.Mock) => { - mock.mockImplementation((serviceId: string, clusterDetails: ClusterDetails) => + mock.mockImplementation((params: ObjectFetchParams) => Promise.resolve({ errors: [], responses: [ @@ -55,7 +55,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-pods-${serviceId}-${clusterDetails.name}`, + name: `my-pods-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], @@ -65,7 +65,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-configmaps-${serviceId}-${clusterDetails.name}`, + name: `my-configmaps-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], @@ -75,7 +75,7 @@ const mockFetch = (mock: jest.Mock) => { resources: [ { metadata: { - name: `my-services-${serviceId}-${clusterDetails.name}`, + name: `my-services-${params.serviceId}-${params.clusterDetails.name}`, }, }, ], diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index cec4fa8b66..1b0cf4f783 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -34,7 +34,7 @@ export type GetKubernetesObjectsForServiceHandler = ( serviceLocator: KubernetesServiceLocator, logger: Logger, requestBody: KubernetesRequestBody, - objectsToFetch?: Set, + objectTypesToFetch?: Set, ) => Promise; const DEFAULT_OBJECTS = new Set([ From 44ea3101a7e6659a6dbae7e20dd1e953078b9367 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:23:40 -0500 Subject: [PATCH 11/12] remove unneeded import --- .../src/service/getKubernetesObjectsByServiceIdHandler.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index fd76cab662..bb0ffddc26 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -17,7 +17,6 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; -import { ClusterDetails } from '..'; const TEST_SERVICE_ID = 'my-service'; From 18d0fd10a03c941ed87b734eada71a284586f8f3 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Mon, 9 Nov 2020 14:49:12 -0500 Subject: [PATCH 12/12] add needed import --- .../src/service/getKubernetesObjectsByServiceIdHandler.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts index bb0ffddc26..adebebe1c5 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsByServiceIdHandler.test.ts @@ -17,6 +17,7 @@ import { handleGetKubernetesObjectsForService } from './getKubernetesObjectsForServiceHandler'; import { getVoidLogger } from '@backstage/backend-common'; import { ComponentEntityV1alpha1 } from '@backstage/catalog-model'; +import { ObjectFetchParams } from '..'; const TEST_SERVICE_ID = 'my-service';