Skip to content
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 @@ -11,17 +11,24 @@
*/
package com.redhat.devtools.gateway

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.ex.ProjectManagerEx
import com.jetbrains.gateway.thinClientLink.LinkedClientManager
import com.jetbrains.gateway.thinClientLink.ThinClientHandle
import com.jetbrains.rd.util.lifetime.Lifetime
import com.redhat.devtools.gateway.openshift.DevWorkspaces
import com.redhat.devtools.gateway.openshift.Pods
import com.redhat.devtools.gateway.devworkspace.DevWorkspacePatch
import com.redhat.devtools.gateway.devworkspace.DevWorkspaceRestart
import com.redhat.devtools.gateway.devworkspace.DevWorkspaces
import com.redhat.devtools.gateway.devworkspace.RestartDevWorkspaceAnnotationWatch
import com.redhat.devtools.gateway.openshift.DevWorkspacePods
import com.redhat.devtools.gateway.server.RemoteIDEServer
import com.redhat.devtools.gateway.server.RemoteIDEServerStatus
import com.redhat.devtools.gateway.util.ProgressCountdown
import com.redhat.devtools.gateway.util.isCancellationException
import com.redhat.devtools.gateway.view.ui.Dialogs
import io.kubernetes.client.openapi.ApiClient
import kotlinx.coroutines.*
import java.io.Closeable
import java.io.IOException
Expand All @@ -33,7 +40,6 @@
import kotlin.time.Duration.Companion.seconds

class DevSpacesConnection(private val devSpacesContext: DevSpacesContext) {
@Throws(Exception::class)
@Suppress("UnstableApiUsage")
fun connect(
onConnected: () -> Unit,
Expand Down Expand Up @@ -116,7 +122,7 @@
onProgress?.invoke(ProgressCountdown.ProgressEvent(
message = "Waiting for the workspace IDE client to start..."))

val pods = Pods(devSpacesContext.client)
val pods = DevWorkspacePods(devSpacesContext.client)
val localPort = findFreePort()
forwarder = pods.forward(remoteIdeServer.pod, localPort, 5990)
pods.waitForForwardReady(localPort)
Expand Down Expand Up @@ -162,6 +168,14 @@
"Could not connect, workspace IDE is not ready."
}

// Watch for restart annotation on the DevWorkspace
watchRestartAnnotation(
workspace.namespace,
workspace.name,
devSpacesContext.client,
client
)

onConnected()
client
} catch (e: Exception) {
Expand All @@ -171,6 +185,31 @@
}
}

@Suppress("UnstableApiUsage")
private fun watchRestartAnnotation(namespace: String, workspaceName: String, kubeClient: ApiClient, thinClient: ThinClientHandle) {
val restartWatchScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
RestartDevWorkspaceAnnotationWatch(
onRestartAnnotated(namespace, workspaceName, thinClient),
kubeClient,
namespace,
workspaceName
).start(restartWatchScope)

thinClient.lifetime.onTermination {
restartWatchScope.cancel()
}
}

@Suppress("UnstableApiUsage")
private fun onRestartAnnotated(namespace: String, workspaceName: String, thinClient: ThinClientHandle): () -> Job {
return {
CoroutineScope(Dispatchers.IO).launch {
val restartHandler = DevWorkspaceRestart(namespace, workspaceName, devSpacesContext.client)
restartHandler.execute(thinClient)
}
}
}

@Suppress("UnstableApiUsage")
private fun onClientClosed(
client: ThinClientHandle? = null,
Expand All @@ -181,14 +220,29 @@
) {
CoroutineScope(Dispatchers.IO).launch {
runCatching { client?.close() }
val currentWorkspace = devSpacesContext.devWorkspace
val workspace = devSpacesContext.devWorkspace
val workspacePatch = DevWorkspacePatch(
workspace.namespace,
workspace.name,
devSpacesContext.client,
{

Check notice on line 228 in src/main/kotlin/com/redhat/devtools/gateway/DevSpacesConnection.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Lambda argument inside parentheses

Lambda argument should be moved out of parentheses
DevWorkspaces(devSpacesContext.client).get(workspace.namespace, workspace.name)
}
)
try {
if (true == remoteIdeServer?.waitServerTerminated()) {
if (workspacePatch.hasRestartAnnotation()) {
/**
* user triggered restart
* logic to restart workspace is in [onRestartAnnotated]
* The annotation will be cleaned up by DevWorkspaceRestart
*/
closeAllProjects()
} else if (true == remoteIdeServer?.waitServerTerminated()) {
/**
* user closed IDE and clicked "Close and Stop"
*/
DevWorkspaces(devSpacesContext.client)
.stop(
devSpacesContext.devWorkspace.namespace,
devSpacesContext.devWorkspace.name
)
.stop(workspace.namespace, workspace.name)
.also { onDevWorkspaceStopped() }
}
} finally {
Expand All @@ -197,12 +251,26 @@
}.onFailure { e ->
thisLogger().debug("Failed to close port forwarder", e)
}
devSpacesContext.removeWorkspace(currentWorkspace)
devSpacesContext.removeWorkspace(workspace)
runCatching { onDisconnected() }
}
}
}

