Skip to content

Commit

Permalink
support multiple resources in the editor (#549)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Feb 14, 2023
1 parent e94bd69 commit 661eb95
Show file tree
Hide file tree
Showing 28 changed files with 1,444 additions and 841 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ projectVersion=0.7.2-SNAPSHOT
jetBrainsToken=invalid
jetBrainsChannel=stable
intellijPluginVersion=1.10.1
intellijCommonVersion=1.9.0-SNAPSHOT
intellijCommonVersion=1.9.0
kotlin.stdlib.default.dependency = false
kotlinVersionIdea221=1.6.21
kotlinVersion=1.4.32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.redhat.devtools.intellij.kubernetes.model.ResourceWatch
import com.redhat.devtools.intellij.kubernetes.model.ResourceWatch.WatchListeners
import com.redhat.devtools.intellij.kubernetes.model.context.IActiveContext
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
import com.redhat.devtools.intellij.kubernetes.model.util.areEqual
import com.redhat.devtools.intellij.kubernetes.model.util.isNotFound
import com.redhat.devtools.intellij.kubernetes.model.util.isSameResource
import com.redhat.devtools.intellij.kubernetes.model.util.isUnsupported
Expand All @@ -25,7 +26,7 @@ import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.KubernetesClientException

/**
* A resource that exists on the cluster. May be [pull], [set] etc.
* A resource that exists on the cluster. May be [pull], [push] etc.
* Notifies listeners of addition, removal and modification if [watch]
*/
open class ClusterResource protected constructor(
Expand All @@ -48,8 +49,12 @@ open class ClusterResource protected constructor(

private val initialResource: HasMetadata = resource
protected open var updatedResource: HasMetadata? = null
private var isDeleted: Boolean = false
private var closed: Boolean = false
protected open val watchListeners = WatchListeners(
{},
{
// do nothing
},
{ removed ->
set(null)
setDeleted(true)
Expand All @@ -60,8 +65,6 @@ open class ClusterResource protected constructor(
setDeleted(false)
modelChange.fireModified(changed)
})
private var isDeleted: Boolean = false
private var closed: Boolean = false

/**
* Sets the given resource as the current value in this instance.
Expand All @@ -85,7 +88,7 @@ open class ClusterResource protected constructor(

/**
* Returns the resource in the cluster. Returns the cached value by default,
* requests it from cluster if instructed so by the given `forceRequest` parameter.
* requests it from cluster if not cached yet or is instructed to force a new request by the given `forceRequest` parameter.
*
* @param forceRequest requests from server if set to true, returns the cached value otherwise
*
Expand Down Expand Up @@ -120,40 +123,6 @@ open class ClusterResource protected constructor(
}
}

protected open fun setDeleted(deleted: Boolean) {
synchronized(this) {
this.isDeleted = deleted
}
}

fun isDeleted(): Boolean {
synchronized(this) {
return isDeleted
}
}

fun isClosed(): Boolean {
synchronized(this) {
return this.closed
}
}

fun canPush(toCompare: HasMetadata?): Boolean {
if (toCompare == null) {
return true
}
return try {
val resource = pull()
resource == null
|| (isSameResource(toCompare) && isModified(toCompare))
} catch (e: ResourceException) {
logger<ClusterResource>().warn(
"Could not request resource ${initialResource.kind} ${initialResource.metadata?.name ?: ""} from server ${context.masterUrl}",
e)
false
}
}

/**
* Pushes the given resource to the cluster. The currently existing resource on the cluster is replaced
* if it is the same resource in an older version. A new resource is created if the given resource
Expand All @@ -173,11 +142,16 @@ open class ClusterResource protected constructor(
set(updated)
return updated
} catch (e: KubernetesClientException) {
val details = getDetails(e)
throw ResourceException(details, e)
throw ResourceException(
getDetails(e),
e,
listOf(resource))
} catch (e: RuntimeException) {
// ex. IllegalArgumentException
throw ResourceException("Could not push ${resource.kind} ${resource.metadata.name ?: ""}", e)
throw ResourceException(
"Could not push ${resource.kind} ${resource.metadata.name ?: ""}",
e,
listOf(resource))
}
}

Expand All @@ -191,6 +165,18 @@ open class ClusterResource protected constructor(
return message.substring(detailsStart + detailsIdentifier.length)
}

protected open fun setDeleted(deleted: Boolean) {
synchronized(this) {
this.isDeleted = deleted
}
}

fun isDeleted(): Boolean {
synchronized(this) {
return isDeleted
}
}

/**
* Returns `true` if the given resource version is outdated when compared to the version of the resource on the cluster.
* A given resourceVersion is considered outdated if it is not equal to the resourceVersion of the resource on the cluster.
Expand All @@ -201,15 +187,14 @@ open class ClusterResource protected constructor(
* versions for equality (this means that you must not compare resource versions for greater-than or less-than
* relationships)."
*
* @param resourceVersion the resource version to compare to the version of the cluster resource
* @return true if the given resource version != resource version of the cluster resource
* @param localVersion the resource version to compare to the resource version on the cluster
* @return `true` if the given resource != resource on the cluster. `false` otherwise
*
* @see io.fabric8.kubernetes.api.model.ObjectMeta.resourceVersion
*/
fun isOutdated(resourceVersion: String?): Boolean {
val resource = pull()
val clusterVersion = resource?.metadata?.resourceVersion ?: return false
return clusterVersion != resourceVersion
fun isOutdatedVersion(localVersion: String?): Boolean {
val clusterVersion = pull()?.metadata?.resourceVersion ?: return false
return clusterVersion != localVersion
}

/**
Expand All @@ -232,9 +217,9 @@ open class ClusterResource protected constructor(
*
* @param toCompare resource to compare to the resource on the cluster
*/
fun isModified(toCompare: HasMetadata?): Boolean {
val resource = pull() ?: return false
return resource != toCompare
fun isEqual(toCompare: HasMetadata?): Boolean {
val pulled = pull() ?: return false
return areEqual(pulled, toCompare)
}

/**
Expand Down Expand Up @@ -316,6 +301,12 @@ open class ClusterResource protected constructor(
}
}

fun isClosed(): Boolean {
synchronized(this) {
return this.closed
}
}

/**
* Adds the given listener to the list of listeners that should be informed of changes to the resource given
* to this instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.redhat.devtools.intellij.kubernetes.editor.notification.ErrorNotification
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException

class EditorFocusListener(private val project: Project) : FileEditorManagerListener, FileEditorManagerListener.Before {

Expand Down Expand Up @@ -61,17 +60,9 @@ class EditorFocusListener(private val project: Project) : FileEditorManagerListe
editor: FileEditor,
project: Project
) {
if (e is ResourceException) {
ErrorNotification(editor, project).show(
e.message ?: "Undefined error",
e
)
} else {
ErrorNotification(editor, project).show(
"Error contacting cluster. Make sure it's reachable, api version supported, etc.",
e.cause ?: e
)
}
ErrorNotification(editor, project).show(
e.message ?: "Undefined error",
e)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes.editor

import com.intellij.openapi.Disposable
import com.redhat.devtools.intellij.kubernetes.model.IResourceModel
import com.redhat.devtools.intellij.kubernetes.model.IResourceModelListener
import com.redhat.devtools.intellij.kubernetes.model.context.IActiveContext
import com.redhat.devtools.intellij.kubernetes.model.resource.ResourceIdentifier
import io.fabric8.kubernetes.api.model.HasMetadata
import io.fabric8.kubernetes.client.KubernetesClient

open class EditorResourceAttributes(
// for mocking purposes
private val resourceModel: IResourceModel,
// for mocking purposes
private val clusterResourceFactory: (resource: HasMetadata, context: IActiveContext<out HasMetadata, out KubernetesClient>?) -> ClusterResource? =
ClusterResource.Factory::create,
// for mocking purposes
private val attributes: LinkedHashMap<ResourceIdentifier, ResourceAttributes> = linkedMapOf()
) : Disposable {

var resourceChangedListener: IResourceModelListener? = null

fun getClusterResource(resource: HasMetadata): ClusterResource? {
return attributes[ResourceIdentifier(resource)]?.clusterResource
}

fun getAllClusterResources(): List<ClusterResource> {
return attributes.values.mapNotNull { attributes -> attributes.clusterResource }
}

fun setLastPushedPulled(resource: HasMetadata?) {
if (resource == null) {
return
}
getAttributes(resource)?.lastPushedPulled = resource
}

fun getLastPulledPushed(resource: HasMetadata?): HasMetadata? {
return getAttributes(resource)?.lastPushedPulled
}

fun setResourceVersion(resource: HasMetadata?) {
if (resource == null) {
return
}
setResourceVersion(resource)
}

fun setResourceVersion(resource: HasMetadata?, version: String? = resource?.metadata?.resourceVersion) {
if (resource == null) {
return
}
getAttributes(resource)?.resourceVersion = version
}

fun getResourceVersion(resource: HasMetadata): String? {
return getAttributes(resource)?.resourceVersion
}

private fun getAttributes(resource: HasMetadata?): ResourceAttributes? {
if (resource == null) {
return null
}
return attributes[ResourceIdentifier(resource)]
}

fun update(resources: List<HasMetadata>) {
val identifiers = resources
.map { resource -> ResourceIdentifier(resource) }
.toSet()
removeOrphanedAttributes(identifiers)
addNewAttributes(identifiers)
}

private fun removeOrphanedAttributes(new: Set<ResourceIdentifier>) {
val toRemove = attributes
.filter { (identifier, _) -> !new.contains(identifier) }
attributes.keys.removeAll(toRemove.keys)
toRemove.values.forEach { attributes -> attributes.dispose() }
}

private fun addNewAttributes(identifiers: Set<ResourceIdentifier>) {
val existing = attributes.keys
val new = identifiers.subtract(existing)
val toPut = new.associateWith { identifier ->
ResourceAttributes(identifier.resource)
}
attributes.putAll(toPut)
}

fun disposeAll() {
dispose(attributes.keys)
}

private fun dispose(identifiers: Collection<ResourceIdentifier>) {
attributes
.filter { (resourceIdentifier, _) -> identifiers.contains(resourceIdentifier) }
.forEach { (_, attributes) ->
attributes.dispose()
}
}


override fun dispose() {
disposeAll()
}

inner class ResourceAttributes(private val resource: HasMetadata) {

val clusterResource: ClusterResource? = createClusterResource(resource)

var lastPushedPulled: HasMetadata? = resource
var resourceVersion: String? = resource.metadata.resourceVersion

private fun createClusterResource(resource: HasMetadata): ClusterResource? {
val context = resourceModel.getCurrentContext()
return if (context != null) {
val clusterResource = clusterResourceFactory.invoke(
resource,
context
)

val resourceChangeListener = resourceChangedListener
if (resourceChangeListener != null) {
clusterResource?.addListener(resourceChangeListener)
}
clusterResource?.watch()
clusterResource
} else {
null
}
}

fun dispose() {
clusterResource?.close()
lastPushedPulled = null
resourceVersion = null
}
}

}
Loading

0 comments on commit 661eb95

Please sign in to comment.