diff --git a/api/kubernetes/cli/volumes.go b/api/kubernetes/cli/volumes.go index 5343507c2..3308816a5 100644 --- a/api/kubernetes/cli/volumes.go +++ b/api/kubernetes/cli/volumes.go @@ -214,6 +214,7 @@ func (kcl *KubeClient) CombineVolumesWithApplications(volumes *[]models.K8sVolum hasReplicaSetOwnerReference := containsReplicaSetOwnerReference(pods) replicaSetItems := make([]appsv1.ReplicaSet, 0) + deploymentItems := make([]appsv1.Deployment, 0) if hasReplicaSetOwnerReference { replicaSets, err := kcl.cli.AppsV1().ReplicaSets("").List(context.Background(), metav1.ListOptions{}) if err != nil { @@ -221,19 +222,48 @@ func (kcl *KubeClient) CombineVolumesWithApplications(volumes *[]models.K8sVolum return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list replica sets across the cluster. Error: %w", err) } replicaSetItems = replicaSets.Items + + deployments, err := kcl.cli.AppsV1().Deployments("").List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Error().Err(err).Msg("Failed to list deployments across the cluster") + return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list deployments across the cluster. Error: %w", err) + } + deploymentItems = deployments.Items } - return kcl.updateVolumesWithOwningApplications(volumes, pods, replicaSetItems) + hasStatefulSetOwnerReference := containsStatefulSetOwnerReference(pods) + statefulSetItems := make([]appsv1.StatefulSet, 0) + if hasStatefulSetOwnerReference { + statefulSets, err := kcl.cli.AppsV1().StatefulSets("").List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Error().Err(err).Msg("Failed to list stateful sets across the cluster") + return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list stateful sets across the cluster. Error: %w", err) + } + statefulSetItems = statefulSets.Items + } + + hasDaemonSetOwnerReference := containsDaemonSetOwnerReference(pods) + daemonSetItems := make([]appsv1.DaemonSet, 0) + if hasDaemonSetOwnerReference { + daemonSets, err := kcl.cli.AppsV1().DaemonSets("").List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Error().Err(err).Msg("Failed to list daemon sets across the cluster") + return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list daemon sets across the cluster. Error: %w", err) + } + daemonSetItems = daemonSets.Items + } + + return kcl.updateVolumesWithOwningApplications(volumes, pods, deploymentItems, replicaSetItems, statefulSetItems, daemonSetItems) } // updateVolumesWithOwningApplications updates the volumes with the applications that use them. -func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8sVolumeInfo, pods *corev1.PodList, replicaSetItems []appsv1.ReplicaSet) (*[]models.K8sVolumeInfo, error) { +func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8sVolumeInfo, pods *corev1.PodList, deploymentItems []appsv1.Deployment, replicaSetItems []appsv1.ReplicaSet, statefulSetItems []appsv1.StatefulSet, daemonSetItems []appsv1.DaemonSet) (*[]models.K8sVolumeInfo, error) { for i, volume := range *volumes { for _, pod := range pods.Items { if pod.Spec.Volumes != nil { for _, podVolume := range pod.Spec.Volumes { - if podVolume.PersistentVolumeClaim != nil && podVolume.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace { - application, err := kcl.ConvertPodToApplication(pod, replicaSetItems, []appsv1.Deployment{}, []appsv1.StatefulSet{}, []appsv1.DaemonSet{}, []corev1.Service{}, false) + if podVolume.VolumeSource.PersistentVolumeClaim != nil && podVolume.VolumeSource.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace { + application, err := kcl.ConvertPodToApplication(pod, replicaSetItems, deploymentItems, statefulSetItems, daemonSetItems, []corev1.Service{}, false) if err != nil { log.Error().Err(err).Msg("Failed to convert pod to application") return nil, fmt.Errorf("an error occurred during the CombineServicesWithApplications operation, unable to convert pod to application. Error: %w", err) diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js index 25dcfd271..c01e18192 100644 --- a/app/kubernetes/helpers/application/index.js +++ b/app/kubernetes/helpers/application/index.js @@ -21,7 +21,7 @@ import { KubernetesApplicationVolumePersistentPayload, KubernetesApplicationVolumeSecretPayload, } from 'Kubernetes/models/application/payloads'; -import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper'; +import { generatedApplicationConfigVolumeName } from '@/react/kubernetes/volumes/utils'; import { HelmApplication } from 'Kubernetes/models/application/models'; import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants'; import { KubernetesPodAffinity, KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models'; @@ -239,7 +239,7 @@ class KubernetesApplicationHelper { const volKeys = _.filter(config.overridenKeys, (item) => item.type === 'FILESYSTEM'); const groupedVolKeys = _.groupBy(volKeys, 'path'); _.forEach(groupedVolKeys, (items, path) => { - const volumeName = KubernetesVolumeHelper.generatedApplicationConfigVolumeName(app.Name); + const volumeName = generatedApplicationConfigVolumeName(app.Name); const configurationName = config.selectedConfiguration.metadata.name; const itemsMap = _.map(items, (item) => { const entry = new KubernetesApplicationVolumeEntryPayload(); diff --git a/app/kubernetes/helpers/volumeHelper.js b/app/kubernetes/helpers/volumeHelper.js index 0b158d8f5..a0c3c3827 100644 --- a/app/kubernetes/helpers/volumeHelper.js +++ b/app/kubernetes/helpers/volumeHelper.js @@ -1,5 +1,4 @@ import _ from 'lodash-es'; -import uuidv4 from 'uuid/v4'; import { KubernetesApplicationTypes } from 'Kubernetes/models/application/models/appConstants'; class KubernetesVolumeHelper { @@ -20,18 +19,6 @@ class KubernetesVolumeHelper { ); }); } - - static isUsed(item) { - return item.Applications.length !== 0; - } - - static generatedApplicationConfigVolumeName(name) { - return 'config-' + name + '-' + uuidv4(); - } - - static isExternalVolume(volume) { - return !volume.PersistentVolumeClaim.ApplicationOwner; - } } export default KubernetesVolumeHelper; diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js index 5d5994dc5..8c2841dc6 100644 --- a/app/kubernetes/views/applications/create/createApplicationController.js +++ b/app/kubernetes/views/applications/create/createApplicationController.js @@ -29,6 +29,7 @@ import { confirm, confirmUpdate, confirmWebEditorDiscard } from '@@/modals/confi import { buildConfirmButton } from '@@/modals/utils'; import { ModalType } from '@@/modals'; import { KUBE_STACK_NAME_VALIDATION_REGEX } from '@/react/kubernetes/DeployView/StackName/constants'; +import { isVolumeUsed } from '@/react/kubernetes/volumes/utils'; class KubernetesCreateApplicationController { /* #region CONSTRUCTOR */ @@ -785,7 +786,7 @@ class KubernetesCreateApplicationController { }); this.volumes = volumes; const filteredVolumes = _.filter(this.volumes, (volume) => { - const isUnused = !KubernetesVolumeHelper.isUsed(volume); + const isUnused = !isVolumeUsed(volume); const isRWX = volume.PersistentVolumeClaim.storageClass && _.includes(volume.PersistentVolumeClaim.storageClass.AccessModes, 'RWX'); return isUnused || isRWX; }); diff --git a/app/kubernetes/views/volumes/edit/volumeController.js b/app/kubernetes/views/volumes/edit/volumeController.js index f9e9c12f6..5a66bae94 100644 --- a/app/kubernetes/views/volumes/edit/volumeController.js +++ b/app/kubernetes/views/volumes/edit/volumeController.js @@ -6,6 +6,7 @@ import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper'; import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models'; import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper'; import { confirmRedeploy } from '@/react/kubernetes/volumes/ItemView/ConfirmRedeployModal'; +import { isVolumeUsed, isVolumeExternal } from '@/react/kubernetes/volumes/utils'; class KubernetesVolumeController { /* @ngInject */ @@ -49,7 +50,7 @@ class KubernetesVolumeController { } isExternalVolume() { - return KubernetesVolumeHelper.isExternalVolume(this.volume); + return isVolumeExternal(this.volume); } isSystemNamespace() { @@ -57,7 +58,7 @@ class KubernetesVolumeController { } isUsed() { - return KubernetesVolumeHelper.isUsed(this.volume); + return isVolumeUsed(this.volume); } onChangeSize() { @@ -102,7 +103,7 @@ class KubernetesVolumeController { } updateVolume() { - if (KubernetesVolumeHelper.isUsed(this.volume)) { + if (isVolumeUsed(this.volume)) { confirmRedeploy().then((redeploy) => { return this.$async(this.updateVolumeAsync, redeploy); }); diff --git a/app/react/kubernetes/volumes/ListView/VolumesDatatable.tsx b/app/react/kubernetes/volumes/ListView/VolumesDatatable.tsx index 612d1053d..79aeef5d5 100644 --- a/app/react/kubernetes/volumes/ListView/VolumesDatatable.tsx +++ b/app/react/kubernetes/volumes/ListView/VolumesDatatable.tsx @@ -1,7 +1,6 @@ import { Database } from 'lucide-react'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; -import KubernetesVolumeHelper from '@/kubernetes/helpers/volumeHelper'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { refreshableSettings } from '@@/datatables/types'; @@ -20,6 +19,7 @@ import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery' import { useAllVolumesQuery } from '../queries/useVolumesQuery'; import { isSystemNamespace } from '../../namespaces/queries/useIsSystemNamespace'; import { useDeleteVolumes } from '../queries/useDeleteVolumes'; +import { isVolumeUsed } from '../utils'; import { columns } from './columns'; @@ -68,7 +68,7 @@ export function VolumesDatatable() { disableSelect={!hasWriteAuth} isRowSelectable={({ original: volume }) => !isSystemNamespace(volume.ResourcePool.Namespace.Name, namespaces) && - !KubernetesVolumeHelper.isUsed(volume) + !isVolumeUsed(volume) } renderTableActions={(selectedItems) => ( diff --git a/app/react/kubernetes/volumes/ListView/columns.name.tsx b/app/react/kubernetes/volumes/ListView/columns.name.tsx index e11a1ab26..1dd17b677 100644 --- a/app/react/kubernetes/volumes/ListView/columns.name.tsx +++ b/app/react/kubernetes/volumes/ListView/columns.name.tsx @@ -1,6 +1,5 @@ import { CellContext } from '@tanstack/react-table'; -import KubernetesVolumeHelper from '@/kubernetes/helpers/volumeHelper'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { Link } from '@@/Link'; @@ -9,6 +8,7 @@ import { ExternalBadge } from '@@/Badge/ExternalBadge'; import { UnusedBadge } from '@@/Badge/UnusedBadge'; import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery'; +import { isVolumeExternal, isVolumeUsed } from '../utils'; import { VolumeViewModel } from './types'; import { helper } from './columns.helper'; @@ -44,8 +44,8 @@ export function NameCell({ ) : ( <> - {KubernetesVolumeHelper.isExternalVolume(item) && } - {!KubernetesVolumeHelper.isUsed(item) && } + {isVolumeExternal(item) && } + {!isVolumeUsed(item) && } )} diff --git a/app/react/kubernetes/volumes/utils.ts b/app/react/kubernetes/volumes/utils.ts new file mode 100644 index 000000000..4a9fdfcc3 --- /dev/null +++ b/app/react/kubernetes/volumes/utils.ts @@ -0,0 +1,15 @@ +import uuidv4 from 'uuid/v4'; + +import { VolumeViewModel } from './ListView/types'; + +export function isVolumeUsed(volume: VolumeViewModel) { + return volume.Applications.length !== 0; +} + +export function isVolumeExternal(volume: VolumeViewModel) { + return !volume.PersistentVolumeClaim.ApplicationOwner; +} + +export function generatedApplicationConfigVolumeName(applicationName: string) { + return `config-${applicationName}-${uuidv4()}`; +}