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

support multiple resources in the editor (#549) #551

Merged
merged 1 commit into from
Feb 14, 2023
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
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