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 -```