private fun closeAllProjects() {
ApplicationManager.getApplication().invokeLater(
{
val pm = ProjectManagerEx.getInstanceEx()
for (project in pm.openProjects.toList()) {
if (!project.isDisposed) {
pm.closeAndDispose(project)
}
}
},
ModalityState.nonModal()
)
}

private fun findFreePort(): Int {
ServerSocket(0).use { socket ->
socket.reuseAddress = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import com.jetbrains.gateway.api.ConnectionRequestor
import com.jetbrains.gateway.api.GatewayConnectionHandle
import com.jetbrains.gateway.api.GatewayConnectionProvider
import com.redhat.devtools.gateway.kubeconfig.KubeConfigUtils
import com.redhat.devtools.gateway.openshift.DevWorkspaces
import com.redhat.devtools.gateway.devworkspace.DevWorkspaces
import com.redhat.devtools.gateway.openshift.OpenShiftClientFactory
import com.redhat.devtools.gateway.openshift.isNotFound
import com.redhat.devtools.gateway.openshift.isUnauthorized
Expand Down Expand Up @@ -190,7 +190,6 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
thisLogger().error("Query parameter \"$DW_NAME\" is missing")
throw IllegalArgumentException("Query parameter \"$DW_NAME\" is missing")
}

val ctx = DevSpacesContext()

indicator.update(message = "Initializing Kubernetes connection…")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
*/
package com.redhat.devtools.gateway

import com.redhat.devtools.gateway.openshift.DevWorkspace
import com.redhat.devtools.gateway.devworkspace.DevWorkspace
import io.kubernetes.client.openapi.ApiClient

class DevSpacesContext {
class DevSpacesContext() {

Check notice on line 17 in src/main/kotlin/com/redhat/devtools/gateway/DevSpacesContext.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Redundant empty primary constructor

Remove empty primary constructor
lateinit var client: ApiClient
lateinit var devWorkspace: DevWorkspace
var activeWorkspaces = mutableSetOf<DevWorkspace>()
Expand All @@ -34,4 +34,4 @@
}
}

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024-2026 Red Hat, Inc.
* Copyright (c) 2024-2025 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -9,8 +9,9 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.devtools.gateway.openshift
package com.redhat.devtools.gateway.devworkspace

import com.redhat.devtools.gateway.openshift.Utils
import java.util.Collections.emptyMap

data class DevWorkspace(
Expand Down Expand Up @@ -48,14 +49,14 @@ data class DevWorkspace(
return status.running
}

val labels: Any?
val annotations: Map<String, String>
get() {
return metadata.labels
return metadata.annotations
}

val cheEditor: String?
val labels: Map<String, String>
get() {
return metadata.cheEditor
return metadata.labels
}

companion object {
Expand All @@ -78,11 +79,10 @@ data class DevWorkspace(

other as DevWorkspace

if (metadata.name != other.metadata.name) return false
if (metadata.namespace != other.metadata.namespace) return false
if (metadata.cheEditor != other.metadata.cheEditor) return false

return true
return metadata.name == other.metadata.name &&
metadata.namespace == other.metadata.namespace &&
metadata.annotations == other.metadata.annotations &&
labels == other.labels
}

override fun hashCode(): Int {
Expand All @@ -97,23 +97,27 @@ data class DevWorkspaceObjectMeta(
val name: String,
val namespace: String,
val uid: String,
val labels: Any?,
val cheEditor: String?
val annotations: Map<String, String>,
val labels: Map<String, String>
) {
companion object {
fun from(map: Any) = object {
val name = Utils.getValue(map, arrayOf("name"))
val namespace = Utils.getValue(map, arrayOf("namespace"))
val uid = Utils.getValue(map, arrayOf("uid"))
val labels = Utils.getValue(map, arrayOf("labels"))
val cheEditor = Utils.getValue(map, arrayOf("annotations", "che.eclipse.org/che-editor"))
@Suppress("UNCHECKED_CAST")
val annotations = (Utils.getValue(map, arrayOf("annotations")) as? Map<String, String>)
?: emptyMap<String, String>()
@Suppress("UNCHECKED_CAST")
val labels = (Utils.getValue(map, arrayOf("labels")) as? Map<String, String>)
?: emptyMap<String, String>()

val data = DevWorkspaceObjectMeta(
name as String,
namespace as String,
uid as String,
labels,
cheEditor as String?
annotations,
labels
)
}.data
}
Expand Down Expand Up @@ -150,6 +154,5 @@ data class DevWorkspaceStatus(
get() {
return phase == "Running"
}

}

Loading
Loading