1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 22:05:23 +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,52 @@
import { EnvironmentId } from '@/react/portainer/environments/types';
import { ConfigMapQueryParams, SecretQueryParams } from './types';
export const configMapQueryKeys = {
configMap: (
environmentId: EnvironmentId,
namespace: string,
configMap: string
) => [
'environments',
environmentId,
'kubernetes',
'configmaps',
'namespaces',
namespace,
configMap,
],
configMaps: (environmentId: EnvironmentId, namespace?: string) => [
'environments',
environmentId,
'kubernetes',
'configmaps',
'namespaces',
namespace,
],
configMapsForCluster: (
environmentId: EnvironmentId,
params?: ConfigMapQueryParams
) =>
params
? ['environments', environmentId, 'kubernetes', 'configmaps', params]
: ['environments', environmentId, 'kubernetes', 'configmaps'],
};
export const secretQueryKeys = {
secrets: (environmentId: EnvironmentId, namespace?: string) => [
'environments',
environmentId,
'kubernetes',
'secrets',
'namespaces',
namespace,
],
secretsForCluster: (
environmentId: EnvironmentId,
params?: SecretQueryParams
) =>
params
? ['environments', environmentId, 'kubernetes', 'secrets', params]
: ['environments', environmentId, 'kubernetes', 'secrets'],
};

View file

@ -0,0 +1,2 @@
export type ConfigMapQueryParams = { isUsed?: boolean };
export type SecretQueryParams = { isUsed?: boolean };

View file

