diff --git a/.changeset/good-cameras-repair.md b/.changeset/good-cameras-repair.md new file mode 100644 index 0000000000..e994112e94 --- /dev/null +++ b/.changeset/good-cameras-repair.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes-node': patch +--- + +Add PersistentVolume and PersistentVolumeClaims Rendering diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index a265a142bb..4c165c17f4 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -148,6 +148,18 @@ export const ALL_OBJECTS: ObjectToFetch[] = [ plural: 'secrets', objectType: 'secrets', }, + { + group: '', + apiVersion: 'v1', + plural: 'persistentvolumes', + objectType: 'persistentvolumes', + }, + { + group: '', + apiVersion: 'v1', + plural: 'persistentvolumeclaims', + objectType: 'persistentvolumeclaims', + }, ...DEFAULT_OBJECTS, ]; diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index bf99cdaddd..da989ceeca 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -17,6 +17,8 @@ import type { V1Deployment } from '@kubernetes/client-node'; import type { V1Ingress } from '@kubernetes/client-node'; import type { V1Job } from '@kubernetes/client-node'; import type { V1LimitRange } from '@kubernetes/client-node'; +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; import type { V1Pod } from '@kubernetes/client-node'; import type { V1ReplicaSet } from '@kubernetes/client-node'; import type { V1ResourceQuota } from '@kubernetes/client-node'; @@ -267,7 +269,9 @@ export type FetchResponse = | StatefulSetsFetchResponse | DaemonSetsFetchResponse | PodStatusFetchResponse - | SecretsFetchResponse; + | SecretsFetchResponse + | PersistentVolumeFetchResponse + | PersistentVolumeClaimsFetchResponse; // @public (undocumented) export interface GroupedResponses extends DeploymentResources { @@ -284,6 +288,10 @@ export interface GroupedResponses extends DeploymentResources { // (undocumented) jobs: V1Job[]; // (undocumented) + persistentVolumeClaims: V1PersistentVolumeClaim[]; + // (undocumented) + persistentVolumes: V1PersistentVolume[]; + // (undocumented) secrets: V1Secret[]; // (undocumented) services: V1Service[]; @@ -385,6 +393,22 @@ export interface ObjectsByEntityResponse { items: ClusterObjects[]; } +// @public (undocumented) +export interface PersistentVolumeClaimsFetchResponse { + // (undocumented) + resources: Array; + // (undocumented) + type: 'persistentvolumeclaims'; +} + +// @public (undocumented) +export interface PersistentVolumeFetchResponse { + // (undocumented) + resources: Array; + // (undocumented) + type: 'persistentvolumes'; +} + // @public (undocumented) export interface PodFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 30846e3b8e..9791216604 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -31,6 +31,8 @@ import type { V1Service, V1StatefulSet, V1Secret, + V1PersistentVolume, + V1PersistentVolumeClaim, } from '@kubernetes/client-node'; import { Entity } from '@backstage/catalog-model'; @@ -142,7 +144,9 @@ export type FetchResponse = | StatefulSetsFetchResponse | DaemonSetsFetchResponse | PodStatusFetchResponse - | SecretsFetchResponse; + | SecretsFetchResponse + | PersistentVolumeFetchResponse + | PersistentVolumeClaimsFetchResponse; /** @public */ export interface PodFetchResponse { @@ -246,6 +250,18 @@ export interface SecretsFetchResponse { resources: Array; } +/** @public */ +export interface PersistentVolumeFetchResponse { + type: 'persistentvolumes'; + resources: Array; +} + +/** @public */ +export interface PersistentVolumeClaimsFetchResponse { + type: 'persistentvolumeclaims'; + resources: Array; +} + /** @public */ export type KubernetesFetchError = StatusError | RawFetchError; @@ -311,4 +327,6 @@ export interface GroupedResponses extends DeploymentResources { customResources: any[]; statefulsets: V1StatefulSet[]; daemonSets: V1DaemonSet[]; + persistentVolumes: V1PersistentVolume[]; + persistentVolumeClaims: V1PersistentVolumeClaim[]; } diff --git a/plugins/kubernetes-common/src/util/response.ts b/plugins/kubernetes-common/src/util/response.ts index ba181c48e2..7f7aacfb0b 100644 --- a/plugins/kubernetes-common/src/util/response.ts +++ b/plugins/kubernetes-common/src/util/response.ts @@ -64,6 +64,12 @@ export const groupResponses = ( case 'daemonsets': prev.daemonSets.push(...next.resources); break; + case 'persistentvolumes': + prev.persistentVolumes.push(...next.resources); + break; + case 'persistentvolumeclaims': + prev.persistentVolumeClaims.push(...next.resources); + break; default: } return prev; @@ -82,6 +88,8 @@ export const groupResponses = ( customResources: [], statefulsets: [], daemonSets: [], + persistentVolumes: [], + persistentVolumeClaims: [], } as GroupedResponses, ); }; diff --git a/plugins/kubernetes-node/report.api.md b/plugins/kubernetes-node/report.api.md index c23a0b5bb8..901d1ff782 100644 --- a/plugins/kubernetes-node/report.api.md +++ b/plugins/kubernetes-node/report.api.md @@ -221,7 +221,9 @@ export type KubernetesObjectTypes = | 'customresources' | 'statefulsets' | 'daemonsets' - | 'secrets'; + | 'secrets' + | 'persistentvolumes' + | 'persistentvolumeclaims'; // @public export interface KubernetesRouterExtensionPoint { diff --git a/plugins/kubernetes-node/src/types/types.ts b/plugins/kubernetes-node/src/types/types.ts index 3789835d52..1a2370f7d7 100644 --- a/plugins/kubernetes-node/src/types/types.ts +++ b/plugins/kubernetes-node/src/types/types.ts @@ -187,7 +187,9 @@ export type KubernetesObjectTypes = | 'customresources' | 'statefulsets' | 'daemonsets' - | 'secrets'; + | 'secrets' + | 'persistentvolumes' + | 'persistentvolumeclaims'; // If updating this list, also make sure to update // `objectTypes` and `apiVersionOverrides` in config.d.ts on @backstage/plugin-kubernetes-backend! diff --git a/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumeclaims.json b/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumeclaims.json new file mode 100644 index 0000000000..0da7fec9c9 --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumeclaims.json @@ -0,0 +1,37 @@ +{ + "persistentVolumeClaims": [ + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-web-storage", + "namespace": "default", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "web-app" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteOnce"], + "resources": { + "requests": { + "storage": "10Gi" + } + }, + "storageClassName": "standard", + "volumeName": "pv-web-volume" + }, + "status": { + "phase": "Bound", + "accessModes": ["ReadWriteOnce"], + "capacity": { + "storage": "10Gi" + } + } + } + ] +} diff --git a/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumes.json b/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumes.json new file mode 100644 index 0000000000..cddf92224f --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/1-persistentvolumes.json @@ -0,0 +1,33 @@ +{ + "persistentVolumes": [ + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-hostpath", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "type": "local" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "10Gi" + }, + "accessModes": ["ReadWriteOnce"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "manual", + "hostPath": { + "path": "/tmp/data" + } + }, + "status": { + "phase": "Available" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumeclaims.json b/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumeclaims.json new file mode 100644 index 0000000000..d418e6f691 --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumeclaims.json @@ -0,0 +1,160 @@ +{ + "persistentVolumeClaims": [ + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-web-storage", + "namespace": "default", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "web-app" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteOnce"], + "resources": { + "requests": { + "storage": "10Gi" + } + }, + "storageClassName": "standard", + "volumeName": "pv-web-volume" + }, + "status": { + "phase": "Bound", + "accessModes": ["ReadWriteOnce"], + "capacity": { + "storage": "10Gi" + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-database-storage", + "namespace": "production", + "uid": "2fb184cd-8b5c-5c10-9432-1416cdf96679", + "resourceVersion": "1362732553", + "creationTimestamp": "2021-07-17T10:20:15Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "database" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteOnce"], + "resources": { + "requests": { + "storage": "50Gi" + } + }, + "storageClassName": "fast-ssd", + "volumeName": "pv-database-volume" + }, + "status": { + "phase": "Bound", + "accessModes": ["ReadWriteOnce"], + "capacity": { + "storage": "50Gi" + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-cache-storage", + "namespace": "staging", + "uid": "3gc295de-9c6d-6d21-a543-2527def07790", + "resourceVersion": "1362732554", + "creationTimestamp": "2021-07-18T15:30:45Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "cache-service" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteMany"], + "resources": { + "requests": { + "storage": "5Gi" + } + }, + "storageClassName": "shared-storage" + }, + "status": { + "phase": "Pending" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-logs-storage", + "namespace": "logging", + "uid": "4hd3a6ef-ad7e-7e32-b654-3638eff18801", + "resourceVersion": "1362732555", + "creationTimestamp": "2021-07-19T09:15:30Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "log-aggregator" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteMany"], + "resources": { + "requests": { + "storage": "100Gi" + } + }, + "storageClassName": "nfs-storage", + "volumeName": "pv-logs-volume" + }, + "status": { + "phase": "Bound", + "accessModes": ["ReadWriteMany"], + "capacity": { + "storage": "100Gi" + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "pvc-backup-storage", + "namespace": "backup", + "uid": "5ie4b7f0-be8f-8f43-c765-4749f0029912", + "resourceVersion": "1362732556", + "creationTimestamp": "2021-07-20T16:45:22Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "backup-service" + }, + "annotations": {} + }, + "spec": { + "accessModes": ["ReadWriteOnce"], + "resources": { + "requests": { + "storage": "200Gi" + } + }, + "storageClassName": "backup-storage", + "volumeName": "pv-backup-volume-deleted" + }, + "status": { + "phase": "Lost" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumes.json b/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumes.json new file mode 100644 index 0000000000..46165b78ac --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/2-persistentvolumes.json @@ -0,0 +1,274 @@ +{ + "persistentVolumes": [ + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-aws-ebs", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "100Gi" + }, + "accessModes": ["ReadWriteOnce"], + "persistentVolumeReclaimPolicy": "Delete", + "storageClassName": "gp3", + "csi": { + "driver": "ebs.csi.aws.com", + "volumeHandle": "vol-1234567890abcdef0" + } + }, + "status": { + "phase": "Bound" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-aws-efs", + "uid": "2fb184cd-8b5c-5c10-9432-1416cdf96679", + "resourceVersion": "1362732553", + "creationTimestamp": "2021-07-17T10:20:15Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "5Ti" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "efs-sc", + "csi": { + "driver": "efs.csi.aws.com", + "volumeHandle": "fs-0123456789abcdef0" + } + }, + "status": { + "phase": "Available" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-aws-s3", + "uid": "3gc295de-9c6d-6d21-a543-2527def07790", + "resourceVersion": "1362732554", + "creationTimestamp": "2021-07-18T08:45:30Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "10Ti" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "s3-csi", + "csi": { + "driver": "s3.csi.aws.com", + "volumeHandle": "my-s3-bucket" + } + }, + "status": { + "phase": "Bound" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-gcp-pd", + "uid": "4hd3a6ef-ad7e-7e32-b654-3638eff18801", + "resourceVersion": "1362732555", + "creationTimestamp": "2021-07-18T15:30:45Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "200Gi" + }, + "accessModes": ["ReadWriteOnce"], + "persistentVolumeReclaimPolicy": "Delete", + "storageClassName": "ssd", + "csi": { + "driver": "pd.csi.storage.gke.io", + "volumeHandle": "projects/my-project/zones/us-central1-a/disks/my-disk" + } + }, + "status": { + "phase": "Bound" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-gcp-filestore", + "uid": "5ie4b7f0-be8f-8f43-c765-4749f0029912", + "resourceVersion": "1362732556", + "creationTimestamp": "2021-07-19T09:15:30Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "1Ti" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "filestore-csi", + "csi": { + "driver": "filestore.csi.storage.gke.io", + "volumeHandle": "projects/my-project/locations/us-central1-c/instances/my-filestore" + } + }, + "status": { + "phase": "Available" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-gcp-gcsfuse", + "uid": "6jf5c8g1-cf90-9054-d876-5850g1130a23", + "resourceVersion": "1362732557", + "creationTimestamp": "2021-07-20T12:30:45Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "50Ti" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "gcsfuse", + "csi": { + "driver": "gcsfuse.csi.storage.gke.io", + "volumeHandle": "my-gcs-bucket" + } + }, + "status": { + "phase": "Available" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-azure-disk", + "uid": "7kg6d9h2-dg01-0165-e987-6961h2241b34", + "resourceVersion": "1362732558", + "creationTimestamp": "2021-07-20T14:45:20Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "128Gi" + }, + "accessModes": ["ReadWriteOnce"], + "persistentVolumeReclaimPolicy": "Delete", + "storageClassName": "managed-premium", + "csi": { + "driver": "disk.csi.azure.com", + "volumeHandle": "/subscriptions/subscription-id/resourceGroups/rg/providers/Microsoft.Compute/disks/my-disk" + } + }, + "status": { + "phase": "Bound" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-azure-file", + "uid": "8lh7e0i3-eh12-1276-f098-7072i3352c45", + "resourceVersion": "1362732559", + "creationTimestamp": "2021-07-21T11:20:10Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "100Gi" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Retain", + "storageClassName": "azurefile", + "csi": { + "driver": "file.csi.azure.com", + "volumeHandle": "unique-volumeid" + } + }, + "status": { + "phase": "Available" + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "name": "pv-azure-blob", + "uid": "9mi8f1j4-fi23-2387-g109-8183j4463d56", + "resourceVersion": "1362732560", + "creationTimestamp": "2021-07-22T16:30:25Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "spec": { + "capacity": { + "storage": "500Gi" + }, + "accessModes": ["ReadWriteMany"], + "persistentVolumeReclaimPolicy": "Delete", + "storageClassName": "azureblob", + "csi": { + "driver": "blob.csi.azure.com", + "volumeHandle": "my-storage-account/my-container" + } + }, + "status": { + "phase": "Bound" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx index ca0f57e855..7fcdf70d6e 100644 --- a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx +++ b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx @@ -34,6 +34,8 @@ import { ConfigmapsAccordions } from '../ConfigmapsAccordions'; import { CronJobsAccordions } from '../CronJobsAccordions'; import { CustomResources } from '../CustomResources'; import { DaemonSetsAccordions } from '../DaemonSetsAccordions'; +import { PersistentVolumesAccordions } from '../PersistentVolumesAccordions'; +import { PersistentVolumeClaimsAccordions } from '../PersistentVolumesClaimsAccordions'; import { ClusterContext, GroupedResponsesContext, @@ -187,6 +189,16 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => { ) : undefined} + {groupedResponses.persistentVolumes.length > 0 ? ( + + + + ) : undefined} + {groupedResponses.persistentVolumeClaims.length > 0 ? ( + + + + ) : undefined} {groupedResponses.cronJobs.length > 0 ? ( diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.test.tsx new file mode 100644 index 0000000000..6561cf20c2 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { PersistentVolumesAccordions } from './PersistentVolumesAccordions'; +import * as onePersistentVolumesFixture from '../../__fixtures__/1-persistentvolumes.json'; +import * as twoPersistentVolumesFixture from '../../__fixtures__/2-persistentvolumes.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('PersistentVolumesAccordions', () => { + it('should render 1 persistent volume with summary', async () => { + const wrapper = kubernetesProviders( + onePersistentVolumesFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumes')).toBeInTheDocument(); + expect(screen.getByText('1 volumes')).toBeInTheDocument(); + expect(screen.getByText('0 bound')).toBeInTheDocument(); + }); + + it('should render 9 persistent volumes with summary', async () => { + const wrapper = kubernetesProviders( + twoPersistentVolumesFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumes')).toBeInTheDocument(); + expect(screen.getByText('9 volumes')).toBeInTheDocument(); + expect(screen.getByText('5 bound')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx new file mode 100644 index 0000000000..2d486bc383 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesAccordions.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode, useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { PersistentVolumesTable } from './PersistentVolumesTable'; +import { GroupedResponsesContext } from '../../hooks'; +import { StatusOK, StatusError } from '@backstage/core-components'; + +type PersistentVolumesAccordionsProps = { + children?: ReactNode; +}; + +type PersistentVolumesAccordionProps = { + persistentVolumes: V1PersistentVolume[]; + children?: ReactNode; +}; + +type PersistentVolumesSummaryProps = { + numberOfPersistentVolumes: number; + numberOfBoundVolumes: number; + numberOfFailedVolumes: number; + children?: ReactNode; +}; + +const PersistentVolumesSummary = ({ + numberOfPersistentVolumes, + numberOfBoundVolumes, + numberOfFailedVolumes, +}: PersistentVolumesSummaryProps) => { + return ( + + + + PersistentVolumes + + + + + {numberOfPersistentVolumes} volumes + + + {numberOfBoundVolumes} bound + + {numberOfFailedVolumes > 0 && ( + + + {numberOfFailedVolumes} volume + {numberOfFailedVolumes > 1 ? 's' : ''} failed + + + )} + + + ); +}; + +const PersistentVolumesAccordion = ({ + persistentVolumes, +}: PersistentVolumesAccordionProps) => { + const boundVolumes = persistentVolumes.filter( + pv => pv.status?.phase === 'Bound', + ); + const failedVolumes = persistentVolumes.filter( + pv => pv.status?.phase === 'Failed', + ); + + return ( + + }> + + + + + + + ); +}; + +export const PersistentVolumesAccordions = + ({}: PersistentVolumesAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + + return ( + + + + + + + + ); + }; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx new file mode 100644 index 0000000000..85911016ac --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as onePersistentVolumesFixture from '../../__fixtures__/1-persistentvolumes.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { PersistentVolumesDrawer } from './PersistentVolumesDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('PersistentVolumesDrawer', () => { + it('should render persistent volume drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('pv-hostpath')).toHaveLength(3); + expect(getAllByText('PersistentVolume')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx new file mode 100644 index 0000000000..7da0d16842 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesDrawer.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; + +export const PersistentVolumesDrawer = ({ + persistentVolume, + expanded, +}: { + persistentVolume: V1PersistentVolume; + expanded?: boolean; +}) => { + const namespace = persistentVolume.metadata?.namespace; + return ( + { + return persistentVolumeObject || {}; + }} + > + + + + {persistentVolume.metadata?.name ?? 'unknown object'} + + + + + PersistentVolume + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx new file mode 100644 index 0000000000..a1684748cb --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import * as onePersistentVolumesFixture from '../../__fixtures__/1-persistentvolumes.json'; +import * as twoPersistentVolumesFixture from '../../__fixtures__/2-persistentvolumes.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { PersistentVolumesTable } from './PersistentVolumesTable'; + +describe('PersistentVolumesTable', () => { + it('should render persistent volume table with columns', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('type')).toBeInTheDocument(); + expect(screen.getByText('claim')).toBeInTheDocument(); + + expect(screen.getByText('pv-hostpath')).toBeInTheDocument(); + expect(screen.getAllByText('Available').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('10Gi')).toBeInTheDocument(); + }); + + it('should render multiple persistent volumes with cloud provider types', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('type')).toBeInTheDocument(); + expect(screen.getByText('claim')).toBeInTheDocument(); + + expect(screen.getByText('pv-aws-ebs')).toBeInTheDocument(); + expect(screen.getByText('pv-aws-efs')).toBeInTheDocument(); + expect(screen.getByText('pv-aws-s3')).toBeInTheDocument(); + expect(screen.getByText('pv-gcp-pd')).toBeInTheDocument(); + expect(screen.getByText('pv-gcp-filestore')).toBeInTheDocument(); + + expect(screen.getByText('AWS EBS Volume')).toBeInTheDocument(); + expect(screen.getByText('AWS EFS')).toBeInTheDocument(); + expect(screen.getByText('S3 Bucket')).toBeInTheDocument(); + expect(screen.getByText('GCP Persistent Disk')).toBeInTheDocument(); + expect(screen.getByText('GCP Filestore')).toBeInTheDocument(); + + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(3); + expect(screen.getAllByText('Available').length).toBeGreaterThanOrEqual(2); + + expect(screen.getByText('100Gi')).toBeInTheDocument(); + expect(screen.getByText('5Ti')).toBeInTheDocument(); + expect(screen.getByText('10Ti')).toBeInTheDocument(); + expect(screen.getByText('200Gi')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx new file mode 100644 index 0000000000..6c9ed7c339 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/PersistentVolumesTable.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { PersistentVolumesDrawer } from './PersistentVolumesDrawer'; +import { Table, TableColumn } from '@backstage/core-components'; +import type { V1PersistentVolume } from '@kubernetes/client-node'; +import { + StatusError, + StatusOK, + StatusPending, +} from '@backstage/core-components'; +import { getPersistentVolumeType } from '../../utils/persistentVolumes'; + +export type PersistentVolumesTableProps = { + persistentVolumes: V1PersistentVolume[]; + children?: ReactNode; +}; + +const PersistentVolumeDrawerTrigger = ({ + persistentVolume, +}: { + persistentVolume: V1PersistentVolume; +}) => { + return ; +}; + +const renderPhaseStatus = (persistentVolume: V1PersistentVolume) => { + const phase = persistentVolume.status?.phase; + + if (phase === 'Bound') { + return Bound; + } + if (phase === 'Available') { + return Available; + } + if (phase === 'Released') { + return Released; + } + if (phase === 'Pending') { + return Pending; + } + if (phase === 'Failed') { + return Failed; + } + return <>{phase ?? 'Unknown'}; +}; + +export const PersistentVolumesTable = ({ + persistentVolumes, +}: PersistentVolumesTableProps) => { + const defaultColumns: TableColumn[] = [ + { + title: 'ID', + field: 'metadata.uid', + hidden: true, + }, + { + title: 'name', + highlight: true, + render: (persistentVolume: V1PersistentVolume) => { + return ( + + ); + }, + cellStyle: { + width: '20%', + }, + }, + { + title: 'phase', + render: renderPhaseStatus, + cellStyle: { + width: '9%', + }, + }, + { + title: 'status', + render: (persistentVolume: V1PersistentVolume) => + persistentVolume.status?.phase ?? 'Unknown', + cellStyle: { + width: '9%', + }, + }, + { + title: 'capacity', + render: (persistentVolume: V1PersistentVolume) => + persistentVolume.spec?.capacity?.storage ?? 'N/A', + cellStyle: { + width: '10%', + }, + }, + { + title: 'type', + render: (persistentVolume: V1PersistentVolume) => { + const driver = persistentVolume.spec?.csi?.driver; + return getPersistentVolumeType(driver) ?? 'N/A'; + }, + cellStyle: { + width: '15%', + }, + }, + { + title: 'claim', + render: (persistentVolume: V1PersistentVolume) => { + const claim = persistentVolume.spec?.claimRef; + if (claim?.namespace && claim?.name) { + return `${claim.namespace}/${claim.name}`; + } + return 'N/A'; + }, + cellStyle: { + width: '37%', + }, + }, + ]; + + const columns: TableColumn[] = [...defaultColumns]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( +
+ ({ + ...pv, + id: pv?.metadata?.uid, + })) as any as V1PersistentVolume[] + } + columns={columns} + /> + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts new file mode 100644 index 0000000000..29123cc6fc --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesAccordions/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './PersistentVolumesAccordions'; +export * from './PersistentVolumesTable'; +export * from './PersistentVolumesDrawer'; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx new file mode 100644 index 0000000000..2497b2fa04 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { PersistentVolumeClaimsAccordions } from './PersistentVolumeClaimsAccordions'; +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import * as twoPersistentVolumeClaimsFixture from '../../__fixtures__/2-persistentvolumeclaims.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('PersistentVolumeClaimsAccordions', () => { + it('should render 1 persistent volume claim with summary', async () => { + const wrapper = kubernetesProviders( + onePersistentVolumeClaimsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumeClaims')).toBeInTheDocument(); + expect(screen.getByText('1 claims')).toBeInTheDocument(); + expect(screen.getByText('1 bound')).toBeInTheDocument(); + }); + + it('should render multiple persistent volume claims with summary', async () => { + const wrapper = kubernetesProviders( + twoPersistentVolumeClaimsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('PersistentVolumeClaims')).toBeInTheDocument(); + expect(screen.getByText('5 claims')).toBeInTheDocument(); + expect(screen.getByText('3 bound')).toBeInTheDocument(); + expect(screen.getByText('1 claim lost')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx new file mode 100644 index 0000000000..c16b1399f1 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsAccordions.tsx @@ -0,0 +1,139 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode, useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { PersistentVolumeClaimsTable } from './PersistentVolumeClaimsTable'; +import { GroupedResponsesContext } from '../../hooks'; +import { StatusOK, StatusError } from '@backstage/core-components'; + +type PersistentVolumeClaimsAccordionsProps = { + children?: ReactNode; +}; + +type PersistentVolumeClaimsAccordionProps = { + persistentVolumeClaims: V1PersistentVolumeClaim[]; + children?: ReactNode; +}; + +type PersistentVolumeClaimsSummaryProps = { + numberOfPersistentVolumeClaims: number; + numberOfBoundClaims: number; + numberOfLostClaims: number; + children?: ReactNode; +}; + +const PersistentVolumeClaimsSummary = ({ + numberOfPersistentVolumeClaims, + numberOfBoundClaims, + numberOfLostClaims, +}: PersistentVolumeClaimsSummaryProps) => { + return ( + + + + PersistentVolumeClaims + + + + + {numberOfPersistentVolumeClaims} claims + + + {numberOfBoundClaims} bound + + {numberOfLostClaims > 0 && ( + + + {numberOfLostClaims} claim + {numberOfLostClaims > 1 ? 's' : ''} lost + + + )} + + + ); +}; + +const PersistentVolumeClaimsAccordion = ({ + persistentVolumeClaims, +}: PersistentVolumeClaimsAccordionProps) => { + const boundClaims = persistentVolumeClaims.filter( + pvc => pvc.status?.phase === 'Bound', + ); + const lostClaims = persistentVolumeClaims.filter( + pvc => pvc.status?.phase === 'Lost', + ); + + return ( + + }> + + + + + + + ); +}; + +export const PersistentVolumeClaimsAccordions = + ({}: PersistentVolumeClaimsAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + + return ( + + + + + + + + ); + }; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx new file mode 100644 index 0000000000..1340a176b5 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { PersistentVolumeClaimsDrawer } from './PersistentVolumeClaimsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('PersistentVolumeClaimsDrawer', () => { + it('should render persistent volume claim drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('pvc-web-storage')).toHaveLength(3); + expect(getAllByText('PersistentVolumeClaim')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + expect(getByText('namespace: default')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx new file mode 100644 index 0000000000..d0af9caf48 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsDrawer.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; + +export const PersistentVolumeClaimsDrawer = ({ + persistentVolumeClaim, + expanded, +}: { + persistentVolumeClaim: V1PersistentVolumeClaim; + expanded?: boolean; +}) => { + const namespace = persistentVolumeClaim.metadata?.namespace; + return ( + { + return persistentVolumeClaimObject || {}; + }} + > + + + + {persistentVolumeClaim.metadata?.name ?? 'unknown object'} + + + + + PersistentVolumeClaim + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx new file mode 100644 index 0000000000..06230a85b7 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import * as onePersistentVolumeClaimsFixture from '../../__fixtures__/1-persistentvolumeclaims.json'; +import * as twoPersistentVolumeClaimsFixture from '../../__fixtures__/2-persistentvolumeclaims.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { PersistentVolumeClaimsTable } from './PersistentVolumeClaimsTable'; + +describe('PersistentVolumeClaimsTable', () => { + it('should render persistent volume claim table with columns', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('volume')).toBeInTheDocument(); + + expect(screen.getByText('pvc-web-storage')).toBeInTheDocument(); + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('10Gi')).toBeInTheDocument(); + }); + + it('should render multiple persistent volume claims', async () => { + await renderInTestApp( + , + ); + + expect(screen.getByText('name')).toBeInTheDocument(); + expect(screen.getByText('phase')).toBeInTheDocument(); + expect(screen.getByText('status')).toBeInTheDocument(); + expect(screen.getByText('capacity')).toBeInTheDocument(); + expect(screen.getByText('volume')).toBeInTheDocument(); + + expect(screen.getByText('pvc-web-storage')).toBeInTheDocument(); + expect(screen.getByText('pvc-database-storage')).toBeInTheDocument(); + + expect(screen.getAllByText('Bound').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('Pending').length).toBeGreaterThanOrEqual(1); + + expect(screen.getByText('10Gi')).toBeInTheDocument(); + expect(screen.getByText('50Gi')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx new file mode 100644 index 0000000000..eb801fc1b8 --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/PersistentVolumeClaimsTable.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { PersistentVolumeClaimsDrawer } from './PersistentVolumeClaimsDrawer'; +import { Table, TableColumn } from '@backstage/core-components'; +import type { V1PersistentVolumeClaim } from '@kubernetes/client-node'; +import { + StatusError, + StatusOK, + StatusPending, +} from '@backstage/core-components'; + +export type PersistentVolumeClaimsTableProps = { + persistentVolumeClaims: V1PersistentVolumeClaim[]; + children?: ReactNode; +}; + +const PersistentVolumeClaimDrawerTrigger = ({ + persistentVolumeClaim, +}: { + persistentVolumeClaim: V1PersistentVolumeClaim; +}) => { + return ( + + ); +}; + +const renderPhaseStatus = (persistentVolumeClaim: V1PersistentVolumeClaim) => { + const phase = persistentVolumeClaim.status?.phase; + + if (phase === 'Bound') { + return Bound; + } + if (phase === 'Pending') { + return Pending; + } + if (phase === 'Lost') { + return Lost; + } + return <>{phase ?? 'Unknown'}; +}; + +export const PersistentVolumeClaimsTable = ({ + persistentVolumeClaims, +}: PersistentVolumeClaimsTableProps) => { + const defaultColumns: TableColumn[] = [ + { + title: 'ID', + field: 'metadata.uid', + hidden: true, + }, + { + title: 'name', + highlight: true, + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => { + return ( + + ); + }, + cellStyle: { + width: '25%', + }, + }, + { + title: 'phase', + render: renderPhaseStatus, + cellStyle: { + width: '10%', + }, + }, + { + title: 'status', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.status?.phase ?? 'Unknown', + cellStyle: { + width: '10%', + }, + }, + { + title: 'capacity', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.status?.capacity?.storage ?? 'N/A', + cellStyle: { + width: '10%', + }, + }, + { + title: 'volume', + render: (persistentVolumeClaim: V1PersistentVolumeClaim) => + persistentVolumeClaim.spec?.volumeName ?? 'N/A', + cellStyle: { + width: '45%', + }, + }, + ]; + + const columns: TableColumn[] = [...defaultColumns]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( +
+
({ + ...pvc, + id: pvc?.metadata?.uid, + })) as any as V1PersistentVolumeClaim[] + } + columns={columns} + /> + + ); +}; diff --git a/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts new file mode 100644 index 0000000000..63308960fe --- /dev/null +++ b/plugins/kubernetes-react/src/components/PersistentVolumesClaimsAccordions/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './PersistentVolumeClaimsAccordions'; +export * from './PersistentVolumeClaimsTable'; +export * from './PersistentVolumeClaimsDrawer'; diff --git a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts index 750ee58846..81ad9af048 100644 --- a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts +++ b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts @@ -35,4 +35,6 @@ export const GroupedResponsesContext = createContext({ cronJobs: [], customResources: [], statefulsets: [], + persistentVolumes: [], + persistentVolumeClaims: [], }); diff --git a/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts b/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts new file mode 100644 index 0000000000..ab583b7c9d --- /dev/null +++ b/plugins/kubernetes-react/src/utils/persistentVolumes.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getPersistentVolumeType } from './persistentVolumes'; + +describe('getPersistentVolumeType', () => { + it('should return null for undefined driver', () => { + expect(getPersistentVolumeType(undefined)).toBeNull(); + }); + + it('should return null for empty driver', () => { + expect(getPersistentVolumeType('')).toBeNull(); + }); + + describe('AWS drivers', () => { + it('should return AWS EBS Volume for aws ebs driver', () => { + expect(getPersistentVolumeType('ebs.csi.aws.com')).toBe('AWS EBS Volume'); + }); + + it('should return AWS EFS for aws efs driver', () => { + expect(getPersistentVolumeType('efs.csi.aws.com')).toBe('AWS EFS'); + }); + + it('should return S3 Bucket for aws s3 driver', () => { + expect(getPersistentVolumeType('s3.csi.aws.com')).toBe('S3 Bucket'); + }); + }); + + describe('GCP drivers', () => { + it('should return GCP Persistent Disk for gcp pd driver', () => { + expect(getPersistentVolumeType('pd.csi.storage.gke.io')).toBe( + 'GCP Persistent Disk', + ); + }); + + it('should return GCP Filestore for gcp filestore driver', () => { + expect(getPersistentVolumeType('filestore.csi.storage.gke.io')).toBe( + 'GCP Filestore', + ); + }); + + it('should return GCS Fuse for gcp gcsfuse driver', () => { + expect(getPersistentVolumeType('gcsfuse.csi.storage.gke.io')).toBe( + 'GCS Fuse', + ); + }); + }); + + describe('Azure drivers', () => { + it('should return Azure Disk for azure disk driver', () => { + expect(getPersistentVolumeType('disk.csi.azure.com')).toBe('Azure Disk'); + }); + + it('should return Azure File for azure file driver', () => { + expect(getPersistentVolumeType('file.csi.azure.com')).toBe('Azure File'); + }); + + it('should return Azure Blob for azure blob driver', () => { + expect(getPersistentVolumeType('blob.csi.azure.com')).toBe('Azure Blob'); + }); + }); + + it('should return the original driver for unknown drivers', () => { + expect(getPersistentVolumeType('unknown.driver.com')).toBe( + 'unknown.driver.com', + ); + expect(getPersistentVolumeType('local-storage')).toBe('local-storage'); + expect(getPersistentVolumeType('nfs')).toBe('nfs'); + }); +}); diff --git a/plugins/kubernetes-react/src/utils/persistentVolumes.ts b/plugins/kubernetes-react/src/utils/persistentVolumes.ts new file mode 100644 index 0000000000..348e442cb1 --- /dev/null +++ b/plugins/kubernetes-react/src/utils/persistentVolumes.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getPersistentVolumeType = (driver?: string): string | null => { + if (!driver) return null; + + if (driver.includes('aws')) { + if (driver.includes('ebs')) return `AWS EBS Volume`; + if (driver.includes('efs')) return `AWS EFS`; + if (driver.includes('s3')) return `S3 Bucket`; + } + + if (driver.includes('gke')) { + if (driver.includes('gcsfuse')) return `GCS Fuse`; + if (driver.includes('filestore')) return `GCP Filestore`; + if (driver.includes('pd')) return `GCP Persistent Disk`; + } + + if (driver.includes('azure')) { + if (driver.includes('disk')) return `Azure Disk`; + if (driver.includes('file')) return `Azure File`; + if (driver.includes('blob')) return `Azure Blob`; + } + + return driver; +};