Skip to content

Commit 0d769d9

Browse files
authored
feat(template): add owner ref to template deployments (#6649) (#6659)
feat(template): [backport] add owner ref to template deployments (#6649) Signed-off-by: Nixieboluo <me@sagirii.me>
1 parent 863b299 commit 0d769d9

File tree

10 files changed

+583
-199
lines changed

10 files changed

+583
-199
lines changed

frontend/providers/template/src/constants/keys.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ export const gpuResourceKey = 'nvidia.com/gpu';
1212
export const templateDeployKey = 'cloud.sealos.io/deploy-on-sealos';
1313
export const templateDisplayNameKey = 'cloud.sealos.io/deploy-on-sealos-displayName';
1414

15+
/**
16+
* ownerReferences (template instance cascade deletion)
17+
* - label key to indicate instance and its dependents are linked by ownerReferences
18+
*/
19+
export const ownerReferencesKey = 'cloud.sealos.io/owner-references';
20+
export const ownerReferencesReadyValue = 'ready';
21+
1522
// db
1623
export const kubeblocksTypeKey = 'clusterdefinition.kubeblocks.io/name';
1724
export const dbProviderKey = 'sealos-db-provider-cr';

frontend/providers/template/src/pages/api/applyApp.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { authSession } from '@/services/backend/auth';
22
import { getK8s } from '@/services/backend/kubernetes';
33
import { handleK8sError, jsonRes } from '@/services/backend/response';
44
import type { NextApiRequest, NextApiResponse } from 'next';
5+
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';
56

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

1920
try {
20-
const { applyYamlList } = await getK8s({
21+
const { applyYamlList, k8sCustomObjects, namespace } = await getK8s({
2122
kubeconfig: await authSession(req.headers)
2223
});
2324

24-
const applyRes = await applyYamlList(yamlList, type);
25+
const { appliedKinds } = await applyWithInstanceOwnerReferences(
26+
{ applyYamlList, k8sCustomObjects, namespace },
27+
yamlList,
28+
type
29+
);
2530

26-
jsonRes(res, { data: applyRes.map((item) => item.kind) });
31+
jsonRes(res, { data: appliedKinds });
2732
} catch (err: any) {
2833
return jsonRes(res, handleK8sError(err));
2934
}

frontend/providers/template/src/pages/api/instance/deleteByName.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
import type { NextApiRequest, NextApiResponse } from 'next';
22

33
import { authSession } from '@/services/backend/auth';
4-
import { CRDMeta, getK8s } from '@/services/backend/kubernetes';
4+
import { getK8s } from '@/services/backend/kubernetes';
55
import { jsonRes } from '@/services/backend/response';
6+
import { ResponseCode } from '@/types/response';
7+
import {
8+
deleteInstanceOnly,
9+
getInstanceOrThrow404,
10+
isInstanceOwnerReferencesReady,
11+
legacyDeleteInstanceAll
12+
} from '@/services/backend/instanceDelete';
613

714
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
815
try {
916
const { instanceName } = req.query as { instanceName: string };
10-
const { k8sCustomObjects, namespace } = await getK8s({
17+
const k8s = await getK8s({
1118
kubeconfig: await authSession(req.headers)
1219
});
1320

14-
const InstanceCRD: CRDMeta = {
15-
group: 'app.sealos.io',
16-
version: 'v1',
17-
namespace: namespace,
18-
plural: 'instances'
19-
};
21+
// Instance (read first, decide strategy)
22+
let instance;
23+
try {
24+
instance = await getInstanceOrThrow404(k8s.k8sCustomObjects, k8s.namespace, instanceName);
25+
} catch (error: any) {
26+
if (+error?.body?.code === 404) {
27+
return jsonRes(res, {
28+
code: ResponseCode.NOT_FOUND,
29+
message: 'Instance not found in namespace'
30+
});
31+
}
32+
throw error;
33+
}
2034

21-
// 删除指定名称的自定义对象
22-
await k8sCustomObjects.deleteNamespacedCustomObject(
23-
InstanceCRD.group,
24-
InstanceCRD.version,
25-
InstanceCRD.namespace,
26-
InstanceCRD.plural,
27-
instanceName
28-
);
35+
// New instances: only delete Instance, rely on GC cascade
36+
if (isInstanceOwnerReferencesReady(instance)) {
37+
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
38+
return jsonRes(res, { message: `Instance "${instanceName}" deleted successfully` });
39+
}
2940

30-
jsonRes(res, { message: `Custom object "${instanceName}" deleted successfully` });
41+
// Legacy instances: comprehensive cleanup then delete Instance
42+
await legacyDeleteInstanceAll(k8s, instanceName);
43+
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
44+
45+
return jsonRes(res, { message: `Instance "${instanceName}" deleted successfully` });
3146
} catch (err: any) {
3247
jsonRes(res, {
3348
code: 500,

frontend/providers/template/src/pages/api/v1/instance/[instanceName].ts

Lines changed: 21 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,14 @@ import { authSession } from '@/services/backend/auth';
22
import { getK8s } from '@/services/backend/kubernetes';
33
import { ResponseCode, ResponseMessages } from '@/types/response';
44
import { NextApiRequest, NextApiResponse } from 'next';
5-
import * as operations from '@/services/backend/operations';
65
import { jsonRes } from '@/services/backend/response';
76
import { deleteInstanceSchemas } from '@/types/apis';
8-
9-
// Helper function to delete resources with error handling
10-
async function deleteResourcesBatch<T>(
11-
resourcesPromise: Promise<T[]>,
12-
deleteOperation: (name: string) => Promise<any>,
13-
errorMessage: string
14-
): Promise<void> {
15-
const resourceNames = await resourcesPromise
16-
.then((resources) =>
17-
resources.map((item: any) => item?.metadata?.name).filter((name) => !!name)
18-
)
19-
.catch((error) => {
20-
if (error?.statusCode === 404) {
21-
return [];
22-
}
23-
throw error;
24-
});
25-
26-
const deleteResults = await Promise.allSettled(
27-
resourceNames.map((name: string) => deleteOperation(name))
28-
);
29-
30-
for (const result of deleteResults) {
31-
if (result.status === 'rejected' && +result?.reason?.body?.code !== 404) {
32-
throw new Error(errorMessage);
33-
}
34-
}
35-
}
7+
import {
8+
deleteInstanceOnly,
9+
getInstanceOrThrow404,
10+
isInstanceOwnerReferencesReady,
11+
legacyDeleteInstanceAll
12+
} from '@/services/backend/instanceDelete';
3613

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

6845
if (req.method === 'DELETE') {
6946
try {
70-
// Instance
47+
// Instance (read first, decide strategy)
48+
let instance;
7149
try {
72-
const instance = await operations.getInstance(
73-
k8s.k8sCustomObjects,
74-
k8s.namespace,
75-
instanceName
76-
);
77-
await operations.deleteInstance(
78-
k8s.k8sCustomObjects,
79-
k8s.namespace,
80-
instance.metadata.name
81-
);
50+
instance = await getInstanceOrThrow404(k8s.k8sCustomObjects, k8s.namespace, instanceName);
8251
} catch (error: any) {
8352
if (error?.body?.code !== 404) {
84-
throw new Error(error?.message || 'An error occurred whilst deleting instance.');
53+
throw error?.body || error;
8554
}
8655

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

93-
// AppLaunchpad
94-
await deleteResourcesBatch(
95-
operations.getAppLaunchpad(k8s.k8sApp, k8s.namespace, instanceName),
96-
(name: string) => operations.deleteAppLaunchpad(k8s, k8s.namespace, name),
97-
'An error occurred whilst deleting App Launchpad.'
98-
);
99-
100-
// Databases
101-
await deleteResourcesBatch(
102-
operations.getDatabases(k8s.k8sCustomObjects, k8s.namespace, instanceName),
103-
(name: string) => operations.deleteDatabase(k8s, k8s.namespace, name),
104-
'An error occurred whilst deleting Databases.'
105-
);
106-
107-
// App CR
108-
await deleteResourcesBatch(
109-
operations.getAppCRs(k8s.k8sCustomObjects, k8s.namespace, instanceName),
110-
(name: string) => operations.deleteAppCR(k8s.k8sCustomObjects, k8s.namespace, name),
111-
'An error occurred whilst deleting App CR.'
112-
);
113-
114-
// ObjectStorage
115-
await deleteResourcesBatch(
116-
operations.getObjectStorage(k8s.k8sCustomObjects, k8s.namespace, instanceName),
117-
(name: string) => operations.deleteObjectStorage(k8s.k8sCustomObjects, k8s.namespace, name),
118-
'An error occurred whilst deleting Object Storage.'
119-
);
120-
121-
// CronJobs
122-
await deleteResourcesBatch(
123-
operations.getCronJobs(k8s.k8sBatch, k8s.namespace, instanceName),
124-
(name: string) => operations.deleteCronJob(k8s.k8sBatch, k8s.namespace, name),
125-
'An error occurred whilst deleting CronJobs.'
126-
);
127-
128-
// Secrets
129-
await deleteResourcesBatch(
130-
operations.getSecrets(k8s.k8sCore, k8s.namespace, instanceName),
131-
(name: string) => operations.deleteSecret(k8s.k8sCore, k8s.namespace, name),
132-
'An error occurred whilst deleting Secrets.'
133-
);
134-
135-
// Jobs
136-
await deleteResourcesBatch(
137-
operations.getJobs(k8s.k8sBatch, k8s.namespace, instanceName),
138-
(name: string) => operations.deleteJob(k8s.k8sBatch, k8s.namespace, name),
139-
'An error occurred whilst deleting Jobs.'
140-
);
141-
142-
// CertIssuers
143-
await deleteResourcesBatch(
144-
operations.getCertIssuers(k8s.k8sCustomObjects, k8s.namespace, instanceName),
145-
(name: string) => operations.deleteCertIssuer(k8s.k8sCustomObjects, k8s.namespace, name),
146-
'An error occurred whilst deleting CertIssuers.'
147-
);
148-
149-
// Roles
150-
await deleteResourcesBatch(
151-
operations.getRoles(k8s.k8sAuth, k8s.namespace, instanceName),
152-
(name: string) => operations.deleteRole(k8s.k8sAuth, k8s.namespace, name),
153-
'An error occurred whilst deleting Roles.'
154-
);
155-
156-
// RoleBindings
157-
await deleteResourcesBatch(
158-
operations.getRoleBindings(k8s.k8sAuth, k8s.namespace, instanceName),
159-
(name: string) => operations.deleteRoleBinding(k8s.k8sAuth, k8s.namespace, name),
160-
'An error occurred whilst deleting RoleBindings.'
161-
);
162-
163-
// ServiceAccounts
164-
await deleteResourcesBatch(
165-
operations.getServiceAccounts(k8s.k8sCore, k8s.namespace, instanceName),
166-
(name: string) => operations.deleteServiceAccount(k8s.k8sCore, k8s.namespace, name),
167-
'An error occurred whilst deleting ServiceAccounts.'
168-
);
169-
170-
// ConfigMaps
171-
await deleteResourcesBatch(
172-
operations.getConfigMaps(k8s.k8sCore, k8s.namespace, instanceName),
173-
(name: string) => operations.deleteConfigMap(k8s.k8sCore, k8s.namespace, name),
174-
'An error occurred whilst deleting ConfigMaps.'
175-
);
176-
177-
// PrometheusRules
178-
await deleteResourcesBatch(
179-
operations.getPrometheusRules(k8s.k8sCustomObjects, k8s.namespace, instanceName),
180-
(name: string) =>
181-
operations.deletePrometheusRule(k8s.k8sCustomObjects, k8s.namespace, name),
182-
'An error occurred whilst deleting PrometheusRules.'
183-
);
184-
185-
// Prometheuses
186-
await deleteResourcesBatch(
187-
operations.getPrometheuses(k8s.k8sCustomObjects, k8s.namespace, instanceName),
188-
(name: string) => operations.deletePrometheus(k8s.k8sCustomObjects, k8s.namespace, name),
189-
'An error occurred whilst deleting Prometheuses.'
190-
);
191-
192-
// PersistentVolumeClaims
193-
await deleteResourcesBatch(
194-
operations.getPersistentVolumeClaims(k8s.k8sCore, k8s.namespace, instanceName),
195-
(name: string) => operations.deletePersistentVolumeClaim(k8s.k8sCore, k8s.namespace, name),
196-
'An error occurred whilst deleting PersistentVolumeClaims.'
197-
);
198-
199-
// ServiceMonitors
200-
await deleteResourcesBatch(
201-
operations.getServiceMonitors(k8s.k8sCustomObjects, k8s.namespace, instanceName),
202-
(name: string) =>
203-
operations.deleteServiceMonitor(k8s.k8sCustomObjects, k8s.namespace, name),
204-
'An error occurred whilst deleting ServiceMonitors.'
205-
);
206-
207-
// Probes
208-
await deleteResourcesBatch(
209-
operations.getProbes(k8s.k8sCustomObjects, k8s.namespace, instanceName),
210-
(name: string) => operations.deleteProbe(k8s.k8sCustomObjects, k8s.namespace, name),
211-
'An error occurred whilst deleting Probes.'
212-
);
62+
// New instances: only delete Instance, rely on GC cascade
63+
if (isInstanceOwnerReferencesReady(instance)) {
64+
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
65+
return jsonRes(res, {
66+
code: ResponseCode.SUCCESS,
67+
message: ResponseMessages[ResponseCode.SUCCESS]
68+
});
69+
}
21370

214-
// Services
215-
await deleteResourcesBatch(
216-
operations.getServices(k8s.k8sCore, k8s.namespace, instanceName),
217-
(name: string) => operations.deleteService(k8s.k8sCore, k8s.namespace, name),
218-
'An error occurred whilst deleting Services.'
219-
);
71+
// Legacy instances: comprehensive cleanup then delete Instance
72+
await legacyDeleteInstanceAll(k8s, instanceName);
73+
await deleteInstanceOnly(k8s.k8sCustomObjects, k8s.namespace, instance.metadata.name);
22074

22175
return jsonRes(res, {
22276
code: ResponseCode.SUCCESS,

frontend/providers/template/src/pages/api/v1alpha/applyYaml.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { authSession } from '@/services/backend/auth';
22
import { getK8s } from '@/services/backend/kubernetes';
33
import { jsonRes } from '@/services/backend/response';
44
import type { NextApiRequest, NextApiResponse } from 'next';
5+
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';
56

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

1819
try {
19-
const { applyYamlList } = await getK8s({
20+
const { applyYamlList, k8sCustomObjects, namespace } = await getK8s({
2021
kubeconfig: await authSession(req.headers)
2122
});
2223

23-
const applyRes = await applyYamlList(yamlList, 'create');
24+
const { appliedKinds } = await applyWithInstanceOwnerReferences(
25+
{ applyYamlList, k8sCustomObjects, namespace },
26+
yamlList,
27+
'create'
28+
);
2429

25-
jsonRes(res, { data: applyRes.map((item) => item.kind), message: 'success' });
30+
jsonRes(res, { data: appliedKinds, message: 'success' });
2631
} catch (err: any) {
2732
jsonRes(res, {
2833
code: 500,

frontend/providers/template/src/pages/api/v1alpha/createInstance.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { generateYamlList, parseTemplateString } from '@/utils/json-yaml';
66
import { mapValues, reduce } from 'lodash';
77
import type { NextApiRequest, NextApiResponse } from 'next';
88
import { GetTemplateByName } from '../getTemplateSource';
9+
import { applyWithInstanceOwnerReferences } from '@/services/backend/instanceOwnerReferencesApply';
910

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

17-
const { namespace, applyYamlList } = await getK8s({
18+
const { namespace, applyYamlList, k8sCustomObjects } = await getK8s({
1819
kubeconfig: await authSession(req.headers)
1920
});
2021

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

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

52-
const applyRes = await applyYamlList(yamls, 'create');
53+
const { appliedKinds } = await applyWithInstanceOwnerReferences(
54+
{ applyYamlList, k8sCustomObjects, namespace },
55+
yamls,
56+
'create'
57+
);
5358

5459
jsonRes(res, {
5560
code: 200,
56-
data: applyRes
61+
data: appliedKinds
5762
});
5863
} catch (err: any) {
5964
console.log(err);

0 commit comments

Comments
 (0)