1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

refactor(k8s): namespace core logic (#12142)

Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
This commit is contained in:
Steven Kang 2024-10-01 14:15:51 +13:00 committed by GitHub
parent da010f3d08
commit ea228c3d6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
276 changed files with 9241 additions and 3361 deletions

View file

@ -0,0 +1,20 @@
import { compact } from 'lodash';
export const queryKeys = {
list: (environmentId: number, options?: { withResourceQuota?: boolean }) =>
compact([
'environments',
environmentId,
'kubernetes',
'namespaces',
options?.withResourceQuota,
]),
namespace: (environmentId: number, namespace: string) =>
[
'environments',
environmentId,
'kubernetes',
'namespaces',
namespace,
] as const,
};

View file

@ -0,0 +1,47 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { withGlobalError, withInvalidate } from '@/react-tools/react-query';
import { queryKeys } from './queryKeys';
type DeleteNamespaceError = {
namespaceName: string;
error: string;
};
// when successful, the response will contain a list of deleted namespaces and a list of errors
type DeleteNamespacesResponse = {
deleted: string[];
errors: DeleteNamespaceError[];
} | null;
// useDeleteNamespaces is a react query mutation that removes a list of namespaces,
export function useDeleteNamespaces(environmentId: number) {
const queryClient = useQueryClient();
return useMutation(
({ namespaceNames }: { namespaceNames: string[] }) =>
deleteNamespaces(environmentId, namespaceNames),
{
...withInvalidate(queryClient, [queryKeys.list(environmentId)]),
...withGlobalError('Unable to delete namespaces'),
// onSuccess handled by the caller
}
);
}
async function deleteNamespaces(
environmentId: number,
namespaceNames: string[]
) {
try {
return await axios.delete<DeleteNamespacesResponse>(
`kubernetes/${environmentId}/namespaces`,
{
data: namespaceNames,
}
);
} catch (e) {
throw parseAxiosError(e, 'Unable to delete namespace');
}
}

View file

@ -1,5 +1,7 @@
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { PortainerNamespace } from '../types';
import { useNamespaceQuery } from './useNamespaceQuery';
export function useIsSystemNamespace(namespace: string) {
@ -10,3 +12,12 @@ export function useIsSystemNamespace(namespace: string) {
return !!query.data;
}
export function isSystemNamespace(
namespaceName: string,
namespaces?: PortainerNamespace[]
) {
return namespaces?.some(
(namespace) => namespace.Name === namespaceName && namespace.IsSystem
);
}

View file

@ -4,19 +4,21 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
import { notifyError } from '@/portainer/services/notifications';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { DefaultOrSystemNamespace } from '../types';
import { PortainerNamespace } from '../types';
export function useNamespaceQuery<T = DefaultOrSystemNamespace>(
import { queryKeys } from './queryKeys';
export function useNamespaceQuery<T = PortainerNamespace>(
environmentId: EnvironmentId,
namespace: string,
{
select,
}: {
select?(namespace: DefaultOrSystemNamespace): T;
select?(namespace: PortainerNamespace): T;
} = {}
) {
return useQuery(
['environments', environmentId, 'kubernetes', 'namespaces', namespace],
queryKeys.namespace(environmentId, namespace),
() => getNamespace(environmentId, namespace),
{
onError: (err) => {
@ -33,11 +35,11 @@ export async function getNamespace(
namespace: string
) {
try {
const { data: ns } = await axios.get<DefaultOrSystemNamespace>(
const { data: ns } = await axios.get<PortainerNamespace>(
`kubernetes/${environmentId}/namespaces/${namespace}`
);
return ns;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve namespace');
throw parseAxiosError(e, 'Unable to retrieve namespace');
}
}

View file

@ -1,40 +1,24 @@
import { useQuery } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { withError } from '@/react-tools/react-query';
import { withGlobalError } from '@/react-tools/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { Namespaces } from '../types';
import { getSelfSubjectAccessReview } from '../getSelfSubjectAccessReview';
import { PortainerNamespace } from '../types';
import { queryKeys } from './queryKeys';
export function useNamespacesQuery(
environmentId: EnvironmentId,
options?: { autoRefreshRate?: number }
options?: { autoRefreshRate?: number; withResourceQuota?: boolean }
) {
return useQuery(
['environments', environmentId, 'kubernetes', 'namespaces'],
async () => {
const namespaces = await getNamespaces(environmentId);
const namespaceNames = Object.keys(namespaces);
// use selfsubjectaccess reviews to avoid forbidden requests
const allNamespaceAccessReviews = await Promise.all(
namespaceNames.map((namespaceName) =>
getSelfSubjectAccessReview(environmentId, namespaceName)
)
);
const allowedNamespacesNames = allNamespaceAccessReviews
.filter((accessReview) => accessReview.status.allowed)
.map((accessReview) => accessReview.spec.resourceAttributes.namespace);
const allowedNamespaces = namespaceNames.reduce((acc, namespaceName) => {
if (allowedNamespacesNames.includes(namespaceName)) {
acc[namespaceName] = namespaces[namespaceName];
}
return acc;
}, {} as Namespaces);
return allowedNamespaces;
},
queryKeys.list(environmentId, {
withResourceQuota: !!options?.withResourceQuota,
}),
async () => getNamespaces(environmentId, options?.withResourceQuota),
{
...withError('Unable to get namespaces.'),
...withGlobalError('Unable to get namespaces.'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
@ -43,13 +27,18 @@ export function useNamespacesQuery(
}
// getNamespaces is used to retrieve namespaces using the Portainer backend with caching
async function getNamespaces(environmentId: EnvironmentId) {
export async function getNamespaces(
environmentId: EnvironmentId,
withResourceQuota?: boolean
) {
const params = withResourceQuota ? { withResourceQuota } : {};
try {
const { data: namespaces } = await axios.get<Namespaces>(
`kubernetes/${environmentId}/namespaces`
const { data: namespaces } = await axios.get<PortainerNamespace[]>(
`kubernetes/${environmentId}/namespaces`,
{ params }
);
return namespaces;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve namespaces');
throw parseAxiosError(e, 'Unable to retrieve namespaces');
}
}