Skip to content

Commit 4136a5b

Browse files
committed
✨add uninstall single protected app
1 parent 0f3fbee commit 4136a5b

File tree

13 files changed

+946
-29
lines changed

13 files changed

+946
-29
lines changed

controlpanel/api/routes/deployment-manager.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,5 +331,197 @@ router.get('/deployments/:namespace', authorizationFromCu_id, async (req, res) =
331331
return res.status(500).send({ type: 'error', code: 500, message: 'Server error' });
332332
}
333333
});
334+
/**
335+
* @swagger
336+
* /deployment-manager/uninstall:
337+
* delete:
338+
* summary: Uninstall Cloud Active Defense from chosen application
339+
* tags: [Deployment Manager]
340+
* requestBody:
341+
* required: true
342+
* description: The name of the deployment app and the namespace
343+
* content:
344+
* application/json:
345+
* schema:
346+
* type: object
347+
* properties:
348+
* deploymentAppName:
349+
* type: string
350+
* pattern: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/
351+
* maxLength: 63
352+
* description: The name of the deployment app
353+
* example: myapp
354+
* namespace:
355+
* type: string
356+
* pattern: /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/
357+
* maxLength: 63
358+
* description: The namespace of the deployment app
359+
* example: demo-ns
360+
* responses:
361+
* 200:
362+
* description: Application uninstalled successfully
363+
* content:
364+
* application/json:
365+
* schema:
366+
* type: object
367+
* properties:
368+
* type:
369+
* type: string
370+
* enum: [success]
371+
* example: success
372+
* code:
373+
* type: number
374+
* enum: [200]
375+
* example: 200
376+
* message:
377+
* type: string
378+
* example: Application uninstalled successfully
379+
* 404:
380+
* description: Not found
381+
* content:
382+
* application/json:
383+
* schema:
384+
* oneOf:
385+
* - type: object
386+
* properties:
387+
* type:
388+
* type: string
389+
* enum: [error]
390+
* example: error
391+
* code:
392+
* type: number
393+
* enum: [404]
394+
* example: 404
395+
* message:
396+
* type: string
397+
* example: Customer not found
398+
* - type: object
399+
* properties:
400+
* type:
401+
* type: string
402+
* enum: [error]
403+
* example: error
404+
* code:
405+
* type: number
406+
* enum: [404]
407+
* example: 404
408+
* message:
409+
* type: string
410+
* example: Protected app not found
411+
* - type: object
412+
* properties:
413+
* type:
414+
* type: string
415+
* enum: [error]
416+
* example: error
417+
* code:
418+
* type: number
419+
* enum: [404]
420+
* example: 404
421+
* message:
422+
* type: string
423+
* example: |
424+
* Something went wrong in deployment manager: <message>
425+
* 400:
426+
* description: Bad request
427+
* content:
428+
* application/json:
429+
* schema:
430+
* oneOf:
431+
* - type: object
432+
* properties:
433+
* type:
434+
* type: string
435+
* enum: [error]
436+
* example: error
437+
* code:
438+
* type: number
439+
* enum: [400]
440+
* example: 400
441+
* message:
442+
* type: string
443+
* example: |
444+
* Something went wrong in deployment manager: <message>
445+
* - type: object
446+
* properties:
447+
* type:
448+
* type: string
449+
* enum: [error]
450+
* example: error
451+
* code:
452+
* type: number
453+
* enum: [400]
454+
* example: 400
455+
* message:
456+
* type: string
457+
* example: Customer ID is required
458+
* - type: object
459+
* properties:
460+
* type:
461+
* type: string
462+
* enum: [error]
463+
* example: error
464+
* code:
465+
* type: number
466+
* enum: [400]
467+
* example: 400
468+
* message:
469+
* type: string
470+
* example: Namespace is required
471+
* - type: object
472+
* properties:
473+
* type:
474+
* type: string
475+
* enum: [error]
476+
* example: error
477+
* code:
478+
* type: number
479+
* enum: [400]
480+
* example: 400
481+
* message:
482+
* type: string
483+
* example: Deployment name is required
484+
* - type: object
485+
* properties:
486+
* type:
487+
* type: string
488+
* enum: [error]
489+
* example: error
490+
* code:
491+
* type: number
492+
* enum: [400]
493+
* example: 400
494+
* message:
495+
* type: string
496+
* example: Invalid customer ID, must be a valid UUID
497+
* 500:
498+
* description: Server error
499+
* content:
500+
* application/json:
501+
* schema:
502+
* type: object
503+
* properties:
504+
* code:
505+
* type: integer
506+
* enum: [500]
507+
* example: 500
508+
* type:
509+
* type: string
510+
* enum: [error]
511+
* example: error
512+
* message:
513+
* type: string
514+
* example: |
515+
* Something went wrong in deployment manager: <message>
516+
*/
517+
router.delete('/uninstall', authorizationFromCu_id, async (req, res) => {
518+
try {
519+
const result = await deploymentManagerService.uninstallCADToApp(req.cu_id, req.body.deploymentAppName, req.body.namespace);
520+
return res.status(result.code).send(result);
521+
} catch(e) {
522+
console.error(e);
523+
return res.status(500).send({ type: 'error', code: 500, message: 'Server error' });
524+
}
525+
});
334526

