mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
fix(configs): update unused badge logic [EE-6608] (#11500)
Co-authored-by: testa113 <testa113>
This commit is contained in:
parent
9b6779515e
commit
14a365045d
12 changed files with 590 additions and 113 deletions
|
@ -1,23 +1,25 @@
|
|||
import { useMemo } from 'react';
|
||||
import { FileCode } from 'lucide-react';
|
||||
import { ConfigMap } from 'kubernetes-types/core/v1';
|
||||
import { ConfigMap, Pod } from 'kubernetes-types/core/v1';
|
||||
import { CronJob, Job } from 'kubernetes-types/batch/v1';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
|
||||
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
|
||||
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
|
||||
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
||||
import { useApplicationsQuery } from '@/react/kubernetes/applications/application.queries';
|
||||
import { Application } from '@/react/kubernetes/applications/types';
|
||||
import { pluralize } from '@/portainer/helpers/strings';
|
||||
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
||||
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
|
||||
import { usePods } from '@/react/kubernetes/applications/usePods';
|
||||
import { useJobs } from '@/react/kubernetes/applications/useJobs';
|
||||
import { useCronJobs } from '@/react/kubernetes/applications/useCronJobs';
|
||||
|
||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||
import { AddButton } from '@@/buttons';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||
import { AddButton } from '@@/buttons/AddButton';
|
||||
|
||||
import {
|
||||
useConfigMapsForCluster,
|
||||
|
@ -55,10 +57,11 @@ export function ConfigMapsDatatable() {
|
|||
autoRefreshRate: tableState.autoRefreshRate * 1000,
|
||||
}
|
||||
);
|
||||
const { data: applications, ...applicationsQuery } = useApplicationsQuery(
|
||||
environmentId,
|
||||
namespaceNames
|
||||
);
|
||||
const podsQuery = usePods(environmentId, namespaceNames);
|
||||
const jobsQuery = useJobs(environmentId, namespaceNames);
|
||||
const cronJobsQuery = useCronJobs(environmentId, namespaceNames);
|
||||
const isInUseLoading =
|
||||
podsQuery.isLoading || jobsQuery.isLoading || cronJobsQuery.isLoading;
|
||||
|
||||
const filteredConfigMaps = useMemo(
|
||||
() =>
|
||||
|
@ -71,8 +74,10 @@ export function ConfigMapsDatatable() {
|
|||
);
|
||||
const configMapRowData = useConfigMapRowData(
|
||||
filteredConfigMaps,
|
||||
applications ?? [],
|
||||
applicationsQuery.isLoading,
|
||||
podsQuery.data ?? [],
|
||||
jobsQuery.data ?? [],
|
||||
cronJobsQuery.data ?? [],
|
||||
isInUseLoading,
|
||||
namespaces
|
||||
);
|
||||
|
||||
|
@ -112,8 +117,10 @@ export function ConfigMapsDatatable() {
|
|||
// and wraps with useMemo to prevent unnecessary calculations
|
||||
function useConfigMapRowData(
|
||||
configMaps: ConfigMap[],
|
||||
applications: Application[],
|
||||
applicationsLoading: boolean,
|
||||
pods: Pod[],
|
||||
jobs: Job[],
|
||||
cronJobs: CronJob[],
|
||||
isInUseLoading: boolean,
|
||||
namespaces?: Namespaces
|
||||
): ConfigMapRowData[] {
|
||||
return useMemo(
|
||||
|
@ -122,12 +129,13 @@ function useConfigMapRowData(
|
|||
...configMap,
|
||||
inUse:
|
||||
// if the apps are loading, set inUse to true to hide the 'unused' badge
|
||||
applicationsLoading || getIsConfigMapInUse(configMap, applications),
|
||||
isInUseLoading ||
|
||||
getIsConfigMapInUse(configMap, pods, jobs, cronJobs),
|
||||
isSystem: namespaces
|
||||
? namespaces?.[configMap.metadata?.namespace ?? '']?.IsSystem
|
||||
: false,
|
||||
})),
|
||||
[configMaps, applicationsLoading, applications, namespaces]
|
||||
[configMaps, isInUseLoading, pods, jobs, cronJobs, namespaces]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import { ConfigMap, Pod } from 'kubernetes-types/core/v1';
|
||||
import { CronJob, Job } from 'kubernetes-types/batch/v1';
|
||||
|
||||
import { getIsConfigMapInUse } from './utils';
|
||||
|
||||
describe('getIsConfigMapInUse', () => {
|
||||
it('should return false when no resources reference the configMap', () => {
|
||||
const configMap: ConfigMap = {
|
||||
metadata: { name: 'my-configmap', namespace: 'default' },
|
||||
};
|
||||
const pods: Pod[] = [];
|
||||
const jobs: Job[] = [];
|
||||
const cronJobs: CronJob[] = [];
|
||||
|
||||
expect(getIsConfigMapInUse(configMap, pods, jobs, cronJobs)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when a pod references the configMap', () => {
|
||||
const configMap: ConfigMap = {
|
||||
metadata: { name: 'my-configmap', namespace: 'default' },
|
||||
};
|
||||
const pods: Pod[] = [
|
||||
{
|
||||
metadata: { namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'container1',
|
||||
envFrom: [{ configMapRef: { name: 'my-configmap' } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
const jobs: Job[] = [];
|
||||
const cronJobs: CronJob[] = [];
|
||||
|
||||
expect(getIsConfigMapInUse(configMap, pods, jobs, cronJobs)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when a job references the configMap', () => {
|
||||
const configMap: ConfigMap = {
|
||||
metadata: { name: 'my-configmap', namespace: 'default' },
|
||||
};
|
||||
const pods: Pod[] = [];
|
||||
const jobs: Job[] = [
|
||||
{
|
||||
metadata: { namespace: 'default' },
|
||||
spec: {
|
||||
template: {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'container1',
|
||||
envFrom: [{ configMapRef: { name: 'my-configmap' } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const cronJobs: CronJob[] = [];
|
||||
|
||||
expect(getIsConfigMapInUse(configMap, pods, jobs, cronJobs)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when a cronJob references the configMap', () => {
|
||||
const configMap: ConfigMap = {
|
||||
metadata: { name: 'my-configmap', namespace: 'default' },
|
||||
};
|
||||
const pods: Pod[] = [];
|
||||
const jobs: Job[] = [];
|
||||
const cronJobs: CronJob[] = [
|
||||
{
|
||||
metadata: { namespace: 'default' },
|
||||
spec: {
|
||||
schedule: '0 0 * * *',
|
||||
jobTemplate: {
|
||||
spec: {
|
||||
template: {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'container1',
|
||||
envFrom: [{ configMapRef: { name: 'my-configmap' } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
expect(getIsConfigMapInUse(configMap, pods, jobs, cronJobs)).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,33 +1,67 @@
|
|||
import { ConfigMap, Pod } from 'kubernetes-types/core/v1';
|
||||
import { ConfigMap, Pod, PodSpec } from 'kubernetes-types/core/v1';
|
||||
import { CronJob, Job } from 'kubernetes-types/batch/v1';
|
||||
|
||||
import { Application } from '@/react/kubernetes/applications/types';
|
||||
import { applicationIsKind } from '@/react/kubernetes/applications/utils';
|
||||
|
||||
// getIsConfigMapInUse returns true if the configmap is referenced by any
|
||||
// application in the cluster
|
||||
/**
|
||||
* getIsConfigMapInUse returns true if the configmap is referenced by any pod, job, or cronjob in the same namespace
|
||||
*/
|
||||
export function getIsConfigMapInUse(
|
||||
configMap: ConfigMap,
|
||||
applications: Application[]
|
||||
pods: Pod[],
|
||||
jobs: Job[],
|
||||
cronJobs: CronJob[]
|
||||
) {
|
||||
return applications.some((app) => {
|
||||
const appSpec = applicationIsKind<Pod>('Pod', app)
|
||||
? app?.spec
|
||||
: app?.spec?.template?.spec;
|
||||
// get all podspecs from pods, jobs and cronjobs that are in the same namespace
|
||||
const podsInNamespace = pods
|
||||
.filter((pod) => pod.metadata?.namespace === configMap.metadata?.namespace)
|
||||
.map((pod) => pod.spec);
|
||||
const jobsInNamespace = jobs
|
||||
.filter((job) => job.metadata?.namespace === configMap.metadata?.namespace)
|
||||
.map((job) => job.spec?.template.spec);
|
||||
const cronJobsInNamespace = cronJobs
|
||||
.filter(
|
||||
(cronJob) => cronJob.metadata?.namespace === configMap.metadata?.namespace
|
||||
)
|
||||
.map((cronJob) => cronJob.spec?.jobTemplate.spec?.template.spec);
|
||||
const allPodSpecs = [
|
||||
...podsInNamespace,
|
||||
...jobsInNamespace,
|
||||
...cronJobsInNamespace,
|
||||
];
|
||||
|
||||
const hasEnvVarReference = appSpec?.containers.some((container) => {
|
||||
const valueFromEnv = container.env?.some(
|
||||
(envVar) =>
|
||||
envVar.valueFrom?.configMapKeyRef?.name === configMap.metadata?.name
|
||||
);
|
||||
const envFromEnv = container.envFrom?.some(
|
||||
(envVar) => envVar.configMapRef?.name === configMap.metadata?.name
|
||||
);
|
||||
return valueFromEnv || envFromEnv;
|
||||
});
|
||||
const hasVolumeReference = appSpec?.volumes?.some(
|
||||
(volume) => volume.configMap?.name === configMap.metadata?.name
|
||||
);
|
||||
|
||||
return hasEnvVarReference || hasVolumeReference;
|
||||
// check if the configmap is referenced by any pod, job or cronjob in the namespace
|
||||
const isReferenced = allPodSpecs.some((podSpec) => {
|
||||
if (!podSpec || !configMap.metadata?.name) {
|
||||
return false;
|
||||
}
|
||||
return doesPodSpecReferenceConfigMap(podSpec, configMap.metadata?.name);
|
||||
});
|
||||
|
||||
return isReferenced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a PodSpec references a specific ConfigMap.
|
||||
* @param podSpec - The PodSpec object to check.
|
||||
* @param configmapName - The name of the ConfigMap to check for references.
|
||||
* @returns A boolean indicating whether the PodSpec references the ConfigMap.
|
||||
*/
|
||||
function doesPodSpecReferenceConfigMap(
|
||||
podSpec: PodSpec,
|
||||
configmapName: string
|
||||
) {
|
||||
const hasEnvVarReference = podSpec?.containers.some((container) => {
|
||||
const valueFromEnv = container.env?.some(
|
||||
(envVar) => envVar.valueFrom?.configMapKeyRef?.name === configmapName
|
||||
);
|
||||
const envFromEnv = container.envFrom?.some(
|
||||
(envVar) => envVar.configMapRef?.name === configmapName
|
||||
);
|
||||
return valueFromEnv || envFromEnv;
|
||||
});
|
||||
|
||||
const hasVolumeReference = podSpec?.volumes?.some(
|
||||
(volume) => volume.configMap?.name === configmapName
|
||||
);
|
||||
|
||||
return hasEnvVarReference || hasVolumeReference;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue