From e5e57978afbf76934c851fed993b4462c9ad6d49 Mon Sep 17 00:00:00 2001 From: Prabhat Khera <91852476+prabhat-org@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:35:30 +1200 Subject: [PATCH] delete force terminating namespace (#7081) --- app/kubernetes/rest/namespace.js | 8 +++++ app/kubernetes/services/namespaceService.js | 27 ++++++++++++++++ .../resource-pools/resourcePoolsController.js | 32 +++++++++++++------ .../services/modal.service/confirm.ts | 19 +++++++++++ app/portainer/services/modal.service/index.ts | 2 ++ 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/kubernetes/rest/namespace.js b/app/kubernetes/rest/namespace.js index 482ca4132..9cec537f0 100644 --- a/app/kubernetes/rest/namespace.js +++ b/app/kubernetes/rest/namespace.js @@ -26,6 +26,14 @@ angular.module('portainer.kubernetes').factory('KubernetesNamespaces', [ transformResponse: rawResponse, ignoreLoadingBar: true, }, + getJSON: { + method: 'GET', + headers: { + Accept: 'application/json', + }, + transformResponse: rawResponse, + ignoreLoadingBar: true, + }, create: { method: 'POST' }, update: { method: 'PUT' }, delete: { method: 'DELETE' }, diff --git a/app/kubernetes/services/namespaceService.js b/app/kubernetes/services/namespaceService.js index df8670b0f..1b85bd639 100644 --- a/app/kubernetes/services/namespaceService.js +++ b/app/kubernetes/services/namespaceService.js @@ -17,6 +17,8 @@ class KubernetesNamespaceService { this.getAllAsync = this.getAllAsync.bind(this); this.createAsync = this.createAsync.bind(this); this.deleteAsync = this.deleteAsync.bind(this); + this.getJSONAsync = this.getJSONAsync.bind(this); + this.updateFinalizeAsync = this.updateFinalizeAsync.bind(this); } /** @@ -36,6 +38,31 @@ class KubernetesNamespaceService { } } + /** + * GET namesspace in JSON format + */ + async getJSONAsync(name) { + try { + const params = new KubernetesCommonParams(); + params.id = name; + await this.KubernetesNamespaces().status(params).$promise; + return await this.KubernetesNamespaces().getJSON(params).$promise; + } catch (err) { + throw new PortainerError('Unable to retrieve namespace', err); + } + } + + /** + * Update finalize + */ + async updateFinalizeAsync(namespace) { + try { + return await this.KubernetesNamespaces().update({ id: namespace.metadata.name, action: 'finalize' }, namespace).$promise; + } catch (err) { + throw new PortainerError('Unable to update namespace', err); + } + } + async getAllAsync() { try { const data = await this.KubernetesNamespaces().get().$promise; diff --git a/app/kubernetes/views/resource-pools/resourcePoolsController.js b/app/kubernetes/views/resource-pools/resourcePoolsController.js index cd5403946..de3ba6b7b 100644 --- a/app/kubernetes/views/resource-pools/resourcePoolsController.js +++ b/app/kubernetes/views/resource-pools/resourcePoolsController.js @@ -2,12 +2,13 @@ import angular from 'angular'; class KubernetesResourcePoolsController { /* @ngInject */ - constructor($async, $state, Notifications, ModalService, KubernetesResourcePoolService) { + constructor($async, $state, Notifications, ModalService, KubernetesResourcePoolService, KubernetesNamespaceService) { this.$async = $async; this.$state = $state; this.Notifications = Notifications; this.ModalService = ModalService; this.KubernetesResourcePoolService = KubernetesResourcePoolService; + this.KubernetesNamespaceService = KubernetesNamespaceService; this.onInit = this.onInit.bind(this); this.getResourcePools = this.getResourcePools.bind(this); @@ -20,7 +21,19 @@ class KubernetesResourcePoolsController { let actionCount = selectedItems.length; for (const pool of selectedItems) { try { - await this.KubernetesResourcePoolService.delete(pool); + const isTerminating = pool.Namespace.Status === 'Terminating'; + if (isTerminating) { + const ns = await this.KubernetesNamespaceService.getJSONAsync(pool.Namespace.Name); + ns.$promise.then(async (namespace) => { + const n = JSON.parse(namespace.data); + if (n.spec && n.spec.finalizers) { + delete n.spec.finalizers; + } + await this.KubernetesNamespaceService.updateFinalizeAsync(n); + }); + } else { + await this.KubernetesResourcePoolService.delete(pool); + } this.Notifications.success('Namespace successfully removed', pool.Namespace.Name); const index = this.resourcePools.indexOf(pool); this.resourcePools.splice(index, 1); @@ -36,14 +49,15 @@ class KubernetesResourcePoolsController { } removeAction(selectedItems) { - this.ModalService.confirmDeletion( - 'Do you want to remove the selected namespace(s)? All the resources associated to the selected namespace(s) will be removed too.', - (confirmed) => { - if (confirmed) { - return this.$async(this.removeActionAsync, selectedItems); - } + const isTerminatingNS = selectedItems.some((pool) => pool.Namespace.Status === 'Terminating'); + const message = isTerminatingNS + ? 'At least one namespace is in a terminating state. For terminating state namespaces, you may continue and force removal, but doing so without having properly cleaned up may lead to unstable and unpredictable behavior. Are you sure you wish to proceed?' + : 'Do you want to remove the selected namespace(s)? All the resources associated to the selected namespace(s) will be removed too. Are you sure you wish to proceed?'; + this.ModalService.confirmWithTitle(isTerminatingNS ? 'Force namespace removal' : 'Are you sure?', message, (confirmed) => { + if (confirmed) { + return this.$async(this.removeActionAsync, selectedItems); } - ); + }); } async getResourcePoolsAsync() { diff --git a/app/portainer/services/modal.service/confirm.ts b/app/portainer/services/modal.service/confirm.ts index 482c8d731..4f80c859a 100644 --- a/app/portainer/services/modal.service/confirm.ts +++ b/app/portainer/services/modal.service/confirm.ts @@ -100,6 +100,25 @@ export function confirmDeletion(message: string, callback: ConfirmCallback) { }); } +export function confirmWithTitle( + title: string, + message: string, + callback: ConfirmCallback +) { + const messageSanitized = sanitize(message); + confirm({ + title: sanitize(title), + message: messageSanitized, + buttons: { + confirm: { + label: 'Remove', + className: 'btn-danger', + }, + }, + callback, + }); +} + export function confirmDetachment(message: string, callback: ConfirmCallback) { const messageSanitized = sanitize(message); confirm({ diff --git a/app/portainer/services/modal.service/index.ts b/app/portainer/services/modal.service/index.ts index 19012110a..1def4d400 100644 --- a/app/portainer/services/modal.service/index.ts +++ b/app/portainer/services/modal.service/index.ts @@ -16,6 +16,7 @@ import { confirmWebEditorDiscard, confirm, confirmForceChangePassword, + confirmWithTitle, } from './confirm'; import { confirmContainerDeletion, @@ -58,5 +59,6 @@ export function ModalServiceAngular() { selectRegistry, confirmContainerDeletion, confirmForceChangePassword, + confirmWithTitle, }; }