335527
module.exports = router;

controlpanel/api/services/customer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ module.exports = {
7676
if (!customer) return { type: 'error', code: 404, message: 'Customer not found' };
7777
if (!customer.kubeconfig) return { type: 'error', code: 400, message: 'No kubeconfig uploaded' };
7878

79-
console.trace(`Cleaning customer ${cu_id}...`);
8079
await ProtectedApp.destroy({ where: {
8180
cu_id: cu_id,
8281
[sequelize.Op.or]: [

controlpanel/api/services/deployment-manager.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,34 @@ module.exports = {
8989
} catch (e) {
9090
throw e;
9191
}
92+
},
93+
/**
94+
* Uninstall Cloud Active Defense from a specific application
95+
* @param {UUID} cu_id Customer ID
96+
* @param {string} deploymentAppName Name of the application deployment
97+
* @param {string} namespace Namespace of the application
98+
* @returns {{code: number, type: string, message: string}}
99+
*/
100+
uninstallCADToApp: async (cu_id, deploymentAppName, namespace) => {
101+
try {
102+
if (!cu_id) return { code: 400, type: 'error', message: 'Customer ID is required' };
103+
if (!namespace) return { code: 400, type: 'error', message: 'Namespace is required' };
104+
if (!deploymentAppName) return { code: 400, type: 'error', message: 'Deployment name is required' };
105+
if (!isUUID(cu_id)) return { code: 400, type: 'error', message: 'Invalid customer ID, must be a valid UUID' };
106+
107+
const customer = await Customer.findOne({ where: { id: cu_id } });
108+
if (!customer) return { type: 'error', code: 404, message: 'Customer not found' };
109+
110+
const deletedProtectedAppNb = await ProtectedApp.destroy({ where: { namespace, application: deploymentAppName, cu_id }});
111+
if (deletedProtectedAppNb == 0) return { type: 'error', code: 404, message: 'Protected app not found' };
112+
113+
const responseEnvoy = await axios.delete(`${process.env.DEPLOYMENT_MANAGER_URL}/envoy`, { data: { namespace, cu_id, deploymentName: deploymentAppName }, validateStatus:_=>true } )
114+
if (responseEnvoy.data.type !== 'success') return { ...responseEnvoy.data, message: `Something went wrong in deployment manager: ${responseEnvoy.data.message}` };
115+
const responseTelemetry = await axios.delete(`${process.env.DEPLOYMENT_MANAGER_URL}/telemetry`, { data: { namespace, cu_id, deploymentName: deploymentAppName }, validateStatus:_=>true } )
116+
if (responseTelemetry.data.type !== 'success') return { ...responseTelemetry.data, message: `Something went wrong in deployment manager: ${responseTelemetry.data.message}` };
117+
return { type: 'success', code: 200, message: 'Application uninstalled successfully' };
118+
} catch (e) {
119+
throw e;
120+
}
92121
}
93122
}

controlpanel/cad/src/app/pages/system/system.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ <h1>System 🛠️</h1>
4242
<div class="deployment-loading-install-icon" *ngIf="deployment.loadingInstall"></div>
4343
<label class="switch" *ngIf="!deployment.loadingInstall">
4444
<input type="checkbox" [checked]="deployment.protected" (click)="onSwitchChange($event, deployment)">
45-
<span class="slider round"></span>
45+
<span class="slider round" [ngClass]="{'disableSwitch': deployment.disableSwitch}"></span>
4646
</label>
4747
</td>
4848
<td class="centered">

controlpanel/cad/src/app/pages/system/system.component.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@
187187
input:checked + .slider {
188188
background-color: #2196F3;
189189
border: #2196F3 solid 1px;
190-
cursor: not-allowed;
191190
}
192191
input:checked + .slider:before {
193192
transform: translateX(20px);
@@ -205,6 +204,9 @@
205204
input:disabled + input:not(:checked) + .slider:before {
206205
background-color: #bdbdbd;
207206
}
207+
.disableSwitch {
208+
cursor: not-allowed;
209+
}
208210
}
209211
.loading-deployment {
210212
height: 3rem;

controlpanel/cad/src/app/pages/system/system.component.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,40 @@ export class SystemComponent {
6464
}
6565
async onSwitchChange(event: Event, deployment: Deployment) {
6666
event.preventDefault();
67+
if (deployment.disableSwitch) return;
6768
if (deployment.loadingInstall) return;
68-
if (deployment.protected) {
69-
this.toastr.error('This deployment is protected and cannot be modified.', 'Error')
70-
deployment.loadingInstall = false;
71-
deployment.protected = true;
72-
return;
73-
}
74-
75-
deployment.protected = true;
69+
deployment.disableSwitch = true
7670
setTimeout(() => {
7771
deployment.loadingInstall = true;
7872
}, 400);
7973

80-
const installApiResponse = await this.deploymentManagerService.installCADForApp(this.selectedNamespace, deployment.name)
81-
if (installApiResponse.type == 'error') {
82-
deployment.protected = false
83-
this.toastr.error(installApiResponse.message, 'Error')
84-
}
85-
else {
86-
this.toastr.success(installApiResponse.message, 'Success')
87-
const applistResponse = await this.applistService.getAppList();
88-
if (applistResponse.type == 'error') this.toastr.error(applistResponse.message, 'Error');
74+
if (deployment.protected) {
75+
deployment.protected = false;
76+
const installApiResponse = await this.deploymentManagerService.uninstallCADForApp(this.selectedNamespace, deployment.name)
77+
if (installApiResponse.type == 'error') {
78+
deployment.protected = true
79+
this.toastr.error(installApiResponse.message, 'Error')
80+
}
81+
else {
82+
this.toastr.success(installApiResponse.message, 'Success')
83+
const applistResponse = await this.applistService.getAppList();
84+
if (applistResponse.type == 'error') this.toastr.error(applistResponse.message, 'Error');
85+
}
86+
} else {
87+
deployment.protected = true;
88+
const installApiResponse = await this.deploymentManagerService.installCADForApp(this.selectedNamespace, deployment.name)
89+
if (installApiResponse.type == 'error') {
90+
deployment.protected = false
91+
this.toastr.error(installApiResponse.message, 'Error')
92+
}
93+
else {
94+
this.toastr.success(installApiResponse.message, 'Success')
95+
const applistResponse = await this.applistService.getAppList();
96+
if (applistResponse.type == 'error') this.toastr.error(applistResponse.message, 'Error');
97+
}
8998
}
9099
deployment.loadingInstall = false;
100+
deployment.disableSwitch = false;
91101
}
92102

93103
async fetchNamespaces() {

controlpanel/cad/src/app/services/api/deployment-manager-api.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ export class DeploymentManagerApiService {
2525
cleanCluster() {
2626
return this.http.delete<ApiResponse>(`${this.globalState.API_URL}/customer/clean`);
2727
}
28+
uninstallCADForApp(namespace: string, deploymentName: string) {
29+
return this.http.delete<ApiResponse>(`${this.globalState.API_URL}/deployment-manager/uninstall`, { body: { deploymentAppName: deploymentName, namespace }});
30+
}
2831
}

controlpanel/cad/src/app/services/deployment-manager.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface Deployment {
99
currentReplicas: number;
1010
protected: boolean;
1111
loadingInstall: boolean;
12+
disableSwitch: boolean;
1213
}
1314

1415
@Injectable({
@@ -83,4 +84,13 @@ export class DeploymentManagerService {
8384
else return { message: "Error when cleaning cluster for app", type: 'error' };
8485
}
8586
}
87+
async uninstallCADForApp(namespace: string, deploymentName: string): Promise<ApiResponse> {
88+
try {
89+
const apiResponse = await lastValueFrom(this.deploymentManagerApi.uninstallCADForApp(namespace, deploymentName));
90+
return apiResponse;
91+
} catch(e: any) {
92+
if (e.error) return e.error;
93+
else return { message: "Error when uninstalling CAD for app", type: 'error' };
94+
}
95+
}
8696
}

0 commit comments

Comments
 (0)