Skip to content

Commit

Permalink
allow to force delete resources (#572)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Jan 11, 2024
1 parent db478b1 commit 2b98214
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes.actions

import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.Progressive
Expand All @@ -23,6 +24,7 @@ import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.PROP_RESOURCE_KIND
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService.getKinds
import io.fabric8.kubernetes.api.model.HasMetadata
import javax.swing.JCheckBox
import javax.swing.tree.TreePath

class DeleteResourceAction: StructureTreeAction() {
Expand All @@ -34,15 +36,16 @@ class DeleteResourceAction: StructureTreeAction() {
override fun actionPerformed(event: AnActionEvent?, path: Array<out TreePath>?, selected: Array<out Any>?) {
val model = getResourceModel() ?: return
val toDelete = selected?.map { it.getDescriptor()?.element as HasMetadata} ?: return
if (!userConfirmed(toDelete)) {
val operation = userConfirms(toDelete)
if (operation.isNo) {
return
}
run("Deleting ${toMessage(toDelete, 30)}...", true,
Progressive {
val telemetry = TelemetryService.instance.action("delete resource")
.property(PROP_RESOURCE_KIND, getKinds(toDelete))
try {
model.delete(toDelete)
model.delete(toDelete, operation.isForce)
Notification().info("Resources Deleted", toMessage(toDelete, 30))
telemetry.success().send()
} catch (e: MultiResourceException) {
Expand All @@ -54,12 +57,18 @@ class DeleteResourceAction: StructureTreeAction() {
})
}

private fun userConfirmed(resources: List<HasMetadata>): Boolean {
val answer = Messages.showYesNoDialog(
private fun userConfirms(resources: List<HasMetadata>): DeleteOperation {
val answer = Messages.showCheckboxMessageDialog(
"Delete ${toMessage(resources, 30)}?",
"Delete resources?",
Messages.getQuestionIcon())
return answer == Messages.OK
"Delete",
arrayOf(Messages.getYesButton(), Messages.getNoButton()),
"Force (immediate)",
false,
0,
0,
AllIcons.General.QuestionDialog,
DeleteOperation.processDialogReturnValue)
return DeleteOperation(answer)
}

override fun isVisible(selected: Array<out Any>?): Boolean {
Expand All @@ -72,4 +81,29 @@ class DeleteResourceAction: StructureTreeAction() {
return element != null
&& !hasDeletionTimestamp(element)
}
}

private class DeleteOperation(private val dialogReturn: Int) {

companion object {
const val FORCE_MASK = 0b1000000

val processDialogReturnValue = { exitCode: Int, checkbox: JCheckBox ->
if (exitCode == -1) {
Messages.CANCEL
} else {
exitCode or (if (checkbox.isSelected) FORCE_MASK else 0)
}
}
}

val isForce: Boolean
get() {
return (dialogReturn and FORCE_MASK) == FORCE_MASK
}

val isNo: Boolean
get() {
return (dialogReturn and Messages.NO) == Messages.NO
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface IResourceModel {
fun stopWatch(kind: ResourceKind<out HasMetadata>)
fun stopWatch(definition: CustomResourceDefinition)
fun invalidate(element: Any?)
fun delete(resources: List<HasMetadata>)
fun delete(resources: List<HasMetadata>, force: Boolean)
fun canWatchLog(resource: HasMetadata): Boolean
fun watchLog(container: Container, resource: HasMetadata, out: OutputStream): LogWatch?
fun stopWatch(watch: LogWatch): Boolean
Expand Down Expand Up @@ -189,8 +189,8 @@ open class ResourceModel : IResourceModel {
allContexts.current?.replaced(resource)
}

override fun delete(resources: List<HasMetadata>) {
allContexts.current?.delete(resources)
override fun delete(resources: List<HasMetadata>, force: Boolean) {
allContexts.current?.delete(resources, force)
}

override fun canWatchLog(resource: HasMetadata): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,14 +434,14 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
.filter { it.namespace == namespace }
}

override fun delete(resources: List<HasMetadata>) {
override fun delete(resources: List<HasMetadata>, force: Boolean) {
logger<ActiveContext<*, *>>().debug("Deleting ${toMessage(resources)}.")
val exceptions = resources
.distinct()
.groupBy { Pair(ResourceKind.create(it), ResourcesIn.valueOf(it, getCurrentNamespace())) }
.mapNotNull {
try {
delete(it.key.first, it.key.second, it.value)
delete(it.key.first, it.key.second, it.value, force)
modelChange.fireModified(it.value)
null
} catch (e: KubernetesClientException) {
Expand All @@ -454,7 +454,7 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
}
}

private fun delete(kind: ResourceKind<out HasMetadata>, scope: ResourcesIn, resources: List<HasMetadata>): Collection<HasMetadata> {
private fun delete(kind: ResourceKind<out HasMetadata>, scope: ResourcesIn, resources: List<HasMetadata>, force: Boolean): Collection<HasMetadata> {
val operator = getOperator(kind, scope)
if (operator == null) {
logger<ActiveContext<*,*>>().warn(
Expand All @@ -463,7 +463,7 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
return emptyList()
}
try {
val deleted = operator.delete(resources)
val deleted = operator.delete(resources, force)
return if (deleted) {
resources.forEach { setWillBeDeleted(it) }
resources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ interface IActiveContext<N: HasMetadata, C: KubernetesClient>: IContext {
* Returns `false` otherwise.
*/
fun isCurrentNamespace(namespace: String): Boolean

/**
* Deletes the given resources.
*
* @param resources the resources to delete
* @param force whether deletion should be forced (immediate deletion, no grace period)
*/
fun delete(resources: List<HasMetadata>)
fun delete(resources: List<HasMetadata>, force: Boolean)

/**
* Returns all resources of the given kind in the given scope.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import com.redhat.devtools.intellij.kubernetes.model.util.isSameResource
import io.fabric8.kubernetes.api.model.HasMetadata
import io.fabric8.kubernetes.client.Client
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.PropagationPolicyConfigurable
import io.fabric8.kubernetes.client.dsl.Deletable
import io.fabric8.kubernetes.client.dsl.ListVisitFromServerGetDeleteRecreateWaitApplicable
import io.fabric8.kubernetes.client.dsl.Resource

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

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

override fun delete(resources: List<HasMetadata>): Boolean {
override fun delete(resources: List<HasMetadata>, force: Boolean): Boolean {
@Suppress("UNCHECKED_CAST")
val toDelete = resources as? List<R> ?: return false
val status = client.adapt(KubernetesClient::class.java)
.resourceList(toDelete)
val resourceList = client.adapt(KubernetesClient::class.java)
.resourceList(toDelete) ?: return false
val status = resourceList
.immediate(force)
.delete()
return status.size == toDelete.size
}
Expand All @@ -90,7 +96,23 @@ abstract class AbstractResourceOperator<R : HasMetadata, C : Client>(protected v
return kind.clazz.isAssignableFrom(resource::class.java)
}

override fun close() {
private fun <T: HasMetadata> ListVisitFromServerGetDeleteRecreateWaitApplicable<T>.immediate(force: Boolean): PropagationPolicyConfigurable<out Deletable> {
return if (force) {
withGracePeriod(0)
} else {
this
}
}

protected fun <T: HasMetadata> Resource<T>?.immediate(force: Boolean): PropagationPolicyConfigurable<out Deletable>? {
return if (force) {
this?.withGracePeriod(0)
} else {
this
}
}

override fun close() {
client.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IResourceOperator<R: HasMetadata>: Closeable {
fun replaced(resource: HasMetadata): Boolean
fun added(resource: HasMetadata): Boolean
fun removed(resource: HasMetadata): Boolean
fun delete(resources: List<HasMetadata>): Boolean
fun delete(resources: List<HasMetadata>, force: Boolean): Boolean
fun replace(resource: HasMetadata): HasMetadata?
fun create(resource: HasMetadata): HasMetadata?
fun get(resource: HasMetadata): HasMetadata?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,23 @@ open class NamespacedCustomResourceOperator(
?.watch(typedWatcher)
}

override fun delete(resources: List<HasMetadata>): Boolean {
override fun delete(resources: List<HasMetadata>, force: Boolean): Boolean {
@Suppress("UNCHECKED_CAST")
val toDelete = resources as? List<GenericKubernetesResource> ?: return false
return toDelete.stream()
.map { delete(it) }
.map { delete(it, force) }
.reduce(false) { thisDelete, thatDelete -> thisDelete || thatDelete }
}

private fun delete(resource: HasMetadata): Boolean {
private fun delete(resource: HasMetadata, force: Boolean): Boolean {
val inNamespace = resourceNamespaceOrCurrent(resource)
getOperation()
val operation = getOperation()
?.inNamespace(inNamespace)
?.withName(resource.metadata.name)
?.delete()
?: return false
operation
.immediate(force)
?.delete()
return true
}

Expand Down Expand Up @@ -103,4 +106,4 @@ open class NamespacedCustomResourceOperator(
return client.genericKubernetesResources(context)
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,20 @@ class NonNamespacedCustomResourceOperator(
?.watch(typedWatcher)
}

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

private fun delete(name: String): Boolean {
getOperation()
?.withName(name)
?.delete()
private fun delete(name: String, force: Boolean): Boolean {
val operation = getOperation()
?.withName(name) ?: return false
operation
.immediate(force)
?.delete()
return true
}

Expand All @@ -85,4 +87,4 @@ class NonNamespacedCustomResourceOperator(
override fun getOperation(): NonNamespacedOperation<GenericKubernetesResource>? {
return client.genericKubernetesResources(context)
}
}
}
Loading

0 comments on commit 2b98214

Please sign in to comment.