Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frontend/providers/template/src/constants/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ export const gpuResourceKey = 'nvidia.com/gpu';
export const templateDeployKey = 'cloud.sealos.io/deploy-on-sealos';
export const templateDisplayNameKey = 'cloud.sealos.io/deploy-on-sealos-displayName';

/**
* ownerReferences (template instance cascade deletion)
* - label key to indicate instance and its dependents are linked by ownerReferences
*/
export const ownerReferencesKey = 'cloud.sealos.io/owner-references';
export const ownerReferencesReadyValue = 'ready';

// db
export const kubeblocksTypeKey = 'clusterdefinition.kubeblocks.io/name';
export const dbProviderKey = 'sealos-db-provider-cr';
Expand Down
11 changes: 8 additions & 3 deletions frontend/providers/template/src/pages/api/applyApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { authSession } from '@/services/backend/auth';
import { getK8s } from '@/services/backend/kubernetes';
import { handleK8sError, jsonRes } from '@/services/backend/response';
import type { NextApiRequest, NextApiResponse } from 'next';
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { yamlList, type = 'create' } = req.body as {
Expand All @@ -17,13 +18,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}

try {
const { applyYamlList } = await getK8s({
const { applyYamlList, k8sCustomObjects, namespace } = await getK8s({
kubeconfig: await authSession(req.headers)
});

const applyRes = await applyYamlList(yamlList, type);
const { appliedKinds } = await applyWithInstanceOwnerReferences(
{ applyYamlList, k8sCustomObjects, namespace },
yamlList,
type
);

jsonRes(res, { data: applyRes.map((item) => item.kind) });
jsonRes(res, { data: appliedKinds });
} catch (err: any) {
return jsonRes(res, handleK8sError(err));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
import type { NextApiRequest, NextApiResponse } from 'next';

import { authSession } from '@/services/backend/auth';
import { CRDMeta, getK8s } from '@/services/backend/kubernetes';
import { getK8s } from '@/services/backend/kubernetes';
import { jsonRes } from '@/services/backend/response';
import { ResponseCode } from '@/types/response';
import {
deleteInstanceOnly,
getInstanceOrThrow404,
isInstanceOwnerReferencesReady,
legacyDeleteInstanceAll
} from '@/services/backend/instanceDelete';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { instanceName } = req.query as { instanceName: string };
const { k8sCustomObjects, namespace } = await getK8s({
const k8s = await getK8s({
kubeconfig: await authSession(req.headers)
});

const InstanceCRD: CRDMeta = {
group: 'app.sealos.io',
version: 'v1',
namespace: namespace,
plural: 'instances'
};
// Instance (read first, decide strategy)
let instance;
try {
instance = await getInstanceOrThrow404(k8s.k8sCustomObjects, k8s.namespace, instanceName);
} catch (error: any) {
if (+error?.body?.code === 404) {
return jsonRes(res, {
code: ResponseCode.NOT_FOUND,
message: 'Instance not found in namespace'
});
}
throw error;
}

// 删除指定名称的自定义对象
await k8sCustomObjects.deleteNamespacedCustomObject(
InstanceCRD.group,
InstanceCRD.version,
InstanceCRD.namespace,
InstanceCRD.plural,
instanceName
);
// New instances: only delete Instance, rely on GC cascade
if (isInstanceOwnerReferencesReady(instance)) {
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
return jsonRes(res, { message: `Instance "${instanceName}" deleted successfully` });
}

jsonRes(res, { message: `Custom object "${instanceName}" deleted successfully` });
// Legacy instances: comprehensive cleanup then delete Instance
await legacyDeleteInstanceAll(k8s, instanceName);
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);

return jsonRes(res, { message: `Instance "${instanceName}" deleted successfully` });
} catch (err: any) {
jsonRes(res, {
code: 500,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,14 @@ import { authSession } from '@/services/backend/auth';
import { getK8s } from '@/services/backend/kubernetes';
import { ResponseCode, ResponseMessages } from '@/types/response';
import { NextApiRequest, NextApiResponse } from 'next';
import * as operations from '@/services/backend/operations';
import { jsonRes } from '@/services/backend/response';
import { deleteInstanceSchemas } from '@/types/apis';

// Helper function to delete resources with error handling
async function deleteResourcesBatch<T>(
resourcesPromise: Promise<T[]>,
deleteOperation: (name: string) => Promise<any>,
errorMessage: string
): Promise<void> {
const resourceNames = await resourcesPromise
.then((resources) =>
resources.map((item: any) => item?.metadata?.name).filter((name) => !!name)
)
.catch((error) => {
if (error?.statusCode === 404) {
return [];
}
throw error;
});

const deleteResults = await Promise.allSettled(
resourceNames.map((name: string) => deleteOperation(name))
);

for (const result of deleteResults) {
if (result.status === 'rejected' && +result?.reason?.body?.code !== 404) {
throw new Error(errorMessage);
}
}
}
import {
deleteInstanceOnly,
getInstanceOrThrow404,
isInstanceOwnerReferencesReady,
legacyDeleteInstanceAll
} from '@/services/backend/instanceDelete';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Parse parameters
Expand Down Expand Up @@ -67,21 +44,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

if (req.method === 'DELETE') {
try {
// Instance
// Instance (read first, decide strategy)
let instance;
try {
const instance = await operations.getInstance(
k8s.k8sCustomObjects,
k8s.namespace,
instanceName
);
await operations.deleteInstance(
k8s.k8sCustomObjects,
k8s.namespace,
instance.metadata.name
);
instance = await getInstanceOrThrow404(k8s.k8sCustomObjects, k8s.namespace, instanceName);
} catch (error: any) {
if (error?.body?.code !== 404) {
throw new Error(error?.message || 'An error occurred whilst deleting instance.');
throw error?.body || error;
}

return jsonRes(res, {
Expand All @@ -90,133 +59,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}

// AppLaunchpad
await deleteResourcesBatch(
operations.getAppLaunchpad(k8s.k8sApp, k8s.namespace, instanceName),
(name: string) => operations.deleteAppLaunchpad(k8s, k8s.namespace, name),
'An error occurred whilst deleting App Launchpad.'
);

// Databases
await deleteResourcesBatch(
operations.getDatabases(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deleteDatabase(k8s, k8s.namespace, name),
'An error occurred whilst deleting Databases.'
);

// App CR
await deleteResourcesBatch(
operations.getAppCRs(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deleteAppCR(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting App CR.'
);

// ObjectStorage
await deleteResourcesBatch(
operations.getObjectStorage(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deleteObjectStorage(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting Object Storage.'
);

// CronJobs
await deleteResourcesBatch(
operations.getCronJobs(k8s.k8sBatch, k8s.namespace, instanceName),
(name: string) => operations.deleteCronJob(k8s.k8sBatch, k8s.namespace, name),
'An error occurred whilst deleting CronJobs.'
);

// Secrets
await deleteResourcesBatch(
operations.getSecrets(k8s.k8sCore, k8s.namespace, instanceName),
(name: string) => operations.deleteSecret(k8s.k8sCore, k8s.namespace, name),
'An error occurred whilst deleting Secrets.'
);

// Jobs
await deleteResourcesBatch(
operations.getJobs(k8s.k8sBatch, k8s.namespace, instanceName),
(name: string) => operations.deleteJob(k8s.k8sBatch, k8s.namespace, name),
'An error occurred whilst deleting Jobs.'
);

// CertIssuers
await deleteResourcesBatch(
operations.getCertIssuers(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deleteCertIssuer(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting CertIssuers.'
);

// Roles
await deleteResourcesBatch(
operations.getRoles(k8s.k8sAuth, k8s.namespace, instanceName),
(name: string) => operations.deleteRole(k8s.k8sAuth, k8s.namespace, name),
'An error occurred whilst deleting Roles.'
);

// RoleBindings
await deleteResourcesBatch(
operations.getRoleBindings(k8s.k8sAuth, k8s.namespace, instanceName),
(name: string) => operations.deleteRoleBinding(k8s.k8sAuth, k8s.namespace, name),
'An error occurred whilst deleting RoleBindings.'
);

// ServiceAccounts
await deleteResourcesBatch(
operations.getServiceAccounts(k8s.k8sCore, k8s.namespace, instanceName),
(name: string) => operations.deleteServiceAccount(k8s.k8sCore, k8s.namespace, name),
'An error occurred whilst deleting ServiceAccounts.'
);

// ConfigMaps
await deleteResourcesBatch(
operations.getConfigMaps(k8s.k8sCore, k8s.namespace, instanceName),
(name: string) => operations.deleteConfigMap(k8s.k8sCore, k8s.namespace, name),
'An error occurred whilst deleting ConfigMaps.'
);

// PrometheusRules
await deleteResourcesBatch(
operations.getPrometheusRules(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) =>
operations.deletePrometheusRule(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting PrometheusRules.'
);

// Prometheuses
await deleteResourcesBatch(
operations.getPrometheuses(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deletePrometheus(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting Prometheuses.'
);

// PersistentVolumeClaims
await deleteResourcesBatch(
operations.getPersistentVolumeClaims(k8s.k8sCore, k8s.namespace, instanceName),
(name: string) => operations.deletePersistentVolumeClaim(k8s.k8sCore, k8s.namespace, name),
'An error occurred whilst deleting PersistentVolumeClaims.'
);

// ServiceMonitors
await deleteResourcesBatch(
operations.getServiceMonitors(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) =>
operations.deleteServiceMonitor(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting ServiceMonitors.'
);

// Probes
await deleteResourcesBatch(
operations.getProbes(k8s.k8sCustomObjects, k8s.namespace, instanceName),
(name: string) => operations.deleteProbe(k8s.k8sCustomObjects, k8s.namespace, name),
'An error occurred whilst deleting Probes.'
);
// New instances: only delete Instance, rely on GC cascade
if (isInstanceOwnerReferencesReady(instance)) {
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
return jsonRes(res, {
code: ResponseCode.SUCCESS,
message: ResponseMessages[ResponseCode.SUCCESS]
});
}

// Services
await deleteResourcesBatch(
operations.getServices(k8s.k8sCore, k8s.namespace, instanceName),
(name: string) => operations.deleteService(k8s.k8sCore, k8s.namespace, name),
'An error occurred whilst deleting Services.'
);
// Legacy instances: comprehensive cleanup then delete Instance
await legacyDeleteInstanceAll(k8s, instanceName);
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);

return jsonRes(res, {
code: ResponseCode.SUCCESS,
Expand Down
11 changes: 8 additions & 3 deletions frontend/providers/template/src/pages/api/v1alpha/applyYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { authSession } from '@/services/backend/auth';
import { getK8s } from '@/services/backend/kubernetes';
import { jsonRes } from '@/services/backend/response';
import type { NextApiRequest, NextApiResponse } from 'next';
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { yamlList } = req.body as {
Expand All @@ -16,13 +17,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}

try {
const { applyYamlList } = await getK8s({
const { applyYamlList, k8sCustomObjects, namespace } = await getK8s({
kubeconfig: await authSession(req.headers)
});

const applyRes = await applyYamlList(yamlList, 'create');
const { appliedKinds } = await applyWithInstanceOwnerReferences(
{ applyYamlList, k8sCustomObjects, namespace },
yamlList,
'create'
);

jsonRes(res, { data: applyRes.map((item) => item.kind), message: 'success' });
jsonRes(res, { data: appliedKinds, message: 'success' });
} catch (err: any) {
jsonRes(res, {
code: 500,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { generateYamlList, parseTemplateString } from '@/utils/json-yaml';
import { mapValues, reduce } from 'lodash';
import type { NextApiRequest, NextApiResponse } from 'next';
import { GetTemplateByName } from '../getTemplateSource';
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
Expand All @@ -14,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
templateForm: Record<string, string>;
};

const { namespace, applyYamlList } = await getK8s({
const { namespace, applyYamlList, k8sCustomObjects } = await getK8s({
kubeconfig: await authSession(req.headers)
});

Expand Down Expand Up @@ -49,11 +50,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

const yamls = correctYaml.map((item) => item.value);

const applyRes = await applyYamlList(yamls, 'create');
const { appliedKinds } = await applyWithInstanceOwnerReferences(
{ applyYamlList, k8sCustomObjects, namespace },
yamls,
'create'
);

jsonRes(res, {
code: 200,
data: applyRes
data: appliedKinds
});
} catch (err: any) {
console.log(err);
Expand Down
Loading
Loading