@ -0,0 +1,48 @@
import { useQuery } from '@tanstack/react-query';
import { withGlobalError } from '@/react-tools/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Configuration } from '../types';
import { configMapQueryKeys } from './query-keys';
import { ConfigMapQueryParams } from './types';
export function useConfigMap(
environmentId: EnvironmentId,
namespace: string,
configMap: string,
options?: { autoRefreshRate?: number } & ConfigMapQueryParams
) {
return useQuery(
configMapQueryKeys.configMap(environmentId, namespace, configMap),
() => getConfigMap(environmentId, namespace, configMap, { withData: true }),
{
...withGlobalError('Unable to retrieve ConfigMaps for cluster'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
}
);
}
// get a configmap
async function getConfigMap(
environmentId: EnvironmentId,
namespace: string,
configMap: string,
params?: { withData?: boolean }
) {
try {
const { data } = await axios.get<Configuration[]>(
`/kubernetes/${environmentId}/namespaces/${namespace}/configmaps/${configMap}`,
{ params }
);
return data;
} catch (e) {
// use parseAxiosError instead of parseKubernetesAxiosError
// because this is an internal portainer api endpoint, not through the kube proxy
throw parseAxiosError(e, 'Unable to retrieve ConfigMaps');
}
}

View file

@ -0,0 +1,41 @@
import { useQuery } from '@tanstack/react-query';
import { ConfigMap, ConfigMapList } from 'kubernetes-types/core/v1';
import axios from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { withGlobalError } from '@/react-tools/react-query';
import { parseKubernetesAxiosError } from '../../axiosError';
import { configMapQueryKeys } from './query-keys';
// returns a usequery hook for the list of configmaps within a namespace from the kubernetes API
export function useConfigMaps(environmentId: EnvironmentId, namespace: string) {
return useQuery(
configMapQueryKeys.configMaps(environmentId, namespace),
() => (namespace ? getConfigMaps(environmentId, namespace) : []),
{
...withGlobalError(
`Unable to get ConfigMaps in namespace '${namespace}'`
),
enabled: !!namespace,
}
);
}
// get all configmaps for a namespace
async function getConfigMaps(environmentId: EnvironmentId, namespace?: string) {
try {
const { data } = await axios.get<ConfigMapList>(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/configmaps`
);
// when fetching a list, the kind isn't appended to the items, so we need to add it
const configmaps: ConfigMap[] = data.items.map((configmap) => ({
...configmap,
kind: 'ConfigMap',
}));
return configmaps;
} catch (e) {
throw parseKubernetesAxiosError(e, 'Unable to retrieve ConfigMaps');
}
}

View file

@ -0,0 +1,53 @@
import { useQuery } from '@tanstack/react-query';
import { withGlobalError } from '@/react-tools/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Configuration } from '../types';
import { configMapQueryKeys } from './query-keys';
import { ConfigMapQueryParams } from './types';
export function useConfigMapsForCluster<TData = Configuration[]>(
environmentId: EnvironmentId,
options?: {
autoRefreshRate?: number;
select?: (data: Configuration[]) => TData;
} & ConfigMapQueryParams
) {
const { autoRefreshRate, select, ...params } = options ?? {};
return useQuery(
configMapQueryKeys.configMapsForCluster(environmentId, params),
() =>
getConfigMapsForCluster(environmentId, {
...params,
isUsed: params?.isUsed,
}),
{
...withGlobalError('Unable to retrieve ConfigMaps for cluster'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
select,
}
);
}
// get all configmaps for a cluster
async function getConfigMapsForCluster(
environmentId: EnvironmentId,
params?: { withData?: boolean; isUsed?: boolean }
) {
try {
const { data } = await axios.get<Configuration[]>(
`/kubernetes/${environmentId}/configmaps`,
{ params }
);
return data;
} catch (e) {
// use parseAxiosError instead of parseKubernetesAxiosError
// because this is an internal portainer api endpoint, not through the kube proxy
throw parseAxiosError(e, 'Unable to retrieve ConfigMaps');
}
}

View file

@ -0,0 +1,77 @@
import { useMutation } from '@tanstack/react-query';
import { queryClient, withGlobalError } from '@/react-tools/react-query';
import axios from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import {
error as notifyError,
notifySuccess,
} from '@/portainer/services/notifications';
import { isFulfilled, isRejected } from '@/portainer/helpers/promise-utils';
import { pluralize } from '@/portainer/helpers/strings';
import { parseKubernetesAxiosError } from '../../axiosError';
import { configMapQueryKeys } from './query-keys';
export function useDeleteConfigMaps(environmentId: EnvironmentId) {
return useMutation(
async (configMaps: { namespace: string; name: string }[]) => {
const promises = await Promise.allSettled(
configMaps.map(({ namespace, name }) =>
deleteConfigMap(environmentId, namespace, name)
)
);
const successfulConfigMaps = promises
.filter(isFulfilled)
.map((_, index) => configMaps[index].name);
const failedConfigMaps = promises
.filter(isRejected)
.map(({ reason }, index) => ({
name: configMaps[index].name,
reason,
}));
return { failedConfigMaps, successfulConfigMaps };
},
{
...withGlobalError('Unable to remove ConfigMaps'),
onSuccess: ({ failedConfigMaps, successfulConfigMaps }) => {
// Promise.allSettled can also resolve with errors, so check for errors here
// show an error message for each configmap that failed to delete
failedConfigMaps.forEach(({ name, reason }) => {
notifyError(
`Failed to remove ConfigMap '${name}'`,
new Error(reason.message) as Error
);
});
// show one summary message for all successful deletes
if (successfulConfigMaps.length) {
notifySuccess(
`${pluralize(
successfulConfigMaps.length,
'ConfigMap'
)} successfully removed`,
successfulConfigMaps.join(', ')
);
}
queryClient.invalidateQueries({
queryKey: configMapQueryKeys.configMapsForCluster(environmentId),
});
},
}
);
}
async function deleteConfigMap(
environmentId: EnvironmentId,
namespace: string,
name: string
) {
try {
await axios.delete(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/configmaps/${name}`
);
} catch (e) {
throw parseKubernetesAxiosError(e, 'Unable to remove ConfigMap');
}
}

View file

@ -0,0 +1,76 @@
import { useMutation } from '@tanstack/react-query';
import { queryClient, withGlobalError } from '@/react-tools/react-query';
import axios from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import {
error as notifyError,
notifySuccess,
} from '@/portainer/services/notifications';
import { isFulfilled, isRejected } from '@/portainer/helpers/promise-utils';
import { pluralize } from '@/portainer/helpers/strings';
import { parseKubernetesAxiosError } from '../../axiosError';
import { secretQueryKeys } from './query-keys';
export function useDeleteSecrets(environmentId: EnvironmentId) {
return useMutation(
async (secrets: { namespace: string; name: string }[]) => {
const promises = await Promise.allSettled(
secrets.map(({ namespace, name }) =>
deleteSecret(environmentId, namespace, name)
)
);
const successfulSecrets = promises
.filter(isFulfilled)
.map((_, index) => secrets[index].name);
const failedSecrets = promises
.filter(isRejected)
.map(({ reason }, index) => ({
name: secrets[index].name,
reason,
}));
return { failedSecrets, successfulSecrets };
},
{
...withGlobalError('Unable to remove secrets'),
onSuccess: ({ failedSecrets, successfulSecrets }) => {
// show an error message for each secret that failed to delete
failedSecrets.forEach(({ name, reason }) => {
notifyError(
`Failed to remove secret '${name}'`,
new Error(reason.message) as Error
);
});
// show one summary message for all successful deletes
if (successfulSecrets.length) {
notifySuccess(
`${pluralize(
successfulSecrets.length,
'Secret'
)} successfully removed`,
successfulSecrets.join(', ')
);
}
queryClient.invalidateQueries({
queryKey: secretQueryKeys.secretsForCluster(environmentId),
});
},
}
);
}
async function deleteSecret(
environmentId: EnvironmentId,
namespace: string,
name: string
) {
try {
await axios.delete(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/secrets/${name}`
);
} catch (e) {
throw parseKubernetesAxiosError(e, 'Unable to remove secret');
}
}

View file

@ -0,0 +1,39 @@
import { useQuery } from '@tanstack/react-query';
import { Secret, SecretList } from 'kubernetes-types/core/v1';
import { withGlobalError } from '@/react-tools/react-query';
import axios from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { parseKubernetesAxiosError } from '../../axiosError';
import { secretQueryKeys } from './query-keys';
// returns a usequery hook for the list of secrets from the kubernetes API
export function useSecrets(environmentId: EnvironmentId, namespace?: string) {
return useQuery(
secretQueryKeys.secrets(environmentId, namespace),
() => (namespace ? getSecrets(environmentId, namespace) : []),
{
...withGlobalError(`Unable to get secrets in namespace '${namespace}'`),
enabled: !!namespace,
}
);
}
// get all secrets for a namespace
async function getSecrets(environmentId: EnvironmentId, namespace: string) {
try {
const { data } = await axios.get<SecretList>(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/secrets`
);
// when fetching a list, the kind isn't appended to the items, so we need to add it
const secrets: Secret[] = data.items.map((secret) => ({
...secret,
kind: 'Secret',
}));
return secrets;
} catch (e) {
throw parseKubernetesAxiosError(e, 'Unable to retrieve secrets');
}
}

View file

@ -0,0 +1,59 @@
import { useQuery } from '@tanstack/react-query';
import { withGlobalError } from '@/react-tools/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Configuration } from '../types';
import { SecretQueryParams } from './types';
import { secretQueryKeys } from './query-keys';
export function useSecretsForCluster<TData = Configuration[]>(
environmentId: EnvironmentId,
options?: {
autoRefreshRate?: number;
select?: (data: Configuration[]) => TData;
} & SecretQueryParams
) {
const { autoRefreshRate, select, ...params } = options ?? {};
return useQuery(
secretQueryKeys.secretsForCluster(environmentId, params),
() =>
getSecretsForCluster(environmentId, {
...params,
isUsed: params?.isUsed,
}),
{
...withGlobalError('Unable to retrieve secrets for cluster'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
select,
}
);
}
async function getSecretsForCluster(
environmentId: EnvironmentId,
params?: { withData?: boolean; isUsed?: boolean }
) {
const secrets = await getSecrets(environmentId, params);
return secrets;
}
// get all secrets for a cluster
async function getSecrets(
environmentId: EnvironmentId,
params?: { withData?: boolean; isUsed?: boolean } | undefined
) {
try {
const { data } = await axios.get<Configuration[]>(
`/kubernetes/${environmentId}/secrets`,
{ params }
);
return data;
} catch (e) {
throw parseAxiosError(e, 'Unable to retrieve secrets');
}
}