From bb848ac5e826b330fde0858a616bc8d14efec9c8 Mon Sep 17 00:00:00 2001 From: nliesenfeld Date: Thu, 11 Mar 2021 18:21:28 -0500 Subject: [PATCH 1/3] Don't list the cluster if there are no resources deployed there Signed-off-by: nliesenfeld --- .../service/KubernetesFanOutHandler.test.ts | 396 ++++++++++++++++-- .../src/service/KubernetesFanOutHandler.ts | 10 +- .../KubernetesContent/KubernetesContent.tsx | 58 ++- 3 files changed, 422 insertions(+), 42 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index a45b1252fe..e76a114d54 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -24,43 +24,93 @@ const getClustersByServiceId = jest.fn(); const mockFetch = (mock: jest.Mock) => { mock.mockImplementation((params: ObjectFetchParams) => - Promise.resolve({ + Promise.resolve( + generateMockResourcesAndErrors( + params.serviceId, + params.clusterDetails.name, + ), + ), + ); +}; + +function generateMockResourcesAndErrors( + serviceId: String, + clusterName: String, +) { + if (clusterName === 'empty-cluster') { + return { errors: [], responses: [ { type: 'pods', - resources: [ - { - metadata: { - name: `my-pods-${params.serviceId}-${params.clusterDetails.name}`, - }, - }, - ], + resources: [], }, { type: 'configmaps', - resources: [ - { - metadata: { - name: `my-configmaps-${params.serviceId}-${params.clusterDetails.name}`, - }, - }, - ], + resources: [], }, { type: 'services', - resources: [ - { - metadata: { - name: `my-services-${params.serviceId}-${params.clusterDetails.name}`, - }, - }, - ], + resources: [], }, ], - }), - ); -}; + }; + } else if (clusterName === 'error-cluster') { + return { + errors: ['some random cluster error'], + responses: [ + { + type: 'pods', + resources: [], + }, + { + type: 'configmaps', + resources: [], + }, + { + type: 'services', + resources: [], + }, + ], + }; + } + + return { + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: `my-pods-${serviceId}-${clusterName}`, + }, + }, + ], + }, + { + type: 'configmaps', + resources: [ + { + metadata: { + name: `my-configmaps-${serviceId}-${clusterName}`, + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: `my-services-${serviceId}-${clusterName}`, + }, + }, + ], + }, + ], + }; +} describe('handleGetKubernetesObjectsForService', () => { beforeEach(() => { @@ -287,4 +337,300 @@ describe('handleGetKubernetesObjectsForService', () => { ], }); }); + it('retrieve objects for three clusters, only two have resources and show in ui', async () => { + getClustersByServiceId.mockImplementation(() => + Promise.resolve([ + { + name: 'test-cluster', + authProvider: 'serviceAccount', + }, + { + name: 'other-cluster', + authProvider: 'google', + }, + { + name: 'empty-cluster', + authProvider: 'google', + }, + ]), + ); + + mockFetch(fetchObjectsForService); + + const sut = new KubernetesFanOutHandler( + getVoidLogger(), + { + fetchObjectsForService, + }, + { + getClustersByServiceId, + }, + [], + ); + + const result = await sut.getKubernetesObjectsByEntity({ + auth: { + google: 'google_token_123', + }, + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'backstage.io/kubernetes-labels-selector': + 'backstage.io/test-label=test-component', + }, + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', + }, + }, + }); + + expect(getClustersByServiceId.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(3); + expect(result).toStrictEqual({ + items: [ + { + cluster: { + name: 'test-cluster', + }, + errors: [], + resources: [ + { + resources: [ + { + metadata: { + name: 'my-pods-test-component-test-cluster', + }, + }, + ], + type: 'pods', + }, + { + resources: [ + { + metadata: { + name: 'my-configmaps-test-component-test-cluster', + }, + }, + ], + type: 'configmaps', + }, + { + resources: [ + { + metadata: { + name: 'my-services-test-component-test-cluster', + }, + }, + ], + type: 'services', + }, + ], + }, + { + cluster: { + name: 'other-cluster', + }, + errors: [], + resources: [ + { + resources: [ + { + metadata: { + name: 'my-pods-test-component-other-cluster', + }, + }, + ], + type: 'pods', + }, + { + resources: [ + { + metadata: { + name: 'my-configmaps-test-component-other-cluster', + }, + }, + ], + type: 'configmaps', + }, + { + resources: [ + { + metadata: { + name: 'my-services-test-component-other-cluster', + }, + }, + ], + type: 'services', + }, + ], + }, + ], + }); + }); + it('retrieve objects for four clusters, two have resources and one error cluster', async () => { + getClustersByServiceId.mockImplementation(() => + Promise.resolve([ + { + name: 'test-cluster', + authProvider: 'serviceAccount', + }, + { + name: 'other-cluster', + authProvider: 'google', + }, + { + name: 'empty-cluster', + authProvider: 'google', + }, + { + name: 'error-cluster', + authProvider: 'google', + }, + ]), + ); + + mockFetch(fetchObjectsForService); + + const sut = new KubernetesFanOutHandler( + getVoidLogger(), + { + fetchObjectsForService, + }, + { + getClustersByServiceId, + }, + [], + ); + + const result = await sut.getKubernetesObjectsByEntity({ + auth: { + google: 'google_token_123', + }, + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'backstage.io/kubernetes-labels-selector': + 'backstage.io/test-label=test-component', + }, + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'joe', + }, + }, + }); + + expect(getClustersByServiceId.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(4); + expect(result).toStrictEqual({ + items: [ + { + cluster: { + name: 'test-cluster', + }, + errors: [], + resources: [ + { + resources: [ + { + metadata: { + name: 'my-pods-test-component-test-cluster', + }, + }, + ], + type: 'pods', + }, + { + resources: [ + { + metadata: { + name: 'my-configmaps-test-component-test-cluster', + }, + }, + ], + type: 'configmaps', + }, + { + resources: [ + { + metadata: { + name: 'my-services-test-component-test-cluster', + }, + }, + ], + type: 'services', + }, + ], + }, + { + cluster: { + name: 'other-cluster', + }, + errors: [], + resources: [ + { + resources: [ + { + metadata: { + name: 'my-pods-test-component-other-cluster', + }, + }, + ], + type: 'pods', + }, + { + resources: [ + { + metadata: { + name: 'my-configmaps-test-component-other-cluster', + }, + }, + ], + type: 'configmaps', + }, + { + resources: [ + { + metadata: { + name: 'my-services-test-component-other-cluster', + }, + }, + ], + type: 'services', + }, + ], + }, + { + cluster: { + name: 'error-cluster', + }, + errors: ['some random cluster error'], + resources: [ + { + type: 'pods', + resources: [], + }, + { + type: 'configmaps', + resources: [], + }, + { + type: 'services', + resources: [], + }, + ], + }, + ], + }); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 489b0631f6..9427ce9553 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -109,6 +109,14 @@ export class KubernetesFanOutHandler { }; }); }), - ).then(r => ({ items: 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)), + ), + })); } } diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index aa9df3e8fa..cda8dd7e6b 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -41,6 +41,8 @@ import { DetectedError, detectErrors } from '../../error-detection'; import { IngressesAccordions } from '../IngressesAccordions'; import { ServicesAccordions } from '../ServicesAccordions'; import { CustomResources } from '../CustomResources'; +import EmptyStateImage from '../../assets/emptystate.svg'; + import { GroupedResponsesContext, PodNamesWithErrorsContext, @@ -203,24 +205,48 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => { Your Clusters - {kubernetesObjects?.items.map((item, i) => { - const podsWithErrors = new Set( - detectedErrors - .get(item.cluster.name) - ?.filter(de => de.kind === 'Pod') - .map(de => de.names) - .flat() ?? [], - ); - - return ( - - + + + No resources on any known clusters for{' '} + {entity.metadata.name} + + + + EmptyState - ); - })} + + )} + {kubernetesObjects?.items.length > 0 && + kubernetesObjects?.items.map((item, i) => { + const podsWithErrors = new Set( + detectedErrors + .get(item.cluster.name) + ?.filter(de => de.kind === 'Pod') + .map(de => de.names) + .flat() ?? [], + ); + + return ( + + + + ); + })} )} From 1f98a6ff82836e82a0b741453368d35608b0b301 Mon Sep 17 00:00:00 2001 From: nliesenfeld Date: Fri, 12 Mar 2021 10:36:23 -0500 Subject: [PATCH 2/3] Add changeset Signed-off-by: nliesenfeld --- .changeset/thirty-peas-itch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thirty-peas-itch.md diff --git a/.changeset/thirty-peas-itch.md b/.changeset/thirty-peas-itch.md new file mode 100644 index 0000000000..4682e81cfc --- /dev/null +++ b/.changeset/thirty-peas-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Filter out k8s cluster with no resources or errors From 25eda415761488072b18733f1e2427de9df4117e Mon Sep 17 00:00:00 2001 From: nliesenfeld Date: Fri, 12 Mar 2021 11:41:43 -0500 Subject: [PATCH 3/3] Add changeset Signed-off-by: nliesenfeld --- .changeset/thirty-peas-itch.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/thirty-peas-itch.md b/.changeset/thirty-peas-itch.md index 4682e81cfc..8df3e123a4 100644 --- a/.changeset/thirty-peas-itch.md +++ b/.changeset/thirty-peas-itch.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes': patch --- Filter out k8s cluster with no resources or errors