Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow to force delete resources (#572) #707

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading