Skip to content

Commit 45ae873

Browse files
committed
allow to force delete resources (#572)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent db478b1 commit 45ae873

File tree

13 files changed

+159
-60
lines changed

13 files changed

+159
-60
lines changed

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/actions/DeleteResourceAction.kt

+42-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
******************************************************************************/
1111
package com.redhat.devtools.intellij.kubernetes.actions
1212

13+
import com.intellij.icons.AllIcons
1314
import com.intellij.openapi.actionSystem.AnActionEvent
1415
import com.intellij.openapi.diagnostic.logger
1516
import com.intellij.openapi.progress.Progressive
@@ -23,6 +24,7 @@ import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService
2324
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_RESOURCE_KIND
2425
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.getKinds
2526
import io.fabric8.kubernetes.api.model.HasMetadata
27+
import javax.swing.JCheckBox
2628
import javax.swing.tree.TreePath
2729

2830
class DeleteResourceAction: StructureTreeAction() {
@@ -34,15 +36,16 @@ class DeleteResourceAction: StructureTreeAction() {
3436
override fun actionPerformed(event: AnActionEvent?, path: Array<out TreePath>?, selected: Array<out Any>?) {
3537
val model = getResourceModel() ?: return
3638
val toDelete = selected?.map { it.getDescriptor()?.element as HasMetadata} ?: return
37-
if (!userConfirmed(toDelete)) {
39+
val operation = userConfirms(toDelete)
40+
if (operation.isNo) {
3841
return
3942
}
4043
run("Deleting ${toMessage(toDelete, 30)}...", true,
4144
Progressive {
4245
val telemetry = TelemetryService.instance.action("delete resource")
4346
.property(PROP_RESOURCE_KIND, getKinds(toDelete))
4447
try {
45-
model.delete(toDelete)
48+
model.delete(toDelete, operation.isForce)
4649
Notification().info("Resources Deleted", toMessage(toDelete, 30))
4750
telemetry.success().send()
4851
} catch (e: MultiResourceException) {
@@ -54,12 +57,18 @@ class DeleteResourceAction: StructureTreeAction() {
5457
})
5558
}
5659

57-
private fun userConfirmed(resources: List<HasMetadata>): Boolean {
58-
val answer = Messages.showYesNoDialog(
60+
private fun userConfirms(resources: List<HasMetadata>): DeleteOperation {
61+
val answer = Messages.showCheckboxMessageDialog(
5962
"Delete ${toMessage(resources, 30)}?",
60-
"Delete resources?",
61-
Messages.getQuestionIcon())
62-
return answer == Messages.OK
63+
"Delete",
64+
arrayOf(Messages.getYesButton(), Messages.getNoButton()),
65+
"Force (immediate)",
66+
false,
67+
0,
68+
0,
69+
AllIcons.General.QuestionDialog,
70+
DeleteOperation.processDialogReturnValue)
71+
return DeleteOperation(answer)
6372
}
6473

6574
override fun isVisible(selected: Array<out Any>?): Boolean {
@@ -72,4 +81,29 @@ class DeleteResourceAction: StructureTreeAction() {
7281
return element != null
7382
&& !hasDeletionTimestamp(element)
7483
}
75-
}
84+
85+
private class DeleteOperation(private val dialogReturn: Int) {
86+
87+
companion object {
88+
const val FORCE_MASK = 0b1000000
89+
90+
val processDialogReturnValue = { exitCode: Int, checkbox: JCheckBox ->
91+
if (exitCode == -1) {
92+
Messages.CANCEL
93+
} else {
94+
exitCode or (if (checkbox.isSelected) FORCE_MASK else 0)
95+
}
96+
}
97+
}
98+
99+
val isForce: Boolean
100+
get() {
101+
return (dialogReturn and FORCE_MASK) == FORCE_MASK
102+
}
103+
104+
val isNo: Boolean
105+
get() {
106+
return (dialogReturn and Messages.NO) == Messages.NO
107+
}
108+
}
109+
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/ResourceModel.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ interface IResourceModel {
5252
fun stopWatch(kind: ResourceKind<out HasMetadata>)
5353
fun stopWatch(definition: CustomResourceDefinition)
5454
fun invalidate(element: Any?)
55-
fun delete(resources: List<HasMetadata>)
55+
fun delete(resources: List<HasMetadata>, force: Boolean)
5656
fun canWatchLog(resource: HasMetadata): Boolean
5757
fun watchLog(container: Container, resource: HasMetadata, out: OutputStream): LogWatch?
5858
fun stopWatch(watch: LogWatch): Boolean
@@ -189,8 +189,8 @@ open class ResourceModel : IResourceModel {
189189
allContexts.current?.replaced(resource)
190190
}
191191

192-
override fun delete(resources: List<HasMetadata>) {
193-
allContexts.current?.delete(resources)
192+
override fun delete(resources: List<HasMetadata>, force: Boolean) {
193+
allContexts.current?.delete(resources, force)
194194
}
195195

196196
override fun canWatchLog(resource: HasMetadata): Boolean {

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/ActiveContext.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,14 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
434434
.filter { it.namespace == namespace }
435435
}
436436

437-
override fun delete(resources: List<HasMetadata>) {
437+
override fun delete(resources: List<HasMetadata>, force: Boolean) {
438438
logger<ActiveContext<*, *>>().debug("Deleting ${toMessage(resources)}.")
439439
val exceptions = resources
440440
.distinct()
441441
.groupBy { Pair(ResourceKind.create(it), ResourcesIn.valueOf(it, getCurrentNamespace())) }
442442
.mapNotNull {
443443
try {
444-
delete(it.key.first, it.key.second, it.value)
444+
delete(it.key.first, it.key.second, it.value, force)
445445
modelChange.fireModified(it.value)
446446
null
447447
} catch (e: KubernetesClientException) {
@@ -454,7 +454,7 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
454454
}
455455
}
456456

457-
private fun delete(kind: ResourceKind<out HasMetadata>, scope: ResourcesIn, resources: List<HasMetadata>): Collection<HasMetadata> {
457+
private fun delete(kind: ResourceKind<out HasMetadata>, scope: ResourcesIn, resources: List<HasMetadata>, force: Boolean): Collection<HasMetadata> {
458458
val operator = getOperator(kind, scope)
459459
if (operator == null) {
460460
logger<ActiveContext<*,*>>().warn(
@@ -463,7 +463,7 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
463463
return emptyList()
464464
}
465465
try {
466-
val deleted = operator.delete(resources)
466+
val deleted = operator.delete(resources, force)
467467
return if (deleted) {
468468
resources.forEach { setWillBeDeleted(it) }
469469
resources

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/IActiveContext.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,14 @@ interface IActiveContext<N: HasMetadata, C: KubernetesClient>: IContext {
100100
* Returns `false` otherwise.
101101
*/
102102
fun isCurrentNamespace(namespace: String): Boolean
103+
103104
/**
104105
* Deletes the given resources.
106+
*
107+
* @param resources the resources to delete
108+
* @param force whether deletion should be forced (immediate deletion, no grace period)
105109
*/
106-
fun delete(resources: List<HasMetadata>)
110+
fun delete(resources: List<HasMetadata>, force: Boolean)
107111

108112
/**
109113
* Returns all resources of the given kind in the given scope.

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/resource/AbstractResourceOperator.kt

+26-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import com.redhat.devtools.intellij.kubernetes.model.util.isSameResource
1515
import io.fabric8.kubernetes.api.model.HasMetadata
1616
import io.fabric8.kubernetes.client.Client
1717
import io.fabric8.kubernetes.client.KubernetesClient
18+
import io.fabric8.kubernetes.client.PropagationPolicyConfigurable
19+
import io.fabric8.kubernetes.client.dsl.Deletable
20+
import io.fabric8.kubernetes.client.dsl.ListVisitFromServerGetDeleteRecreateWaitApplicable
21+
import io.fabric8.kubernetes.client.dsl.Resource
1822

1923
abstract class AbstractResourceOperator<R : HasMetadata, C : Client>(protected val client: C) : IResourceOperator<R> {
2024

@@ -77,11 +81,13 @@ abstract class AbstractResourceOperator<R : HasMetadata, C : Client>(protected v
7781
return true
7882
}
7983

80-
override fun delete(resources: List<HasMetadata>): Boolean {
84+
override fun delete(resources: List<HasMetadata>, force: Boolean): Boolean {
8185
@Suppress("UNCHECKED_CAST")
8286
val toDelete = resources as? List<R> ?: return false
83-
val status = client.adapt(KubernetesClient::class.java)
84-
.resourceList(toDelete)
87+
val resourceList = client.adapt(KubernetesClient::class.java)
88+
.resourceList(toDelete) ?: return false
89+
val status = resourceList
90+
.immediate(force)
8591
.delete()
8692
return status.size == toDelete.size
8793
}
@@ -90,7 +96,23 @@ abstract class AbstractResourceOperator<R : HasMetadata, C : Client>(protected v
9096
return kind.clazz.isAssignableFrom(resource::class.java)
9197
}
9298

93-
override fun close() {
99+
private fun <T: HasMetadata> ListVisitFromServerGetDeleteRecreateWaitApplicable<T>.immediate(force: Boolean): PropagationPolicyConfigurable<out Deletable> {
100+
return if (force) {
101+
withGracePeriod(0)
102+
} else {
103+
this
104+
}
105+
}
106+
107+
protected fun <T: HasMetadata> Resource<T>?.immediate(force: Boolean): PropagationPolicyConfigurable<out Deletable>? {
108+
return if (force) {
109+
this?.withGracePeriod(0)
110+
} else {
111+
this
112+
}
113+
}
114+
115+
override fun close() {
94116
client.close()
95117
}
96118
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/resource/IResourceOperator.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface IResourceOperator<R: HasMetadata>: Closeable {
2727
fun replaced(resource: HasMetadata): Boolean
2828
fun added(resource: HasMetadata): Boolean
2929
fun removed(resource: HasMetadata): Boolean
30-
fun delete(resources: List<HasMetadata>): Boolean
30+
fun delete(resources: List<HasMetadata>, force: Boolean): Boolean
3131
fun replace(resource: HasMetadata): HasMetadata?
3232
fun create(resource: HasMetadata): HasMetadata?
3333
fun get(resource: HasMetadata): HasMetadata?

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/resource/kubernetes/custom/NamespacedCustomResourceOperator.kt

+10-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata
2020
import io.fabric8.kubernetes.client.KubernetesClient
2121
import io.fabric8.kubernetes.client.Watch
2222
import io.fabric8.kubernetes.client.Watcher
23+
import io.fabric8.kubernetes.client.dsl.Resource
2324
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
2425

2526
open class NamespacedCustomResourceOperator(
@@ -58,20 +59,23 @@ open class NamespacedCustomResourceOperator(
5859
?.watch(typedWatcher)
5960
}
6061

61-
override fun delete(resources: List<HasMetadata>): Boolean {
62+
override fun delete(resources: List<HasMetadata>, force: Boolean): Boolean {
6263
@Suppress("UNCHECKED_CAST")
6364
val toDelete = resources as? List<GenericKubernetesResource> ?: return false
6465
return toDelete.stream()
65-
.map { delete(it) }
66+
.map { delete(it, force) }
6667
.reduce(false) { thisDelete, thatDelete -> thisDelete || thatDelete }
6768
}
6869

69-
private fun delete(resource: HasMetadata): Boolean {
70+
private fun delete(resource: HasMetadata, force: Boolean): Boolean {
7071
val inNamespace = resourceNamespaceOrCurrent(resource)
71-
getOperation()
72+
val operation = getOperation()
7273
?.inNamespace(inNamespace)
7374
?.withName(resource.metadata.name)
74-
?.delete()
75+
?: return false
76+
operation
77+
.immediate(force)
78+
?.delete()
7579
return true
7680
}
7781

@@ -103,4 +107,4 @@ open class NamespacedCustomResourceOperator(
103107
return client.genericKubernetesResources(context)
104108
}
105109

106-
}
110+
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/resource/kubernetes/custom/NonNamespacedCustomResourceOperator.kt

+9-7
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,20 @@ class NonNamespacedCustomResourceOperator(
4949
?.watch(typedWatcher)
5050
}
5151

52-
override fun delete(resources: List<HasMetadata>): Boolean {
52+
override fun delete(resources: List<HasMetadata>, force: Boolean): Boolean {
5353
@Suppress("UNCHECKED_CAST")
5454
val toDelete = resources as? List<GenericKubernetesResource> ?: return false
5555
return toDelete.stream()
56-
.map { delete(it.metadata.name) }
56+
.map { delete(it.metadata.name, force) }
5757
.reduce(false ) { thisDelete, thatDelete -> thisDelete || thatDelete }
5858
}
5959

60-
private fun delete(name: String): Boolean {
61-
getOperation()
62-
?.withName(name)
63-
?.delete()
60+
private fun delete(name: String, force: Boolean): Boolean {
61+
val operation = getOperation()
62+
?.withName(name) ?: return false
63+
operation
64+
.immediate(force)
65+
?.delete()
6466
return true
6567
}
6668

@@ -85,4 +87,4 @@ class NonNamespacedCustomResourceOperator(
8587
override fun getOperation(): NonNamespacedOperation<GenericKubernetesResource>? {
8688
return client.genericKubernetesResources(context)
8789
}
88-
}
90+
}

0 commit comments

Comments
 (0)