diff --git a/.changeset/tender-days-tickle.md b/.changeset/tender-days-tickle.md new file mode 100644 index 0000000000..13852fb3de --- /dev/null +++ b/.changeset/tender-days-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Add kubernetes namespace annotation `backstage.io/kubernetes-namespace` to allow namespaced Kubernetes resources fetches diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index dd7e666be4..78f386ae70 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -355,6 +355,16 @@ annotations: 'backstage.io/kubernetes-id': dice-roller ``` +#### Adding the namespace annotation + +Entities can have the `backstage.io/kubernetes-namespace` annotation, this will cause the entity's Kubernetes resources +to by looked up via that namespace. + +```yaml +annotations: + 'backstage.io/kubernetes-namespace': dice-space +``` + #### Labeling Kubernetes components In order for Kubernetes components to show up in the software catalog as a part diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 691b9443e5..9eca295f96 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -245,6 +245,8 @@ export interface ObjectFetchParams { // (undocumented) labelSelector: string; // (undocumented) + namespace?: string; + // (undocumented) objectTypesToFetch: Set; // (undocumented) serviceId: string; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 0db65d87d7..00090e57ac 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -207,6 +207,11 @@ export class KubernetesFanOutHandler { 'backstage.io/kubernetes-label-selector' ] || `backstage.io/kubernetes-id=${entityName}`; + const namespace = + requestBody.entity?.metadata?.annotations?.[ + 'backstage.io/kubernetes-namespace' + ]; + return Promise.all( clusterDetailsDecoratedForAuth.map(clusterDetailsItem => { return this.fetcher @@ -216,19 +221,26 @@ export class KubernetesFanOutHandler { objectTypesToFetch: this.objectTypesToFetch, labelSelector, customResources: this.customResources, + namespace, }) .then(result => this.getMetricsForPods(clusterDetailsItem, result)) .then(r => this.toClusterObjects(clusterDetailsItem, r)); }), - ).then(r => ({ - items: r.filter( + ).then(this.toObjectsByEntityResponse); + } + + toObjectsByEntityResponse( + clusterObjects: ClusterObjects[], + ): ObjectsByEntityResponse { + return { + items: clusterObjects.filter( item => (item.errors !== undefined && item.errors.length >= 1) || (item.resources !== undefined && item.resources.length >= 1 && item.resources.some(fr => fr.resources.length >= 1)), ), - })); + }; } toClusterObjects( diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index e1f701b75c..25a5c9312b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -42,6 +42,7 @@ describe('KubernetesFetcher', () => { jest.resetAllMocks(); clientMock = { listClusterCustomObject: jest.fn(), + listNamespacedCustomObject: jest.fn(), addInterceptor: jest.fn(), }; @@ -479,4 +480,47 @@ describe('KubernetesFetcher', () => { const expectedSelector = 'backstage.io/kubernetes-id=some-service'; expect(actualSelector).toBe(expectedSelector); }); + it('should use namespace if provided', async () => { + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + namespace: 'some-namespace', + customResources: [], + }); + + const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; + const namespace = mockCall[2]; + expect(namespace).toBe('some-namespace'); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index a2c3b9a6a8..1189dfa390 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -97,6 +97,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`, toFetch.objectType, + params.namespace, ).catch(this.captureKubernetesErrorsRethrowOthers.bind(this)); }); @@ -138,6 +139,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { resource: ObjectToFetch, labelSelector: string, objectType: KubernetesObjectTypes, + namespace?: string, ): Promise { const customObjects = this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails); @@ -146,6 +148,26 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/'); }); + if (namespace) { + return customObjects + .listNamespacedCustomObject( + resource.group, + resource.apiVersion, + namespace, + resource.plural, + '', + false, + '', + '', + labelSelector, + ) + .then(r => { + return { + type: objectType, + resources: (r.body as any).items, + }; + }); + } return customObjects .listClusterCustomObject( resource.group, diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 2d7e2d3da7..12c4d2fb48 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -34,6 +34,7 @@ export interface ObjectFetchParams { objectTypesToFetch: Set; labelSelector: string; customResources: CustomResource[]; + namespace?: string; } // Fetches information from a kubernetes cluster using the cluster details object