From 17a86fb29f6d3a80d1395649435e07540d4a88a8 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 21:55:40 -0400 Subject: [PATCH 1/6] add kubernetes namespace annotation Signed-off-by: Matthew Clarke --- .../src/service/KubernetesFanOutHandler.ts | 28 ++++++++---- .../src/service/KubernetesFetcher.test.ts | 44 +++++++++++++++++++ .../src/service/KubernetesFetcher.ts | 33 +++++++++++--- plugins/kubernetes-backend/src/types/types.ts | 1 + 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 0db65d87d7..e35230f34f 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,24 @@ 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( - item => - (item.errors !== undefined && item.errors.length >= 1) || - (item.resources !== undefined && - item.resources.length >= 1 && - item.resources.some(fr => fr.resources.length >= 1)), - ), - })); + ).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..5ba64dd53a 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..0e4f32b0c3 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,18 +139,21 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { resource: ObjectToFetch, labelSelector: string, objectType: KubernetesObjectTypes, + namespace?: string ): Promise { const customObjects = this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails); - customObjects.addInterceptor((requestOptions: any) => { - requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/'); - }); + customObjects.addInterceptor((requestOptions: any) => { + requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/'); + }); - return customObjects - .listClusterCustomObject( + if(namespace){ + return customObjects + .listNamespacedCustomObject( resource.group, resource.apiVersion, + namespace, resource.plural, '', false, @@ -163,5 +167,24 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { resources: (r.body as any).items, }; }); + } else { + return customObjects + .listClusterCustomObject( + resource.group, + resource.apiVersion, + resource.plural, + '', + false, + '', + '', + labelSelector, + ) + .then(r => { + return { + type: objectType, + resources: (r.body as any).items, + }; + }); + } } } 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 From 753a20c89e20208dc4f1859dddbf84ae7fc7c84d Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 22:01:03 -0400 Subject: [PATCH 2/6] add changeset Signed-off-by: Matthew Clarke --- .changeset/tender-days-tickle.md | 5 +++++ docs/features/kubernetes/configuration.md | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 .changeset/tender-days-tickle.md diff --git a/.changeset/tender-days-tickle.md b/.changeset/tender-days-tickle.md new file mode 100644 index 0000000000..c85e6f30d9 --- /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 force namespaced lookups for Kubernetes resources diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index dd7e666be4..dc4b2823e2 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 + +Additionally entities can have the `backstage.io/kubernetes-namespace` annotation to use namespaced +lookups instead of the default cluster wide lookups. + +```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 From 005b1648afbe1be0ab63f7791236d078170860ab Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 22:04:13 -0400 Subject: [PATCH 3/6] fmt Signed-off-by: Matthew Clarke --- .../src/service/KubernetesFanOutHandler.ts | 26 +++++----- .../src/service/KubernetesFetcher.test.ts | 6 +-- .../src/service/KubernetesFetcher.ts | 51 +++++++++---------- 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index e35230f34f..00090e57ac 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -207,7 +207,7 @@ export class KubernetesFanOutHandler { 'backstage.io/kubernetes-label-selector' ] || `backstage.io/kubernetes-id=${entityName}`; - const namespace = + const namespace = requestBody.entity?.metadata?.annotations?.[ 'backstage.io/kubernetes-namespace' ]; @@ -221,7 +221,7 @@ export class KubernetesFanOutHandler { objectTypesToFetch: this.objectTypesToFetch, labelSelector, customResources: this.customResources, - namespace + namespace, }) .then(result => this.getMetricsForPods(clusterDetailsItem, result)) .then(r => this.toClusterObjects(clusterDetailsItem, r)); @@ -229,16 +229,18 @@ export class KubernetesFanOutHandler { ).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)), - ), -} + 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 5ba64dd53a..25a5c9312b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -515,12 +515,12 @@ describe('KubernetesFetcher', () => { }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', - namespace: "some-namespace", + namespace: 'some-namespace', customResources: [], }); const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; - const namespace = mockCall[2] - expect(namespace).toBe("some-namespace"); + 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 0e4f32b0c3..1189dfa390 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -97,7 +97,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`, toFetch.objectType, - params.namespace + params.namespace, ).catch(this.captureKubernetesErrorsRethrowOthers.bind(this)); }); @@ -139,39 +139,21 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { resource: ObjectToFetch, labelSelector: string, objectType: KubernetesObjectTypes, - namespace?: string + namespace?: string, ): Promise { const customObjects = this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails); - customObjects.addInterceptor((requestOptions: any) => { - requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/'); - }); + customObjects.addInterceptor((requestOptions: any) => { + requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/'); + }); - if(namespace){ + 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, - }; - }); - } else { - return customObjects - .listClusterCustomObject( + .listNamespacedCustomObject( resource.group, resource.apiVersion, + namespace, resource.plural, '', false, @@ -186,5 +168,22 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { }; }); } + return customObjects + .listClusterCustomObject( + resource.group, + resource.apiVersion, + resource.plural, + '', + false, + '', + '', + labelSelector, + ) + .then(r => { + return { + type: objectType, + resources: (r.body as any).items, + }; + }); } } From 3c9d6cfb17e3b21c02976c229ac6cf0c884c2cd2 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 22:11:51 -0400 Subject: [PATCH 4/6] docs linting Signed-off-by: Matthew Clarke --- docs/features/kubernetes/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index dc4b2823e2..78f386ae70 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -357,8 +357,8 @@ annotations: #### Adding the namespace annotation -Additionally entities can have the `backstage.io/kubernetes-namespace` annotation to use namespaced -lookups instead of the default cluster wide lookups. +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: From 042ed65f50843391794883016bd195769f7910fb Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 22:13:51 -0400 Subject: [PATCH 5/6] docs verify Signed-off-by: Matthew Clarke --- .changeset/tender-days-tickle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tender-days-tickle.md b/.changeset/tender-days-tickle.md index c85e6f30d9..13852fb3de 100644 --- a/.changeset/tender-days-tickle.md +++ b/.changeset/tender-days-tickle.md @@ -2,4 +2,4 @@ '@backstage/plugin-kubernetes-backend': patch --- -Add kubernetes namespace annotation `backstage.io/kubernetes-namespace` to force namespaced lookups for Kubernetes resources +Add kubernetes namespace annotation `backstage.io/kubernetes-namespace` to allow namespaced Kubernetes resources fetches From 4a05c6453bdcbc6dc18382a3764dd4249511cfb3 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Mon, 11 Apr 2022 22:40:42 -0400 Subject: [PATCH 6/6] api report Signed-off-by: Matthew Clarke --- plugins/kubernetes-backend/api-report.md | 2 ++ 1 file changed, 2 insertions(+) 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;