1
0
Fork 0
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:
Ali 2024-05-03 09:13:33 +12:00 committed by GitHub
parent 9b6779515e
commit 14a365045d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 590 additions and 113 deletions

View file

@ -1,18 +1,20 @@
import { useMemo } from 'react';
import { Lock } from 'lucide-react';
import { Secret } from 'kubernetes-types/core/v1';
import { Pod, Secret } 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';
@ -55,10 +57,11 @@ export function SecretsDatatable() {
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 filteredSecrets = useMemo(
() =>
@ -71,8 +74,10 @@ export function SecretsDatatable() {
);
const secretRowData = useSecretRowData(
filteredSecrets,
applications ?? [],
applicationsQuery.isLoading,
podsQuery.data ?? [],
jobsQuery.data ?? [],
cronJobsQuery.data ?? [],
isInUseLoading,
namespaces
);
@ -112,8 +117,10 @@ export function SecretsDatatable() {
// and wraps with useMemo to prevent unnecessary calculations
function useSecretRowData(
secrets: Secret[],
applications: Application[],
applicationsLoading: boolean,
pods: Pod[],
jobs: Job[],
cronJobs: CronJob[],
isInUseLoading: boolean,
namespaces?: Namespaces
): SecretRowData[] {
return useMemo(
@ -122,12 +129,12 @@ function useSecretRowData(
...secret,
inUse:
// if the apps are loading, set inUse to true to hide the 'unused' badge
applicationsLoading || getIsSecretInUse(secret, applications),
isInUseLoading || getIsSecretInUse(secret, pods, jobs, cronJobs),
isSystem: namespaces
? namespaces?.[secret.metadata?.namespace ?? '']?.IsSystem
: false,
})),
[secrets, applicationsLoading, applications, namespaces]
[secrets, isInUseLoading, pods, jobs, cronJobs, namespaces]
);
}

View file

@ -0,0 +1,99 @@
import { CronJob, Job } from 'kubernetes-types/batch/v1';
import { Secret, Pod } from 'kubernetes-types/core/v1';
import { getIsSecretInUse } from './utils';
describe('getIsSecretInUse', () => {
it('should return false when no resources reference the secret', () => {
const secret: Secret = {
metadata: { name: 'my-secret', namespace: 'default' },
};
const pods: Pod[] = [];
const jobs: Job[] = [];
const cronJobs: CronJob[] = [];
expect(getIsSecretInUse(secret, pods, jobs, cronJobs)).toBe(false);
});
it('should return true when a pod references the secret', () => {
const secret: Secret = {
metadata: { name: 'my-secret', namespace: 'default' },
};
const pods: Pod[] = [
{
metadata: { namespace: 'default' },
spec: {
containers: [
{
name: 'container1',
envFrom: [{ secretRef: { name: 'my-secret' } }],
},
],
},
},
];
const jobs: Job[] = [];
const cronJobs: CronJob[] = [];
expect(getIsSecretInUse(secret, pods, jobs, cronJobs)).toBe(true);
});
it('should return true when a job references the secret', () => {
const secret: Secret = {
metadata: { name: 'my-secret', namespace: 'default' },
};
const pods: Pod[] = [];
const jobs: Job[] = [
{
metadata: { namespace: 'default' },
spec: {
template: {
spec: {
containers: [
{
name: 'container1',
envFrom: [{ secretRef: { name: 'my-secret' } }],
},
],
},
},
},
},
];
const cronJobs: CronJob[] = [];
expect(getIsSecretInUse(secret, pods, jobs, cronJobs)).toBe(true);
});
it('should return true when a cronJob references the secret', () => {
const secret: Secret = {
metadata: { name: 'my-secret', 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: [{ secretRef: { name: 'my-secret' } }],
},
],
},
},
},
},
},
},
];
expect(getIsSecretInUse(secret, pods, jobs, cronJobs)).toBe(true);
});
});

View file

@ -1,30 +1,64 @@
import { Secret, Pod } from 'kubernetes-types/core/v1';
import { Secret, 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';
/**
* getIsSecretInUse returns true if the secret is referenced by any pod, job, or cronjob in the same namespace
*/
export function getIsSecretInUse(
secret: Secret,
pods: Pod[],
jobs: Job[],
cronJobs: CronJob[]
) {
// get all podspecs from pods, jobs and cronjobs that are in the same namespace
const podsInNamespace = pods
.filter((pod) => pod.metadata?.namespace === secret.metadata?.namespace)
.map((pod) => pod.spec);
const jobsInNamespace = jobs
.filter((job) => job.metadata?.namespace === secret.metadata?.namespace)
.map((job) => job.spec?.template.spec);
const cronJobsInNamespace = cronJobs
.filter(
(cronJob) => cronJob.metadata?.namespace === secret.metadata?.namespace
)
.map((cronJob) => cronJob.spec?.jobTemplate.spec?.template.spec);
const allPodSpecs = [
...podsInNamespace,
...jobsInNamespace,
...cronJobsInNamespace,
];
// getIsSecretInUse returns true if the secret is referenced by any
// application in the cluster
export function getIsSecretInUse(secret: Secret, applications: Application[]) {
return applications.some((app) => {
const appSpec = applicationIsKind<Pod>('Pod', app)
? app?.spec
: app?.spec?.template?.spec;
const hasEnvVarReference = appSpec?.containers.some((container) => {
const valueFromEnv = container.env?.some(
(envVar) =>
envVar.valueFrom?.secretKeyRef?.name === secret.metadata?.name
);
const envFromEnv = container.envFrom?.some(
(envVar) => envVar.secretRef?.name === secret.metadata?.name
);
return valueFromEnv || envFromEnv;
});
const hasVolumeReference = appSpec?.volumes?.some(
(volume) => volume.secret?.secretName === secret.metadata?.name
);
return hasEnvVarReference || hasVolumeReference;
// check if the secret is referenced by any pod, job or cronjob in the namespace
const isReferenced = allPodSpecs.some((podSpec) => {
if (!podSpec || !secret.metadata?.name) {
return false;
}
return doesPodSpecReferenceSecret(podSpec, secret.metadata?.name);
});
return isReferenced;
}
/**
* Checks if a PodSpec references a specific Secret.
* @param podSpec - The PodSpec object to check.
* @param secretName - The name of the Secret to check for references.
* @returns A boolean indicating whether the PodSpec references the Secret.
*/
function doesPodSpecReferenceSecret(podSpec: PodSpec, secretName: string) {
const hasEnvVarReference = podSpec?.containers.some((container) => {
const valueFromEnv = container.env?.some(
(envVar) => envVar.valueFrom?.secretKeyRef?.name === secretName
);
const envFromEnv = container.envFrom?.some(
(envVar) => envVar.secretRef?.name === secretName
);
return valueFromEnv || envFromEnv;
});
const hasVolumeReference = podSpec?.volumes?.some(
(volume) => volume.secret?.secretName === secretName
);
return hasEnvVarReference || hasVolumeReference;
}